From e7db08db32c0783d3c6616354b3bf130ce8b3bdd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 22 Mar 2024 12:37:19 -0700 Subject: [PATCH 1/6] Add documentation and examples for `wasmtime-wasi` This commit adds lots of missing documentation and examples to top-level types in `wasmtime-wasi`, mostly related to WASIp2. I've additionally made a number of small refactorings here to try to make the APIs a bit more straightforward and symmetric and simplify where I can. * Remove `bindings::wasi` (reexports are still present under `bindings`) * Rename `bindings::sync_io` to `bindings::sync` * Generate fewer bindings in `bindings::sync` that can be pulled in from the `bindings` module. * Change `WasiCtxBuilder::preopened_dir` to take a path instead of a `Dir` argument to avoid the need for another import. * Synchronize `wasmtime_wasi_http::{add_to_linker, sync::add_to_linker}` in terms of interfaces added. * Remove `wasmtime_wasi::command` and move the generated types to the `bindings` module. * Move top-level add-to-linker functions to `wasmtime_wasi::add_to_linker_sync` and `wasmtime_wasi::add_to_linker_async`. Closes #8187 Closes #8188 --- crates/wasi-http/src/proxy.rs | 18 +- crates/wasi-http/tests/all/async_.rs | 4 +- crates/wasi-http/tests/all/sync.rs | 5 +- crates/wasi/src/bindings.rs | 350 +++++++++++++----- crates/wasi/src/command.rs | 91 ----- crates/wasi/src/ctx.rs | 275 +++++++++++++- crates/wasi/src/filesystem.rs | 9 + crates/wasi/src/host/filesystem/sync.rs | 4 +- crates/wasi/src/host/io.rs | 4 +- crates/wasi/src/lib.rs | 361 ++++++++++++++++++- crates/wasi/src/network.rs | 3 +- crates/wasi/src/poll.rs | 57 ++- crates/wasi/src/preview1.rs | 2 + crates/wasi/src/stdio.rs | 8 + crates/wasi/src/stdio/worker_thread_stdin.rs | 4 + crates/wasi/tests/all/api.rs | 14 +- crates/wasi/tests/all/async_.rs | 5 +- crates/wasi/tests/all/main.rs | 4 +- crates/wasi/tests/all/sync.rs | 5 +- src/commands/run.rs | 32 +- src/commands/serve.rs | 2 +- 21 files changed, 993 insertions(+), 264 deletions(-) diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs index baf13d4d86ef..12f29ffd4dd0 100644 --- a/crates/wasi-http/src/proxy.rs +++ b/crates/wasi-http/src/proxy.rs @@ -12,7 +12,7 @@ wasmtime::component::bindgen!({ pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> anyhow::Result<()> where - T: WasiHttpView + wasmtime_wasi::WasiView + bindings::http::types::Host, + T: WasiHttpView + wasmtime_wasi::WasiView, { wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; @@ -47,18 +47,24 @@ pub mod sync { async: false, with: { "wasi:http": bindings::http, // http is in this crate - "wasi:io": wasmtime_wasi::bindings::sync_io, // io is sync + "wasi:io": wasmtime_wasi::bindings::sync, // io is sync "wasi": wasmtime_wasi::bindings, // everything else }, }); pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> anyhow::Result<()> where - T: WasiHttpView + wasmtime_wasi::WasiView + bindings::http::types::Host, + T: WasiHttpView + wasmtime_wasi::WasiView, { - // TODO: this shouldn't be required, but the adapter unconditionally pulls in all of these - // dependencies. - wasmtime_wasi::command::sync::add_to_linker(l)?; + wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::sync::io::poll::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::sync::io::streams::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::io::error::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::cli::stdin::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::cli::stdout::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::cli::stderr::add_to_linker(l, |t| t)?; + wasmtime_wasi::bindings::random::random::add_to_linker(l, |t| t)?; add_only_http_to_linker(l)?; diff --git a/crates/wasi-http/tests/all/async_.rs b/crates/wasi-http/tests/all/async_.rs index 7d6ac8572230..1ed1bea7f7b0 100644 --- a/crates/wasi-http/tests/all/async_.rs +++ b/crates/wasi-http/tests/all/async_.rs @@ -1,6 +1,6 @@ use super::*; use test_programs_artifacts::*; -use wasmtime_wasi::command::Command; +use wasmtime_wasi::bindings::Command; foreach_http!(assert_test_exists); @@ -13,7 +13,7 @@ async fn run(path: &str, server: &Server) -> Result<()> { let component = Component::from_file(&engine, path)?; let mut store = store(&engine, server); let mut linker = Linker::new(&engine); - wasmtime_wasi::command::add_to_linker(&mut linker)?; + wasmtime_wasi::add_to_linker_async(&mut linker)?; wasmtime_wasi_http::proxy::add_only_http_to_linker(&mut linker)?; let (command, _instance) = Command::instantiate_async(&mut store, &component, &linker).await?; let result = command.wasi_cli_run().call_run(&mut store).await?; diff --git a/crates/wasi-http/tests/all/sync.rs b/crates/wasi-http/tests/all/sync.rs index 7b61ed9b73b4..ac6567f38e11 100644 --- a/crates/wasi-http/tests/all/sync.rs +++ b/crates/wasi-http/tests/all/sync.rs @@ -1,6 +1,6 @@ use super::*; use test_programs_artifacts::*; -use wasmtime_wasi::command::sync::Command; +use wasmtime_wasi::bindings::sync::Command; foreach_http!(assert_test_exists); @@ -12,7 +12,8 @@ fn run(path: &str, server: &Server) -> Result<()> { let component = Component::from_file(&engine, path)?; let mut store = store(&engine, server); let mut linker = Linker::new(&engine); - wasmtime_wasi_http::proxy::sync::add_to_linker(&mut linker)?; + wasmtime_wasi::add_to_linker_sync(&mut linker)?; + wasmtime_wasi_http::proxy::sync::add_only_http_to_linker(&mut linker)?; let (command, _instance) = Command::instantiate(&mut store, &component, &linker)?; let result = command.wasi_cli_run().call_run(&mut store)?; result.map_err(|()| anyhow::anyhow!("run returned an error")) diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs index a313326da3e2..faf1e77ce695 100644 --- a/crates/wasi/src/bindings.rs +++ b/crates/wasi/src/bindings.rs @@ -2,26 +2,27 @@ // // Note that this is only done for interfaces which can block, or those which // have some functions in `only_imports` below for being async. -pub mod sync_io { - pub(crate) mod _internal { +pub mod sync { + mod generated { use crate::{FsError, StreamError}; wasmtime::component::bindgen!({ path: "wit", - interfaces: " - import wasi:io/poll@0.2.0; - import wasi:io/streams@0.2.0; - import wasi:filesystem/types@0.2.0; - ", + world: "wasi:cli/command", tracing: true, trappable_error_type: { "wasi:io/streams/stream-error" => StreamError, "wasi:filesystem/types/error-code" => FsError, }, with: { - // This interface comes from the outer module, as it's + // These interfaces comes from the outer module, as it's // sync/async agnostic. "wasi:clocks": crate::bindings::clocks, + "wasi:random": crate::bindings::random, + "wasi:sockets": crate::bindings::sockets, + "wasi:cli": crate::bindings::cli, + "wasi:io/error": crate::bindings::io::error, + "wasi:filesystem/preopens": crate::bindings::filesystem::preopens, // Configure the resource types of the bound interfaces here // to be the same as the async versions of the resources, that @@ -31,99 +32,252 @@ pub mod sync_io { "wasi:io/poll/pollable": super::super::io::poll::Pollable, "wasi:io/streams/input-stream": super::super::io::streams::InputStream, "wasi:io/streams/output-stream": super::super::io::streams::OutputStream, - "wasi:io/error/error": super::super::io::error::Error, } }); } - pub use self::_internal::wasi::{filesystem, io}; + pub use self::generated::exports; + pub use self::generated::wasi::{filesystem, io}; + + /// Synchronous bindings to execute and run a `wasi:cli/command`. + /// + /// This structure is automatically generated by `bindgen!` and is intended + /// to be used with [`Config::async_support(false)`][async]. For the + /// asynchronous version see [`binidngs::Command`](super::Command). + /// + /// This can be used for a more "typed" view of executing a command + /// component through the [`Command::wasi_cli_run`] method plus + /// [`Guest::call_run`](exports::wasi::cli::run::Guest::call_run). + /// + /// > **Note**: it's recommended to use + /// > [`wasmtime_wasi::add_to_linker_sync`] instead of the auto-generated + /// > [`Command::add_to_linker`] here. + /// + /// [async]: wasmtime::Config::async_support + /// [`wasmtime_wasi::add_to_linker_sync`]: crate::add_to_linker_sync + /// + /// # Examples + /// + /// ```no_run + /// use wasmtime::{Engine, Result, Store, Config}; + /// use wasmtime::component::{ResourceTable, Linker, Component}; + /// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder}; + /// use wasmtime_wasi::bindings::sync::Command; + /// + /// // This example is an example shim of executing a component based on the + /// // command line arguments provided to this program. + /// fn main() -> Result<()> { + /// let args = std::env::args().skip(1).collect::>(); + /// + /// // Configure and create `Engine` + /// let engine = Engine::default(); + /// + /// // Configure a `Linker` with WASI, compile a component based on + /// // command line arguments, and then pre-instantiate it. + /// let mut linker = Linker::::new(&engine); + /// wasmtime_wasi::add_to_linker_async(&mut linker)?; + /// let component = Component::from_file(&engine, &args[0])?; + /// let pre = linker.instantiate_pre(&component)?; + /// + /// + /// // Configure a `WasiCtx` based on this program's environment. Then + /// // build a `Store` to instantiate into. + /// let mut builder = WasiCtxBuilder::new(); + /// builder.inherit_stdio().inherit_env().args(&args); + /// let mut store = Store::new( + /// &engine, + /// MyState { + /// ctx: builder.build(), + /// table: ResourceTable::new(), + /// }, + /// ); + /// + /// // Instantiate the component and we're off to the races. + /// let (command, _instance) = Command::instantiate_pre(&mut store, &pre)?; + /// let program_result = command.wasi_cli_run().call_run(&mut store)?; + /// match program_result { + /// Ok(()) => Ok(()), + /// Err(()) => std::process::exit(1), + /// } + /// } + /// + /// struct MyState { + /// ctx: WasiCtx, + /// table: ResourceTable, + /// } + /// + /// impl WasiView for MyState { + /// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } + /// fn table(&mut self) -> &mut ResourceTable { &mut self.table } + /// } + /// ``` + pub use self::generated::Command; +} + +mod async_io { + wasmtime::component::bindgen!({ + path: "wit", + world: "wasi:cli/command", + tracing: true, + async: { + // Only these functions are `async` and everything else is sync + // meaning that it basically doesn't need to block. These functions + // are the only ones that need to block. + // + // Note that at this time `only_imports` works on function names + // which in theory can be shared across interfaces, so this may + // need fancier syntax in the future. + only_imports: [ + "[method]descriptor.access-at", + "[method]descriptor.advise", + "[method]descriptor.change-directory-permissions-at", + "[method]descriptor.change-file-permissions-at", + "[method]descriptor.create-directory-at", + "[method]descriptor.get-flags", + "[method]descriptor.get-type", + "[method]descriptor.is-same-object", + "[method]descriptor.link-at", + "[method]descriptor.lock-exclusive", + "[method]descriptor.lock-shared", + "[method]descriptor.metadata-hash", + "[method]descriptor.metadata-hash-at", + "[method]descriptor.open-at", + "[method]descriptor.read", + "[method]descriptor.read-directory", + "[method]descriptor.readlink-at", + "[method]descriptor.remove-directory-at", + "[method]descriptor.rename-at", + "[method]descriptor.set-size", + "[method]descriptor.set-times", + "[method]descriptor.set-times-at", + "[method]descriptor.stat", + "[method]descriptor.stat-at", + "[method]descriptor.symlink-at", + "[method]descriptor.sync", + "[method]descriptor.sync-data", + "[method]descriptor.try-lock-exclusive", + "[method]descriptor.try-lock-shared", + "[method]descriptor.unlink-file-at", + "[method]descriptor.unlock", + "[method]descriptor.write", + "[method]input-stream.read", + "[method]input-stream.blocking-read", + "[method]input-stream.blocking-skip", + "[method]input-stream.skip", + "[method]output-stream.forward", + "[method]output-stream.splice", + "[method]output-stream.blocking-splice", + "[method]output-stream.blocking-flush", + "[method]output-stream.blocking-write", + "[method]output-stream.blocking-write-and-flush", + "[method]output-stream.blocking-write-zeroes-and-flush", + "[method]directory-entry-stream.read-directory-entry", + "poll", + "[method]pollable.block", + "[method]pollable.ready", + ], + }, + trappable_error_type: { + "wasi:io/streams/stream-error" => crate::StreamError, + "wasi:filesystem/types/error-code" => crate::FsError, + "wasi:sockets/network/error-code" => crate::SocketError, + }, + with: { + // Configure all resources to be concrete types defined in this crate, + // so that way we get to use nice typed helper methods with + // `ResourceTable`. + "wasi:sockets/network/network": crate::network::Network, + "wasi:sockets/tcp/tcp-socket": crate::tcp::TcpSocket, + "wasi:sockets/udp/udp-socket": crate::udp::UdpSocket, + "wasi:sockets/udp/incoming-datagram-stream": crate::udp::IncomingDatagramStream, + "wasi:sockets/udp/outgoing-datagram-stream": crate::udp::OutgoingDatagramStream, + "wasi:sockets/ip-name-lookup/resolve-address-stream": crate::ip_name_lookup::ResolveAddressStream, + "wasi:filesystem/types/directory-entry-stream": crate::filesystem::ReaddirIterator, + "wasi:filesystem/types/descriptor": crate::filesystem::Descriptor, + "wasi:io/streams/input-stream": crate::stream::InputStream, + "wasi:io/streams/output-stream": crate::stream::OutputStream, + "wasi:io/error/error": crate::stream::Error, + "wasi:io/poll/pollable": crate::poll::Pollable, + "wasi:cli/terminal-input/terminal-input": crate::stdio::TerminalInput, + "wasi:cli/terminal-output/terminal-output": crate::stdio::TerminalOutput, + }, + }); } -wasmtime::component::bindgen!({ - path: "wit", - world: "wasi:cli/imports", - tracing: true, - async: { - // Only these functions are `async` and everything else is sync - // meaning that it basically doesn't need to block. These functions - // are the only ones that need to block. - // - // Note that at this time `only_imports` works on function names - // which in theory can be shared across interfaces, so this may - // need fancier syntax in the future. - only_imports: [ - "[method]descriptor.access-at", - "[method]descriptor.advise", - "[method]descriptor.change-directory-permissions-at", - "[method]descriptor.change-file-permissions-at", - "[method]descriptor.create-directory-at", - "[method]descriptor.get-flags", - "[method]descriptor.get-type", - "[method]descriptor.is-same-object", - "[method]descriptor.link-at", - "[method]descriptor.lock-exclusive", - "[method]descriptor.lock-shared", - "[method]descriptor.metadata-hash", - "[method]descriptor.metadata-hash-at", - "[method]descriptor.open-at", - "[method]descriptor.read", - "[method]descriptor.read-directory", - "[method]descriptor.readlink-at", - "[method]descriptor.remove-directory-at", - "[method]descriptor.rename-at", - "[method]descriptor.set-size", - "[method]descriptor.set-times", - "[method]descriptor.set-times-at", - "[method]descriptor.stat", - "[method]descriptor.stat-at", - "[method]descriptor.symlink-at", - "[method]descriptor.sync", - "[method]descriptor.sync-data", - "[method]descriptor.try-lock-exclusive", - "[method]descriptor.try-lock-shared", - "[method]descriptor.unlink-file-at", - "[method]descriptor.unlock", - "[method]descriptor.write", - "[method]input-stream.read", - "[method]input-stream.blocking-read", - "[method]input-stream.blocking-skip", - "[method]input-stream.skip", - "[method]output-stream.forward", - "[method]output-stream.splice", - "[method]output-stream.blocking-splice", - "[method]output-stream.blocking-flush", - "[method]output-stream.blocking-write", - "[method]output-stream.blocking-write-and-flush", - "[method]output-stream.blocking-write-zeroes-and-flush", - "[method]directory-entry-stream.read-directory-entry", - "poll", - "[method]pollable.block", - "[method]pollable.ready", - ], - }, - trappable_error_type: { - "wasi:io/streams/stream-error" => crate::StreamError, - "wasi:filesystem/types/error-code" => crate::FsError, - "wasi:sockets/network/error-code" => crate::SocketError, - }, - with: { - // Configure all resources to be concrete types defined in this crate, - // so that way we get to use nice typed helper methods with - // `ResourceTable`. - "wasi:sockets/network/network": super::network::Network, - "wasi:sockets/tcp/tcp-socket": super::tcp::TcpSocket, - "wasi:sockets/udp/udp-socket": super::udp::UdpSocket, - "wasi:sockets/udp/incoming-datagram-stream": super::udp::IncomingDatagramStream, - "wasi:sockets/udp/outgoing-datagram-stream": super::udp::OutgoingDatagramStream, - "wasi:sockets/ip-name-lookup/resolve-address-stream": super::ip_name_lookup::ResolveAddressStream, - "wasi:filesystem/types/directory-entry-stream": super::filesystem::ReaddirIterator, - "wasi:filesystem/types/descriptor": super::filesystem::Descriptor, - "wasi:io/streams/input-stream": super::stream::InputStream, - "wasi:io/streams/output-stream": super::stream::OutputStream, - "wasi:io/error/error": super::stream::Error, - "wasi:io/poll/pollable": super::poll::Pollable, - "wasi:cli/terminal-input/terminal-input": super::stdio::TerminalInput, - "wasi:cli/terminal-output/terminal-output": super::stdio::TerminalOutput, - }, -}); +pub use self::async_io::exports; +pub use self::async_io::wasi::*; -pub use wasi::*; +/// Asynchronous bindings to execute and run a `wasi:cli/command`. +/// +/// This structure is automatically generated by `bindgen!` and is intended to +/// be used with [`Config::async_support(true)`][async]. For the synchronous +/// version see [`binidngs::sync::Command`](sync::Command). +/// +/// This can be used for a more "typed" view of executing a command component +/// through the [`Command::wasi_cli_run`] method plus +/// [`Guest::call_run`](exports::wasi::cli::run::Guest::call_run). +/// +/// > **Note**: it's recommended to use [`wasmtime_wasi::add_to_linker_async`] +/// > instead of the auto-generated [`Command::add_to_linker`] here. +/// +/// [async]: wasmtime::Config::async_support +/// [`wasmtime_wasi::add_to_linker_async`]: crate::add_to_linker_async +/// +/// # Examples +/// +/// ```no_run +/// use wasmtime::{Engine, Result, Store, Config}; +/// use wasmtime::component::{ResourceTable, Linker, Component}; +/// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder}; +/// use wasmtime_wasi::bindings::Command; +/// +/// // This example is an example shim of executing a component based on the +/// // command line arguments provided to this program. +/// #[tokio::main] +/// async fn main() -> Result<()> { +/// let args = std::env::args().skip(1).collect::>(); +/// +/// // Configure and create `Engine` +/// let mut config = Config::new(); +/// config.async_support(true); +/// let engine = Engine::new(&config)?; +/// +/// // Configure a `Linker` with WASI, compile a component based on +/// // command line arguments, and then pre-instantiate it. +/// let mut linker = Linker::::new(&engine); +/// wasmtime_wasi::add_to_linker_async(&mut linker)?; +/// let component = Component::from_file(&engine, &args[0])?; +/// let pre = linker.instantiate_pre(&component)?; +/// +/// +/// // Configure a `WasiCtx` based on this program's environment. Then +/// // build a `Store` to instantiate into. +/// let mut builder = WasiCtxBuilder::new(); +/// builder.inherit_stdio().inherit_env().args(&args); +/// let mut store = Store::new( +/// &engine, +/// MyState { +/// ctx: builder.build(), +/// table: ResourceTable::new(), +/// }, +/// ); +/// +/// // Instantiate the component and we're off to the races. +/// let (command, _instance) = Command::instantiate_pre(&mut store, &pre).await?; +/// let program_result = command.wasi_cli_run().call_run(&mut store).await?; +/// match program_result { +/// Ok(()) => Ok(()), +/// Err(()) => std::process::exit(1), +/// } +/// } +/// +/// struct MyState { +/// ctx: WasiCtx, +/// table: ResourceTable, +/// } +/// +/// impl WasiView for MyState { +/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } +/// } +/// ``` +pub use self::async_io::Command; diff --git a/crates/wasi/src/command.rs b/crates/wasi/src/command.rs index 57ccd7be6328..8b137891791f 100644 --- a/crates/wasi/src/command.rs +++ b/crates/wasi/src/command.rs @@ -1,92 +1 @@ -use crate::WasiView; -wasmtime::component::bindgen!({ - world: "wasi:cli/command", - tracing: true, - async: true, - with: { "wasi": crate::bindings }, -}); - -pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> anyhow::Result<()> { - crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; - crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; - crate::bindings::filesystem::types::add_to_linker(l, |t| t)?; - crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; - crate::bindings::io::error::add_to_linker(l, |t| t)?; - crate::bindings::io::poll::add_to_linker(l, |t| t)?; - crate::bindings::io::streams::add_to_linker(l, |t| t)?; - crate::bindings::random::random::add_to_linker(l, |t| t)?; - crate::bindings::random::insecure::add_to_linker(l, |t| t)?; - crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?; - crate::bindings::cli::exit::add_to_linker(l, |t| t)?; - crate::bindings::cli::environment::add_to_linker(l, |t| t)?; - crate::bindings::cli::stdin::add_to_linker(l, |t| t)?; - crate::bindings::cli::stdout::add_to_linker(l, |t| t)?; - crate::bindings::cli::stderr::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?; - crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?; - crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?; - crate::bindings::sockets::udp::add_to_linker(l, |t| t)?; - crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?; - crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?; - crate::bindings::sockets::network::add_to_linker(l, |t| t)?; - crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?; - Ok(()) -} - -pub mod sync { - use crate::WasiView; - - wasmtime::component::bindgen!({ - world: "wasi:cli/command", - tracing: true, - async: false, - with: { - // Map interfaces with synchronous funtions to their synchronous - // counterparts... - "wasi:filesystem": crate::bindings::sync_io::filesystem, - "wasi:io": crate::bindings::sync_io::io, - - // ... and everything else is not-async and so goes through the - // top-level bindings. - "wasi": crate::bindings - }, - }); - - pub fn add_to_linker( - l: &mut wasmtime::component::Linker, - ) -> anyhow::Result<()> { - crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; - crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; - crate::bindings::sync_io::filesystem::types::add_to_linker(l, |t| t)?; - crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; - crate::bindings::io::error::add_to_linker(l, |t| t)?; - crate::bindings::sync_io::io::poll::add_to_linker(l, |t| t)?; - crate::bindings::sync_io::io::streams::add_to_linker(l, |t| t)?; - crate::bindings::random::random::add_to_linker(l, |t| t)?; - crate::bindings::random::insecure::add_to_linker(l, |t| t)?; - crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?; - crate::bindings::cli::exit::add_to_linker(l, |t| t)?; - crate::bindings::cli::environment::add_to_linker(l, |t| t)?; - crate::bindings::cli::stdin::add_to_linker(l, |t| t)?; - crate::bindings::cli::stdout::add_to_linker(l, |t| t)?; - crate::bindings::cli::stderr::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?; - crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?; - crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?; - crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?; - crate::bindings::sockets::udp::add_to_linker(l, |t| t)?; - crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?; - crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?; - crate::bindings::sockets::network::add_to_linker(l, |t| t)?; - crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?; - Ok(()) - } -} diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index 98233fa458e2..3604b685b3f9 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -11,11 +11,34 @@ use crate::{ stdio::{StdinStream, StdoutStream}, DirPerms, FilePerms, }; +use anyhow::Result; use cap_rand::{Rng, RngCore, SeedableRng}; +use cap_std::ambient_authority; +use std::path::Path; use std::sync::Arc; use std::{mem, net::SocketAddr}; use wasmtime::component::ResourceTable; +/// Builder-style structure used to create a [`WasiCtx`]. +/// +/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`] +/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the +/// building process and produce a finalized [`WasiCtx`]. +/// +/// # Examples +/// +/// ``` +/// use wasmtime_wasi::{WasiCtxBuilder, WasiCtx}; +/// +/// let mut wasi = WasiCtxBuilder::new(); +/// wasi.arg("./foo.wasm"); +/// wasi.arg("--help"); +/// wasi.env("FOO", "bar"); +/// +/// let wasi: WasiCtx = wasi.build(); +/// ``` +/// +/// [`Store`]: wasmtime::Store pub struct WasiCtxBuilder { stdin: Box, stdout: Box, @@ -40,20 +63,18 @@ impl WasiCtxBuilder { /// The current defaults are: /// /// * stdin is closed - /// * stdout and stderr eat all input but it doesn't go anywhere + /// * stdout and stderr eat all input and it doesn't go anywhere /// * no env vars /// * no arguments /// * no preopens /// * clocks use the host implementation of wall/monotonic clocks /// * RNGs are all initialized with random state and suitable generator /// quality to satisfy the requirements of WASI APIs. + /// * TCP/UDP are allowed but all addresses are denied by default. + /// * `wasi:network/ip-name-lookup` is denied by default. /// /// These defaults can all be updated via the various builder configuration /// methods below. - /// - /// Note that each builder can only be used once to produce a [`WasiCtx`]. - /// Invoking the [`build`](WasiCtxBuilder::build) method will panic on the - /// second attempt. pub fn new() -> Self { // For the insecure random API, use `SmallRng`, which is fast. It's // also insecure, but that's the deal here. @@ -86,33 +107,71 @@ impl WasiCtxBuilder { } } + /// Provides a custom implementation of stdin to use. + /// + /// By default stdin is closed but an example of using the host's native + /// stdin loos like: + /// + /// ``` + /// use wasmtime_wasi::{stdin, WasiCtxBuilder}; + /// + /// let mut wasi = WasiCtxBuilder::new(); + /// wasi.stdin(stdin()); + /// ``` + /// + /// Note that inheriting the process's stdin can also be done through + /// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin). pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self { self.stdin = Box::new(stdin); self } + /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout. pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self { self.stdout = Box::new(stdout); self } + /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr. pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self { self.stderr = Box::new(stderr); self } + /// Configures this context's stdin stream to read the host process's + /// stdin. + /// + /// Note that concurrent reads of stdin can produce surprising results so + /// when using this it's typically best to have a single wasm instance in + /// the process using this. pub fn inherit_stdin(&mut self) -> &mut Self { self.stdin(stdio::stdin()) } + /// Configures this context's stdout stream to write to the host process's + /// stdout. + /// + /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) + /// multiple instances printing to stdout works well. pub fn inherit_stdout(&mut self) -> &mut Self { self.stdout(stdio::stdout()) } + /// Configures this context's stderr stream to write to the host process's + /// stderr. + /// + /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) + /// multiple instances printing to stderr works well. pub fn inherit_stderr(&mut self) -> &mut Self { self.stderr(stdio::stderr()) } + /// Configures all of stdin, stdout, and stderr to be inherited from the + /// host process. + /// + /// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale + /// on why this should only be done in situations of + /// one-instance-per-process. pub fn inherit_stdio(&mut self) -> &mut Self { self.inherit_stdin().inherit_stdout().inherit_stderr() } @@ -148,6 +207,25 @@ impl WasiCtxBuilder { self } + /// Appends multiple environment variables at once for this builder. + /// + /// All environment variables are appended to the list of environment + /// variables that this builder will configure. + /// + /// At this time environment variables are not deduplicated and if the same + /// key is set twice then the guest will see two entries for the same key. + /// + /// # Examples + /// + /// ``` + /// use wasmtime_wasi::{stdin, WasiCtxBuilder}; + /// + /// let mut wasi = WasiCtxBuilder::new(); + /// wasi.envs(&[ + /// ("FOO", "bar"), + /// ("HOME", "/somewhere"), + /// ]); + /// ``` pub fn envs(&mut self, env: &[(impl AsRef, impl AsRef)]) -> &mut Self { self.env.extend( env.iter() @@ -156,37 +234,105 @@ impl WasiCtxBuilder { self } + /// Appends a single environment variable for this builder. + /// + /// At this time environment variables are not deduplicated and if the same + /// key is set twice then the guest will see two entries for the same key. + /// + /// # Examples + /// + /// ``` + /// use wasmtime_wasi::{stdin, WasiCtxBuilder}; + /// + /// let mut wasi = WasiCtxBuilder::new(); + /// wasi.env("FOO", "bar"); + /// ``` pub fn env(&mut self, k: impl AsRef, v: impl AsRef) -> &mut Self { self.env .push((k.as_ref().to_owned(), v.as_ref().to_owned())); self } + /// Configures all environment variables to be inherited from the calling + /// process into this configuration. + /// + /// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined + /// environment variables. pub fn inherit_env(&mut self) -> &mut Self { self.envs(&std::env::vars().collect::>()) } + /// Appends a list of arguments to the argument array to pass to wasm. pub fn args(&mut self, args: &[impl AsRef]) -> &mut Self { self.args.extend(args.iter().map(|a| a.as_ref().to_owned())); self } + /// Appends a single argument to get passed to wasm. pub fn arg(&mut self, arg: impl AsRef) -> &mut Self { self.args.push(arg.as_ref().to_owned()); self } + /// Appends all host process arguments to the list of arguments to get + /// passed to wasm. pub fn inherit_args(&mut self) -> &mut Self { self.args(&std::env::args().collect::>()) } + /// Configures a "preopened directory" to be available to WebAssembly. + /// + /// By default WebAssembly does not have access to the filesystem because + /// the are no preopened directories. All filesystem operations, such as + /// opening a file, are done through a preexisting handle. This means that + /// to provide WebAssembly access to a directory it must be configured + /// through this API. + /// + /// WASI will also prevent access outside of files provided here. For + /// example `..` can't be used to traverse up from the `dir` provided here + /// to the containing directory. + /// + /// * `host_path` - a path to a directory on the host to open and make + /// accessible to WebAssembly. Note that the name of this directory in the + /// guest is configured with `guest_path` below. + /// * `perms` - this is the permissions that wasm will have to operate on + /// `dir`. This can be used, for example, to provide readonly access to a + /// directory. + /// * `file_perms` - similar to `perms` but corresponds to the maximum set + /// of permissions that can be used for any file in this directory. + /// * `guest_path` - the name of the preopened directory from WebAssembly's + /// perspective. Note that this does not need to match the host's name for + /// the directory. + /// + /// # Errors + /// + /// This method will return an error if `host_path` cannot be opened. + /// + /// # Examples + /// + /// ``` + /// use wasmtime_wasi::{WasiCtxBuilder, DirPerms, FilePerms}; + /// + /// # fn main() {} + /// # fn foo() -> wasmtime::Result<()> { + /// let mut wasi = WasiCtxBuilder::new(); + /// + /// // Make `./host-directory` available in the guest as `.` + /// wasi.preopened_dir("./host-directory", DirPerms::all(), FilePerms::all(), "."); + /// + /// // Make `./readonly` available in the guest as `./ro` + /// wasi.preopened_dir("./readonly", DirPerms::READ, FilePerms::READ, "./ro"); + /// # Ok(()) + /// # } + /// ``` pub fn preopened_dir( &mut self, - dir: cap_std::fs::Dir, + host_path: impl AsRef, perms: DirPerms, file_perms: FilePerms, - path: impl AsRef, - ) -> &mut Self { + guest_path: impl AsRef, + ) -> Result<&mut Self> { + let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; let mut open_mode = OpenMode::empty(); if perms.contains(DirPerms::READ) { open_mode |= OpenMode::READ; @@ -202,13 +348,13 @@ impl WasiCtxBuilder { open_mode, self.allow_blocking_current_thread, ), - path.as_ref().to_owned(), + guest_path.as_ref().to_owned(), )); - self + Ok(self) } - /// Set the generator for the secure random number generator to the custom - /// generator specified. + /// Set the generator for the `wasi:random/random` number generator to the + /// custom generator specified. /// /// Note that contexts have a default RNG configured which is a suitable /// generator for WASI and is configured with a random seed per-context. @@ -222,27 +368,47 @@ impl WasiCtxBuilder { self } + /// Configures the generator for `wasi:random/insecure`. + /// + /// The `insecure_random` generator provided will be used for all randomness + /// requested by the `wasi:random/insecure` interface. pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self { self.insecure_random = Box::new(insecure_random); self } + /// Configures the seed to be returned from `wasi:random/insecure-seed` to + /// the specified custom value. + /// + /// By default this number is randomly generated when a builder is created. pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self { self.insecure_random_seed = insecure_random_seed; self } + /// Configures `wasi:clocks/wall-clock` to use the `clock` specified. + /// + /// By default the host's wall clock is used. pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self { self.wall_clock = Box::new(clock); self } + /// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified. + /// + /// By default the host's monotonic clock is used. pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self { self.monotonic_clock = Box::new(clock); self } - /// Allow all network addresses accessible to the host + /// Allow all network addresses accessible to the host. + /// + /// This method will inherit all network addresses meaning that any address + /// can be bound by the guest or connected to by the guest using any + /// protocol. + /// + /// See also [`WasiCtxBuilder::socket_addr_check`]. pub fn inherit_network(&mut self) -> &mut Self { self.socket_addr_check(|_, _| true) } @@ -260,31 +426,41 @@ impl WasiCtxBuilder { } /// Allow usage of `wasi:sockets/ip-name-lookup` + /// + /// By default this is disabled. pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self { self.allowed_network_uses.ip_name_lookup = enable; self } - /// Allow usage of UDP + /// Allow usage of UDP. + /// + /// This is enabled by default, but can be disabled if UDP should be blanket + /// disabled. pub fn allow_udp(&mut self, enable: bool) -> &mut Self { self.allowed_network_uses.udp = enable; self } /// Allow usage of TCP + /// + /// This is enabled by default, but can be disabled if TCP should be blanket + /// disabled. pub fn allow_tcp(&mut self, enable: bool) -> &mut Self { self.allowed_network_uses.tcp = enable; self } - /// Uses the configured context so far to construct the final `WasiCtx`. + /// Uses the configured context so far to construct the final [`WasiCtx`]. /// /// Note that each `WasiCtxBuilder` can only be used to "build" once, and /// calling this method twice will panic. /// /// # Panics /// - /// Panics if this method is called twice. + /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be + /// used to create only a single [`WasiCtx`]. Repeated usage of this method + /// is not allowed and should use a second builder instead. pub fn build(&mut self) -> WasiCtx { assert!(!self.built); @@ -332,11 +508,78 @@ impl WasiCtxBuilder { } } +/// A trait which provides access to internal WASI state. +/// +/// This trait is the basis of implementation of all traits in this crate. All +/// traits are implemented like: +/// +/// ``` +/// # trait WasiView {} +/// # mod bindings { pub mod wasi { pub trait Host {} } } +/// impl bindings::wasi::Host for T { +/// // ... +/// } +/// ``` +/// +/// For a [`Store`](wasmtime::Store) this trait will be implemented +/// for the `T`. This also corresponds to the `T` in +/// [`Linker`](wasmtime::component::Linker). +/// +/// # Example +/// +/// ``` +/// use wasmtime_wasi::{WasiCtx, ResourceTable, WasiView, WasiCtxBuilder}; +/// +/// struct MyState { +/// ctx: WasiCtx, +/// table: ResourceTable, +/// } +/// +/// impl WasiView for MyState { +/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } +/// } +/// +/// impl MyState { +/// fn new() -> MyState { +/// let mut wasi = WasiCtxBuilder::new(); +/// wasi.arg("./foo.wasm"); +/// wasi.arg("--help"); +/// wasi.env("FOO", "bar"); +/// +/// MyState { +/// ctx: wasi.build(), +/// table: ResourceTable::new(), +/// } +/// } +/// } +/// ``` pub trait WasiView: Send { + /// Yields mutable access to the internal resource management that this + /// context contains. + /// + /// Embedders can add custom resources to this table as well to give + /// resources to wasm as well. fn table(&mut self) -> &mut ResourceTable; + + /// Yields mutable access to the configuration used for this context. + /// + /// The returned type is created through [`WasiCtxBuilder`]. fn ctx(&mut self) -> &mut WasiCtx; } +/// Per-[`Store`] state which holds state necessary to implement WASI from this +/// crate. +/// +/// This structure is created through [`WasiCtxBuilder`] and is stored within +/// the `T` of [`Store`][`Store`]. Access to the structure is provided +/// through the [`WasiView`] trait as an implementation on `T`. +/// +/// Note that this structure itself does not have any accessors, it's here for +/// internal use within the `wasmtime-wasi` crate's implementation of +/// bindgen-generated traits. +/// +/// [`Store`]: wasmtime::Store pub struct WasiCtx { pub(crate) random: Box, pub(crate) insecure_random: Box, diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index 3dffa4603529..652cd5174f15 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -145,9 +145,18 @@ enum SpawnBlocking { } bitflags::bitflags! { + /// Permission bits for operating on a directory. + /// + /// Directories can be limited to being readonly meaning that new files + /// cannot be created for example. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct DirPerms: usize { + /// This directory can be read, for example its entries can be iterated + /// over and files can be opened. const READ = 0b1; + + /// This directory can be mutated, for example by creating new files + /// within it. const MUTATE = 0b10; } } diff --git a/crates/wasi/src/host/filesystem/sync.rs b/crates/wasi/src/host/filesystem/sync.rs index 6d12e50dddcd..38642619ed9f 100644 --- a/crates/wasi/src/host/filesystem/sync.rs +++ b/crates/wasi/src/host/filesystem/sync.rs @@ -1,6 +1,6 @@ use crate::bindings::filesystem::types as async_filesystem; -use crate::bindings::sync_io::filesystem::types as sync_filesystem; -use crate::bindings::sync_io::io::streams; +use crate::bindings::sync::filesystem::types as sync_filesystem; +use crate::bindings::sync::io::streams; use crate::runtime::in_tokio; use crate::{FsError, FsResult}; use wasmtime::component::Resource; diff --git a/crates/wasi/src/host/io.rs b/crates/wasi/src/host/io.rs index 168088d9bb88..3a6682de5f81 100644 --- a/crates/wasi/src/host/io.rs +++ b/crates/wasi/src/host/io.rs @@ -230,8 +230,8 @@ pub mod sync { self as async_streams, Host as AsyncHost, HostInputStream as AsyncHostInputStream, HostOutputStream as AsyncHostOutputStream, }, - bindings::sync_io::io::poll::Pollable, - bindings::sync_io::io::streams::{self, InputStream, OutputStream}, + bindings::sync::io::poll::Pollable, + bindings::sync::io::streams::{self, InputStream, OutputStream}, runtime::in_tokio, StreamError, StreamResult, WasiView, }; diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index a7ba9f0deb22..85a989975f56 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -1,10 +1,184 @@ //! # Wasmtime's WASI Implementation //! -//! This crate provides a Wasmtime host implementation of WASI 0.2 (aka -//! Preview 2), and a compatibility shim that provides an implementation of -//! WASI 0.1 (aka Preview 1). +//! This crate provides a Wasmtime host implementation of WASI 0.2 (aka WASIp2 +//! aka Preview 2) and WASI 0.1 (aka WASIp1 aka Preview 1). WASI is implemented +//! with the Rust crates [`tokio`] and [`cap-std`] primarily, meaning that +//! operations are implemented in terms of their native platform equivalents by +//! default. //! +//! For components and WASIp2, continue reading below. For WASIp1 and core +//! modules, see the [`preview1`] module documentation. //! +//! # WASIp2 interfaces +//! +//! This crate contains implementations of the following interfaces: +//! +//! * [`wasi:cli/environment`] +//! * [`wasi:cli/exit`] +//! * [`wasi:cli/stderr`] +//! * [`wasi:cli/stdin`] +//! * [`wasi:cli/stdout`] +//! * [`wasi:cli/terminal-input`] +//! * [`wasi:cli/terminal-output`] +//! * [`wasi:cli/terminal-stderr`] +//! * [`wasi:cli/terminal-stdin`] +//! * [`wasi:cli/terminal-stdout`] +//! * [`wasi:clocks/monotonic-clock`] +//! * [`wasi:clocks/wall-clock`] +//! * [`wasi:filesystem/preopens`] +//! * [`wasi:filesystem/types`] +//! * [`wasi:io/error`] +//! * [`wasi:io/poll`] +//! * [`wasi:io/streams`] +//! * [`wasi:random/insecure-seed`] +//! * [`wasi:random/insecure`] +//! * [`wasi:random/random`] +//! * [`wasi:sockets/instance-network`] +//! * [`wasi:sockets/ip-name-lookup`] +//! * [`wasi:sockets/network`] +//! * [`wasi:sockets/tcp-create-socket`] +//! * [`wasi:sockets/tcp`] +//! * [`wasi:sockets/udp-create-socket`] +//! * [`wasi:sockets/udp`] +//! +//! All traits are implemented in terms of a [`WasiView`] trait which provides +//! basic access to [`WasiCtx`], configuration for WASI, and [`ResourceTable`], +//! the state for all host-defined component model resources. +//! +//! # Generated Bindings +//! +//! This crate uses [`wasmtime::component::bindgen!`] to generate bindings for +//! all WASI interfaces. Raw bindings are available in the [`bindings`] module +//! of this crate. Downstream users can either implement these traits themselves +//! or you can use the built-in implementations in this crate for all +//! `T: WasiVew` +//! +//! # The `WasiView` trait +//! +//! This crate's implementation of WASI is done in terms of an implementation of +//! [`WasiView`]. This trait provides a "view" into WASI-related state that is +//! contained within a [`Store`](wasmtime::Store). All implementations of +//! traits look like: +//! +//! ``` +//! # trait WasiView {} +//! # mod bindings { pub mod wasi { pub trait Host {} } } +//! impl bindings::wasi::Host for T { +//! // ... +//! } +//! ``` +//! +//! The [`add_to_linker_sync`] and [`add_to_linker_async`] function then require +//! that `T: WasiView` with [`Linker`](wasmtime::component::Linker). +//! +//! To implement the [`WasiView`] trait you will first select a `T` to put in +//! `Store`. Next you'll implement the [`WasiView`] trait for `T`. Somewhere +//! within `T` you'll store: +//! +//! * [`WasiCtx`] - created through [`WasiCtxBuilder`]. +//! * [`ResourceTable`] - created through default constructors. +//! +//! These two fields are then accessed through the methods of [`WasiView`]. +//! +//! # Async and Sync +//! +//! Many WASI functions are not blocking from WebAssembly's point of view, but +//! for those that do they're provided in two flavors: asynchronous and +//! synchronous. Which version you will use depends on how +//! [`Config::async_support`][async] is set. +//! +//! * For non-async users (the default of `Config`), use [`add_to_linker_sync`]. +//! * For async users, use [`add_to_linker_async`]. +//! +//! Note that bindings are generated once for async and once for sync. Most +//! interfaces do not change, however, so only interfaces with blocking +//! functions have bindings generated twice. Bindings are organized as: +//! +//! * [`bindings`] - default location of all bindings, blocking functions are +//! `async` +//! * [`bindings::sync`] - blocking interfaces have synchronous versions here. +//! +//! # Crate-specific traits +//! +//! This crate's default implementation of WASI bindings to native primitives +//! for the platform that it is compiled for. For example opening a TCP socket +//! uses the native platform to open a TCP socket (so long as [`WasiCtxBuilder`] +//! allows it). There are a few important traits, however, that are specific to +//! this crate. +//! +//! * [`HostInputStream`] and [`HostOutputStream`] - these are the host traits +//! behind the WASI `input-stream` and `output-stream` types in the +//! `wasi:io/streams` interface. These enable embedders to build their own +//! custom stream and insert them into a [`ResourceTable`] to be used from +//! wasm. +//! +//! * [`Subscribe`] - this trait enables building arbitrary logic to get hooked +//! into a `pollable` resource from `wasi:io/poll`. A pollable resource is +//! created through the [`subscribe`] function. +//! +//! * [`HostWallClock`] and [`HostMonotonicClock`] are used in conjunction with +//! [`WasiCtxBuilder::wall_clock`] and [`WasiCtxBuilder::monotonic_clock`] if +//! the defaults host's clock should not be used. +//! +//! * [`StdinStream`] and [`StdoutStream`] are used to provide custom +//! stdin/stdout streams if they're not inherited (or null, which is the +//! default). +//! +//! These traits enable embedders to customize small portions of WASI interfaces +//! provided while still providing all other interfaces. +//! +//! # Examples +//! +//! Usage of this crate is done through a few steps to get everything hooked up: +//! +//! 1. First implement [`WasiView`] for your type which is the `T` in +//! `Store`. +//! 2. Add WASI interfaces to a `wasmtime::component::Linker`. This is either +//! done through top-level functions like [`add_to_linker_sync`] or through +//! individual `add_to_linker` functions in generated bindings throughout +//! this crate. +//! 3. Create a [`WasiCtx`] for each `Store` through [`WasiCtxBuilder`]. Each +//! WASI context is "null" or "empty" by default, so items must be explicitly +//! added to get accessed by wasm (such as env vars or program arguments). +//! 4. Use the previous `Linker` to instantiate a `Component` within a +//! `Store`. +//! +//! For examples see each of [`WasiView`], [`WasiCtx`], [`WasiCtxBuilder`], +//! [`add_to_linker_sync`], and [`bindings::Command`]. +//! +//! [`wasmtime::component::bindgen!`]: https://docs.rs/wasmtime/latest/wasmtime/component/macro.bindgen.html +//! [`tokio`]: https://crates.io/crates/tokio +//! [`cap-std`]: https://crates.io/crates/cap-std +//! [`wasi:cli/environment`]: bindings::cli::environment::Host +//! [`wasi:cli/exit`]: bindings::cli::exit::Host +//! [`wasi:cli/stderr`]: bindings::cli::stderr::Host +//! [`wasi:cli/stdin`]: bindings::cli::stdin::Host +//! [`wasi:cli/stdout`]: bindings::cli::stdout::Host +//! [`wasi:cli/terminal-input`]: bindings::cli::terminal_input::Host +//! [`wasi:cli/terminal-output`]: bindings::cli::terminal_output::Host +//! [`wasi:cli/terminal-stdin`]: bindings::cli::terminal_stdin::Host +//! [`wasi:cli/terminal-stdout`]: bindings::cli::terminal_stdout::Host +//! [`wasi:cli/terminal-stderr`]: bindings::cli::terminal_stderr::Host +//! [`wasi:clocks/monotonic-clock`]: bindings::clocks::monotonic_clock::Host +//! [`wasi:clocks/wall-clock`]: bindings::clocks::wall_clock::Host +//! [`wasi:filesystem/preopens`]: bindings::filesystem::preopens::Host +//! [`wasi:filesystem/types`]: bindings::filesystem::types::Host +//! [`wasi:io/error`]: bindings::io::error::Host +//! [`wasi:io/poll`]: bindings::io::poll::Host +//! [`wasi:io/streams`]: bindings::io::streams::Host +//! [`wasi:random/insecure-seed`]: bindings::random::insecure_seed::Host +//! [`wasi:random/insecure`]: bindings::random::insecure::Host +//! [`wasi:random/random`]: bindings::random::random::Host +//! [`wasi:sockets/instance-network`]: bindings::sockets::instance_network::Host +//! [`wasi:sockets/ip-name-lookup`]: bindings::sockets::ip_name_lookup::Host +//! [`wasi:sockets/network`]: bindings::sockets::network::Host +//! [`wasi:sockets/tcp-create-socket`]: bindings::sockets::tcp_create_socket::Host +//! [`wasi:sockets/tcp`]: bindings::sockets::tcp::Host +//! [`wasi:sockets/udp-create-socket`]: bindings::sockets::udp_create_socket::Host +//! [`wasi:sockets/udp`]: bindings::sockets::udp::Host +//! [async]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support + +use wasmtime::component::Linker; pub mod bindings; mod clocks; @@ -47,6 +221,187 @@ pub use self::stdio::{ pub use self::stream::{ HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, StreamResult, }; +#[doc(no_inline)] +pub use async_trait::async_trait; +#[doc(no_inline)] pub use cap_fs_ext::SystemTimeSpec; +#[doc(no_inline)] pub use cap_rand::RngCore; +#[doc(no_inline)] pub use wasmtime::component::{ResourceTable, ResourceTableError}; + +/// Add all WASI interfaces from this crate into the `linker` provided. +/// +/// This function will add the `async` variant of all interfaces into the +/// [`Linker`] provided. By `async` this means that this function is only +/// compatible with [`Config::async_support(true)`][async]. For embeddings with +/// async support disabled see [`add_to_linker_sync`] instead. +/// +/// This function will add all interfaces implemented by this crate to the +/// [`Linker`], which corresponds to the `wasi:cli/imports` world supported by +/// this crate. +/// +/// [async]: wasmtime::Config::async_support +/// +/// # Example +/// +/// ``` +/// use wasmtime::{Engine, Result, Store, Config}; +/// use wasmtime::component::{ResourceTable, Linker}; +/// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder}; +/// +/// fn main() -> Result<()> { +/// let mut config = Config::new(); +/// config.async_support(true); +/// let engine = Engine::new(&config)?; +/// +/// let mut linker = Linker::::new(&engine); +/// wasmtime_wasi::add_to_linker_async(&mut linker)?; +/// // ... add any further functionality to `linker` if desired ... +/// +/// let mut builder = WasiCtxBuilder::new(); +/// +/// // ... configure `builder` more to add env vars, args, etc ... +/// +/// let mut store = Store::new( +/// &engine, +/// MyState { +/// ctx: builder.build(), +/// table: ResourceTable::new(), +/// }, +/// ); +/// +/// // ... use `linker` to instantiate within `store` ... +/// +/// Ok(()) +/// } +/// +/// struct MyState { +/// ctx: WasiCtx, +/// table: ResourceTable, +/// } +/// +/// impl WasiView for MyState { +/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } +/// } +/// ``` +pub fn add_to_linker_async(linker: &mut Linker) -> anyhow::Result<()> { + let l = linker; + crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; + crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; + crate::bindings::filesystem::types::add_to_linker(l, |t| t)?; + crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; + crate::bindings::io::error::add_to_linker(l, |t| t)?; + crate::bindings::io::poll::add_to_linker(l, |t| t)?; + crate::bindings::io::streams::add_to_linker(l, |t| t)?; + crate::bindings::random::random::add_to_linker(l, |t| t)?; + crate::bindings::random::insecure::add_to_linker(l, |t| t)?; + crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?; + crate::bindings::cli::exit::add_to_linker(l, |t| t)?; + crate::bindings::cli::environment::add_to_linker(l, |t| t)?; + crate::bindings::cli::stdin::add_to_linker(l, |t| t)?; + crate::bindings::cli::stdout::add_to_linker(l, |t| t)?; + crate::bindings::cli::stderr::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?; + crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?; + crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?; + crate::bindings::sockets::udp::add_to_linker(l, |t| t)?; + crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?; + crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?; + crate::bindings::sockets::network::add_to_linker(l, |t| t)?; + crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?; + Ok(()) +} + +/// Add all WASI interfaces from this crate into the `linker` provided. +/// +/// This function will add the synchronous variant of all interfaces into the +/// [`Linker`] provided. By synchronous this means that this function is only +/// compatible with [`Config::async_support(false)`][async]. For embeddings +/// with async support enabled see [`add_to_linker_async`] instead. +/// +/// This function will add all interfaces implemented by this crate to the +/// [`Linker`], which corresponds to the `wasi:cli/imports` world supported by +/// this crate. +/// +/// [async]: wasmtime::Config::async_support +/// +/// # Example +/// +/// ``` +/// use wasmtime::{Engine, Result, Store, Config}; +/// use wasmtime::component::{ResourceTable, Linker}; +/// use wasmtime_wasi::{WasiCtx, WasiView, WasiCtxBuilder}; +/// +/// fn main() -> Result<()> { +/// let engine = Engine::default(); +/// +/// let mut linker = Linker::::new(&engine); +/// wasmtime_wasi::add_to_linker_sync(&mut linker)?; +/// // ... add any further functionality to `linker` if desired ... +/// +/// let mut builder = WasiCtxBuilder::new(); +/// +/// // ... configure `builder` more to add env vars, args, etc ... +/// +/// let mut store = Store::new( +/// &engine, +/// MyState { +/// ctx: builder.build(), +/// table: ResourceTable::new(), +/// }, +/// ); +/// +/// // ... use `linker` to instantiate within `store` ... +/// +/// Ok(()) +/// } +/// +/// struct MyState { +/// ctx: WasiCtx, +/// table: ResourceTable, +/// } +/// +/// impl WasiView for MyState { +/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } +/// } +/// ``` +pub fn add_to_linker_sync( + linker: &mut wasmtime::component::Linker, +) -> anyhow::Result<()> { + let l = linker; + crate::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; + crate::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; + crate::bindings::sync::filesystem::types::add_to_linker(l, |t| t)?; + crate::bindings::filesystem::preopens::add_to_linker(l, |t| t)?; + crate::bindings::io::error::add_to_linker(l, |t| t)?; + crate::bindings::sync::io::poll::add_to_linker(l, |t| t)?; + crate::bindings::sync::io::streams::add_to_linker(l, |t| t)?; + crate::bindings::random::random::add_to_linker(l, |t| t)?; + crate::bindings::random::insecure::add_to_linker(l, |t| t)?; + crate::bindings::random::insecure_seed::add_to_linker(l, |t| t)?; + crate::bindings::cli::exit::add_to_linker(l, |t| t)?; + crate::bindings::cli::environment::add_to_linker(l, |t| t)?; + crate::bindings::cli::stdin::add_to_linker(l, |t| t)?; + crate::bindings::cli::stdout::add_to_linker(l, |t| t)?; + crate::bindings::cli::stderr::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_input::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_output::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_stdin::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_stdout::add_to_linker(l, |t| t)?; + crate::bindings::cli::terminal_stderr::add_to_linker(l, |t| t)?; + crate::bindings::sockets::tcp::add_to_linker(l, |t| t)?; + crate::bindings::sockets::tcp_create_socket::add_to_linker(l, |t| t)?; + crate::bindings::sockets::udp::add_to_linker(l, |t| t)?; + crate::bindings::sockets::udp_create_socket::add_to_linker(l, |t| t)?; + crate::bindings::sockets::instance_network::add_to_linker(l, |t| t)?; + crate::bindings::sockets::network::add_to_linker(l, |t| t)?; + crate::bindings::sockets::ip_name_lookup::add_to_linker(l, |t| t)?; + Ok(()) +} diff --git a/crates/wasi/src/network.rs b/crates/wasi/src/network.rs index 43870182f866..2749af23943f 100644 --- a/crates/wasi/src/network.rs +++ b/crates/wasi/src/network.rs @@ -1,5 +1,4 @@ -use crate::bindings::sockets::network::{Ipv4Address, Ipv6Address}; -use crate::bindings::wasi::sockets::network::ErrorCode; +use crate::bindings::sockets::network::{ErrorCode, Ipv4Address, Ipv6Address}; use crate::TrappableError; use std::net::SocketAddr; use std::sync::Arc; diff --git a/crates/wasi/src/poll.rs b/crates/wasi/src/poll.rs index 6d89e7fee08c..bfa14b25fdcc 100644 --- a/crates/wasi/src/poll.rs +++ b/crates/wasi/src/poll.rs @@ -23,8 +23,61 @@ pub struct Pollable { remove_index_on_delete: Option Result<()>>, } +/// A trait used internally within a [`Pollable`] to create a `pollable` +/// resource in `wasi:io/poll`. +/// +/// This trait is the internal implementation detail of any pollable resource in +/// this crate's implementation of WASI. The `ready` function is an `async fn` +/// which resolves when the implementation is ready. Using native `async` Rust +/// enables this type's readiness to compose with other types' readiness +/// throughout the WASI implementation. +/// +/// This trait is used in conjunction with [`subscribe`] to create a `pollable` +/// resource. +/// +/// # Example +/// +/// This is a simple example of creating a `Pollable` resource from a few +/// parameters. +/// +/// ``` +/// use tokio::time::{self, Duration, Instant}; +/// use wasmtime_wasi::{WasiView, Subscribe, subscribe, Pollable, async_trait}; +/// use wasmtime::component::Resource; +/// use wasmtime::Result; +/// +/// fn sleep(cx: &mut dyn WasiView, dur: Duration) -> Result> { +/// let end = Instant::now() + dur; +/// let sleep = MySleep { end }; +/// let sleep_resource = cx.table().push(sleep)?; +/// subscribe(cx.table(), sleep_resource) +/// } +/// +/// struct MySleep { +/// end: Instant, +/// } +/// +/// #[async_trait] +/// impl Subscribe for MySleep { +/// async fn ready(&mut self) { +/// tokio::time::sleep_until(self.end).await; +/// } +/// } +/// ``` #[async_trait::async_trait] pub trait Subscribe: Send + 'static { + /// An asynchronous function which resolves when this object's readiness + /// operation is ready. + /// + /// This function is invoked as part of `poll` in `wasi:io/poll`. The + /// meaning of when this function Returns depends on what object this + /// [`Subscribe`] is attached to. When the returned future resolves then the + /// corresponding call to `wasi:io/poll` will return. + /// + /// Note that this method does not return an error. Returning an error + /// should be done through accessors on the object that this `pollable` is + /// connected to. The call to `wasi:io/poll` itself does not return errors, + /// only a list of ready objects. async fn ready(&mut self); } @@ -153,7 +206,7 @@ impl crate::bindings::io::poll::HostPollable for T { pub mod sync { use crate::{ bindings::io::poll as async_poll, - bindings::sync_io::io::poll::{self, Pollable}, + bindings::sync::io::poll::{self, Pollable}, runtime::in_tokio, WasiView, }; @@ -166,7 +219,7 @@ pub mod sync { } } - impl crate::bindings::sync_io::io::poll::HostPollable for T { + impl crate::bindings::sync::io::poll::HostPollable for T { fn ready(&mut self, pollable: Resource) -> Result { in_tokio(async { async_poll::HostPollable::ready(self, pollable).await }) } diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs index 0147a8103ed0..e0e009007796 100644 --- a/crates/wasi/src/preview1.rs +++ b/crates/wasi/src/preview1.rs @@ -1,3 +1,5 @@ +//! + use crate::bindings::{ self, cli::{ diff --git a/crates/wasi/src/stdio.rs b/crates/wasi/src/stdio.rs index 04bab3bdc471..c2f9778d8a64 100644 --- a/crates/wasi/src/stdio.rs +++ b/crates/wasi/src/stdio.rs @@ -163,6 +163,10 @@ impl StdoutStream for pipe::ClosedOutputStream { /// streams are required. pub struct Stdout; +/// Returns a stream that represents the host's standard out. +/// +/// Suitable for passing to +/// [`WasiCtxBuilder::stdout`](crate::WasiCtxBuilder::stdout). pub fn stdout() -> Stdout { Stdout } @@ -183,6 +187,10 @@ impl StdoutStream for Stdout { /// streams are required. pub struct Stderr; +/// Returns a stream that represents the host's standard err. +/// +/// Suitable for passing to +/// [`WasiCtxBuilder::stderr`](crate::WasiCtxBuilder::stderr). pub fn stderr() -> Stderr { Stderr } diff --git a/crates/wasi/src/stdio/worker_thread_stdin.rs b/crates/wasi/src/stdio/worker_thread_stdin.rs index fbde369b3736..894bdd61affd 100644 --- a/crates/wasi/src/stdio/worker_thread_stdin.rs +++ b/crates/wasi/src/stdio/worker_thread_stdin.rs @@ -100,6 +100,10 @@ fn create() -> GlobalStdin { #[derive(Clone)] pub struct Stdin; +/// Returns a stream that represents the host's standard input. +/// +/// Suitable for passing to +/// [`WasiCtxBuilder::stdin`](crate::WasiCtxBuilder::stdin). pub fn stdin() -> Stdin { Stdin } diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs index 80c93b8d78e6..460c227a0daf 100644 --- a/crates/wasi/tests/all/api.rs +++ b/crates/wasi/tests/all/api.rs @@ -1,14 +1,13 @@ use anyhow::Result; -use cap_std::ambient_authority; -use cap_std::fs::Dir; use std::io::Write; use std::sync::Mutex; use std::time::Duration; use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::{Config, Engine, Store}; +use wasmtime_wasi::bindings::Command; use wasmtime_wasi::{ - bindings::wasi::{clocks::wall_clock, filesystem::types as filesystem}, - command::{add_to_linker, Command}, + add_to_linker_async, + bindings::{clocks::wall_clock, filesystem::types as filesystem}, DirPerms, FilePerms, HostMonotonicClock, HostWallClock, WasiCtx, WasiCtxBuilder, WasiView, }; @@ -35,7 +34,7 @@ async fn instantiate(path: &str, ctx: CommandCtx) -> Result<(Store, config.async_support(true).wasm_component_model(true); let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); - add_to_linker(&mut linker)?; + add_to_linker_async(&mut linker)?; let mut store = Store::new(&engine, ctx); let component = Component::from_file(&engine, path)?; @@ -97,9 +96,8 @@ async fn api_read_only() -> Result<()> { std::fs::create_dir(dir.path().join("sub"))?; let table = ResourceTable::new(); - let open_dir = Dir::open_ambient_dir(dir.path(), ambient_authority())?; let wasi = WasiCtxBuilder::new() - .preopened_dir(open_dir, DirPerms::READ, FilePerms::READ, "/") + .preopened_dir(dir.path(), DirPerms::READ, FilePerms::READ, "/")? .build(); let (mut store, command) = @@ -140,7 +138,7 @@ async fn api_reactor() -> Result<()> { config.async_support(true).wasm_component_model(true); let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); - add_to_linker(&mut linker)?; + add_to_linker_async(&mut linker)?; let mut store = Store::new(&engine, CommandCtx { table, wasi }); let component = Component::from_file(&engine, API_REACTOR_COMPONENT)?; diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs index 0ba7bce1701a..6af7036cb9e7 100644 --- a/crates/wasi/tests/all/async_.rs +++ b/crates/wasi/tests/all/async_.rs @@ -1,7 +1,8 @@ use super::*; use std::path::Path; use test_programs_artifacts::*; -use wasmtime_wasi::command::{add_to_linker, Command}; +use wasmtime_wasi::add_to_linker_async; +use wasmtime_wasi::bindings::Command; async fn run(path: &str, inherit_stdio: bool) -> Result<()> { let path = Path::new(path); @@ -10,7 +11,7 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> { config.async_support(true).wasm_component_model(true); let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); - add_to_linker(&mut linker)?; + add_to_linker_async(&mut linker)?; let (mut store, _td) = store(&engine, name, inherit_stdio)?; let component = Component::from_file(&engine, path)?; diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs index ea1b2e87215e..01831b29c2d4 100644 --- a/crates/wasi/tests/all/main.rs +++ b/crates/wasi/tests/all/main.rs @@ -60,9 +60,7 @@ fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store .inherit_network() .allow_ip_name_lookup(true); println!("preopen: {:?}", workspace); - let preopen_dir = - cap_std::fs::Dir::open_ambient_dir(workspace.path(), cap_std::ambient_authority())?; - builder.preopened_dir(preopen_dir, DirPerms::all(), FilePerms::all(), "."); + builder.preopened_dir(workspace.path(), DirPerms::all(), FilePerms::all(), ".")?; for (var, val) in test_programs_artifacts::wasi_tests_environment() { builder.env(var, val); } diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs index 957724eddef4..a67c4dfce2dc 100644 --- a/crates/wasi/tests/all/sync.rs +++ b/crates/wasi/tests/all/sync.rs @@ -1,7 +1,8 @@ use super::*; use std::path::Path; use test_programs_artifacts::*; -use wasmtime_wasi::command::sync::{add_to_linker, Command}; +use wasmtime_wasi::add_to_linker_sync; +use wasmtime_wasi::bindings::sync::Command; fn run(path: &str, inherit_stdio: bool) -> Result<()> { let path = Path::new(path); @@ -10,7 +11,7 @@ fn run(path: &str, inherit_stdio: bool) -> Result<()> { config.wasm_component_model(true); let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); - add_to_linker(&mut linker)?; + add_to_linker_sync(&mut linker)?; let (mut store, _td) = store(&engine, name, inherit_stdio)?; let component = Component::from_file(&engine, path)?; diff --git a/src/commands/run.rs b/src/commands/run.rs index 2c1eed303542..8717f70aafbc 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -262,20 +262,6 @@ impl RunCommand { Ok(()) } - fn compute_preopen_dirs(&self) -> Result> { - let mut preopen_dirs = Vec::new(); - - for (host, guest) in self.dirs.iter() { - preopen_dirs.push(( - guest.clone(), - Dir::open_ambient_dir(host, ambient_authority()) - .with_context(|| format!("failed to open directory '{}'", host))?, - )); - } - - Ok(preopen_dirs) - } - fn compute_preopen_sockets(&self) -> Result> { let mut listeners = vec![]; @@ -484,7 +470,7 @@ impl RunCommand { let component = module.unwrap_component(); - let (command, _instance) = wasmtime_wasi::command::sync::Command::instantiate( + let (command, _instance) = wasmtime_wasi::bindings::sync::Command::instantiate( &mut *store, component, linker, @@ -663,7 +649,7 @@ impl RunCommand { } #[cfg(feature = "component-model")] CliLinker::Component(linker) => { - wasmtime_wasi::command::sync::add_to_linker(linker)?; + wasmtime_wasi::add_to_linker_sync(linker)?; self.set_preview2_ctx(store)?; } } @@ -798,8 +784,10 @@ impl RunCommand { num_fd += 1; } - for (name, dir) in self.compute_preopen_dirs()? { - builder.preopened_dir(dir, name)?; + for (host, guest) in self.dirs.iter() { + let dir = Dir::open_ambient_dir(host, ambient_authority()) + .with_context(|| format!("failed to open directory '{}'", host))?; + builder.preopened_dir(dir, guest)?; } store.data_mut().preview1_ctx = Some(builder.build()); @@ -844,13 +832,13 @@ impl RunCommand { bail!("components do not support --tcplisten"); } - for (name, dir) in self.compute_preopen_dirs()? { + for (host, guest) in self.dirs.iter() { builder.preopened_dir( - dir, + host, wasmtime_wasi::DirPerms::all(), wasmtime_wasi::FilePerms::all(), - name, - ); + guest, + )?; } if self.run.common.wasi.inherit_network == Some(true) { diff --git a/src/commands/serve.rs b/src/commands/serve.rs index c35182b59d78..22520e28b0ce 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -215,7 +215,7 @@ impl ServeCommand { // bindings which adds just those interfaces that the proxy interface // uses. if cli == Some(true) { - wasmtime_wasi::command::add_to_linker(linker)?; + wasmtime_wasi::add_to_linker_async(linker)?; wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?; } else { wasmtime_wasi_http::proxy::add_to_linker(linker)?; From e2de8d6b04db8646e7bd5af7715c951828ec3455 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 24 Mar 2024 09:08:16 -0700 Subject: [PATCH 2/6] Add documentation for `wasmtime_wasi::preview1` and refactor This commit adds documentation for the `wasmtime_wasi::preview1` module and additionally refactors it as well. Previously this was based on a similar design as WASIp2 with a "view trait" and various bits and pieces, but the design constraints of WASIp1 lends itself to a simpler solution of "just" passing around `WasiP1Ctx` instead. This goes back to what `wasi-common` did of sorts where the `add_to_linker_*` functions only need a projection from `&mut T` to `&mut WasiP1Ctx`, a concrete type, which simplifies the module and usage. --- crates/wasi/src/command.rs | 1 - crates/wasi/src/ctx.rs | 22 +- crates/wasi/src/lib.rs | 7 +- crates/wasi/src/p1ctx.rs | 37 ---- crates/wasi/src/preview0.rs | 17 +- crates/wasi/src/preview1.rs | 336 +++++++++++++++++++++++++----- crates/wasi/tests/all/main.rs | 26 +-- crates/wasi/tests/all/preview1.rs | 4 +- examples/wasi-async/main.rs | 45 +--- src/commands/run.rs | 54 ++--- 10 files changed, 357 insertions(+), 192 deletions(-) delete mode 100644 crates/wasi/src/command.rs delete mode 100644 crates/wasi/src/p1ctx.rs diff --git a/crates/wasi/src/command.rs b/crates/wasi/src/command.rs deleted file mode 100644 index 8b137891791f..000000000000 --- a/crates/wasi/src/command.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index 3604b685b3f9..a33266a5358f 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "preview1")] -use crate::WasiP1Ctx; use crate::{ clocks::{ host::{monotonic_clock, wall_clock}, @@ -501,10 +499,26 @@ impl WasiCtxBuilder { } } + /// Builds a WASIp1 context instead of a [`WasiCtx`]. + /// + /// This method is the same as [`build`](WasiCtxBuilder::build) but it + /// creates a [`WasiP1Ctx`] instead. This is intended for use with the + /// [`preview1`] module of this crate + /// + /// [`WasiP1Ctx`]: crate::preview1::WasiP1Ctx + /// [`preview1`]: crate::preview1 + /// + /// # Panics + /// + /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be + /// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated + /// usage of this method is not allowed and should use a second builder + /// instead. #[cfg(feature = "preview1")] - pub fn build_p1(&mut self) -> WasiP1Ctx { + #[cfg_attr(docsrs, doc(cfg(feature = "preview1")))] + pub fn build_p1(&mut self) -> crate::preview1::WasiP1Ctx { let wasi = self.build(); - WasiP1Ctx::new(wasi) + crate::preview1::WasiP1Ctx::new(wasi) } } diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 85a989975f56..0dccdbdc05d3 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -178,19 +178,18 @@ //! [`wasi:sockets/udp`]: bindings::sockets::udp::Host //! [async]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support +#![cfg_attr(docsrs, feature(doc_cfg))] + use wasmtime::component::Linker; pub mod bindings; mod clocks; -pub mod command; mod ctx; mod error; mod filesystem; mod host; mod ip_name_lookup; mod network; -#[cfg(feature = "preview1")] -mod p1ctx; pub mod pipe; mod poll; #[cfg(feature = "preview1")] @@ -210,8 +209,6 @@ pub use self::ctx::{WasiCtx, WasiCtxBuilder, WasiView}; pub use self::error::{I32Exit, TrappableError}; pub use self::filesystem::{DirPerms, FilePerms, FsError, FsResult}; pub use self::network::{Network, SocketAddrUse, SocketError, SocketResult}; -#[cfg(feature = "preview1")] -pub use self::p1ctx::WasiP1Ctx; pub use self::poll::{subscribe, ClosureFuture, MakeFuture, Pollable, PollableFuture, Subscribe}; pub use self::random::{thread_rng, Deterministic}; pub use self::stdio::{ diff --git a/crates/wasi/src/p1ctx.rs b/crates/wasi/src/p1ctx.rs deleted file mode 100644 index 990609de3680..000000000000 --- a/crates/wasi/src/p1ctx.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::preview1::{WasiPreview1Adapter, WasiPreview1View}; -use crate::{WasiCtx, WasiView}; -use wasmtime::component::ResourceTable; - -pub struct WasiP1Ctx { - pub table: ResourceTable, - pub wasi: WasiCtx, - pub adapter: WasiPreview1Adapter, -} - -impl WasiP1Ctx { - pub fn new(wasi: WasiCtx) -> Self { - Self { - table: ResourceTable::new(), - wasi, - adapter: WasiPreview1Adapter::new(), - } - } -} - -impl WasiView for WasiP1Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi - } -} - -impl WasiPreview1View for WasiP1Ctx { - fn adapter(&self) -> &WasiPreview1Adapter { - &self.adapter - } - fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter { - &mut self.adapter - } -} diff --git a/crates/wasi/src/preview0.rs b/crates/wasi/src/preview0.rs index 38c0de8ddadb..c1992cb5a3c9 100644 --- a/crates/wasi/src/preview0.rs +++ b/crates/wasi/src/preview0.rs @@ -1,19 +1,26 @@ +//! Bindings for WASIp0 aka Preview 0 aka `wasi_unstable`. +//! +//! This module is purely here for backwards compatibility in the Wasmtime CLI. +//! You probably want to use [`preview1`](crate::preview1) instead. + +#![cfg_attr(docsrs, doc(cfg(feature = "preview1")))] + use crate::preview0::types::Error; use crate::preview1::types as snapshot1_types; use crate::preview1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; -use crate::preview1::WasiPreview1View; +use crate::preview1::WasiP1Ctx; use wiggle::{GuestError, GuestPtr}; -pub fn add_to_linker_async( +pub fn add_to_linker_async( linker: &mut wasmtime::Linker, - f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static, + f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { wasi_unstable::add_to_linker(linker, f) } -pub fn add_to_linker_sync( +pub fn add_to_linker_sync( linker: &mut wasmtime::Linker, - f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static, + f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { sync::add_wasi_unstable_to_linker(linker, f) } diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs index e0e009007796..96bab3f88f17 100644 --- a/crates/wasi/src/preview1.rs +++ b/crates/wasi/src/preview1.rs @@ -1,7 +1,71 @@ +//! Bindings for WASIp1 aka Preview 1 aka `wasi_snapshot_preview1`. //! +//! This module contains runtime support for configuring and executing +//! WASIp1-using core WebAssembly modules. Support for WASIp1 is built on top of +//! support for WASIp2 available at [the crate root](crate), but that's just an +//! internal implementation detail. +//! +//! Unlike the crate root support for WASIp1 centers around two APIs: +//! +//! * [`WasiP1Ctx`] +//! * [`add_to_linker_sync`] (or [`add_to_linker_async`]) +//! +//! First a [`WasiCtxBuilder`] will be used and finalized with the [`build_p1`] +//! method to create a [`WasiCtx`]. Next a [`wasmtime::Linker`] is configured +//! with WASI imports by using the `add_to_linker_*` desired (sync or async +//! depending on [`Config::async_support`]). +//! +//! Note that WASIp1 is not as extensible or configurable as WASIp2 so the +//! support in this module is enough to run wasm modules but any customization +//! beyond that [`WasiCtxBuilder`] already supports is not possible yet. +//! +//! [`WasiCtxBuilder`]: crate::WasiCtxBuilder +//! [`build_p1`]: crate::WasiCtxBuilder::build_p1 +//! [`Config::async_support`]: wasmtime::Config::async_support +//! +//! # Components vs Modules +//! +//! Note that WASIp1 does not work for components at this time, only core wasm +//! modules. That means this module is only for users of [`wasmtime::Module`] +//! and [`wasmtime::Linker`], for example. If you're using +//! [`wasmtime::component::Component`] or [`wasmtime::component::Linker`] you'll +//! want the WASIp2 [support this crate has](crate) instead. +//! +//! # Examples +//! +//! ```no_run +//! use wasmtime::{Result, Engine, Linker, Module, Store}; +//! use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +//! use wasmtime_wasi::WasiCtxBuilder; +//! +//! // An example of executing a WASIp1 "command" +//! fn main() -> Result<()> { +//! let args = std::env::args().skip(1).collect::>(); +//! let engine = Engine::default(); +//! let module = Module::from_file(&engine, &args[0])?; +//! +//! let mut linker: Linker = Linker::new(&engine); +//! preview1::add_to_linker_async(&mut linker, |t| t)?; +//! let pre = linker.instantiate_pre(&module)?; +//! +//! let wasi_ctx = WasiCtxBuilder::new() +//! .inherit_stdio() +//! .inherit_env() +//! .args(&args) +//! .build_p1(); +//! +//! let mut store = Store::new(&engine, wasi_ctx); +//! let instance = pre.instantiate(&mut store)?; +//! let func = instance.get_typed_func::<(), ()>(&mut store, "_start")?; +//! func.call(&mut store, ())?; +//! +//! Ok(()) +//! } +//! ``` + +#![cfg_attr(docsrs, doc(cfg(feature = "preview1")))] use crate::bindings::{ - self, cli::{ stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin, terminal_stdout, @@ -10,7 +74,7 @@ use crate::bindings::{ filesystem::{preopens, types as filesystem}, io::{poll, streams}, }; -use crate::{FsError, IsATTY, StreamError, StreamResult, WasiView}; +use crate::{FsError, IsATTY, ResourceTable, StreamError, StreamResult, WasiCtx, WasiView}; use anyhow::{bail, Context}; use std::borrow::Borrow; use std::collections::{BTreeMap, HashSet}; @@ -23,6 +87,81 @@ use wasmtime::component::Resource; use wiggle::tracing::instrument; use wiggle::{GuestError, GuestPtr, GuestStrCow, GuestType}; +// Bring all WASI traits in scope that this implementation builds on. +use crate::bindings::cli::environment::Host as _; +use crate::bindings::filesystem::types::HostDescriptor as _; +use crate::bindings::io::poll::Host as _; +use crate::bindings::random::random::Host as _; + +/// Structure containing state for WASIp1. +/// +/// This structure is created through [`WasiCtxBuilder::build_p1`] and is +/// configured through the various methods of [`WasiCtxBuilder`]. This structure +/// itself implements generated traits for WASIp1 as well as [`WasiView`] to +/// have access to WASIp2. +/// +/// Instances of [`WasiP1Ctx`] are typically stored within the `T` of +/// [`Store`](wasmtime::Store). +/// +/// [`WasiCtxBuilder::build_p1`]: crate::WasiCtxBuilder::build_p1 +/// [`WasiCtxBuilder`]: crate::WasiCtxBuilder +/// +/// # Examples +/// +/// ```no_run +/// use wasmtime::{Result, Linker}; +/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// use wasmtime_wasi::WasiCtxBuilder; +/// +/// struct MyState { +/// // ... custom state as necessary ... +/// +/// wasi: WasiP1Ctx, +/// } +/// +/// impl MyState { +/// fn new() -> MyState { +/// MyState { +/// // .. initialize custom state if needed .. +/// +/// wasi: WasiCtxBuilder::new() +/// .arg("./foo.wasm") +/// // .. more customization if necesssary .. +/// .build_p1(), +/// } +/// } +/// } +/// +/// fn add_to_linker(linker: &mut Linker) -> Result<()> { +/// preview1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?; +/// Ok(()) +/// } +/// ``` +pub struct WasiP1Ctx { + table: ResourceTable, + wasi: WasiCtx, + adapter: WasiPreview1Adapter, +} + +impl WasiP1Ctx { + pub(crate) fn new(wasi: WasiCtx) -> Self { + Self { + table: ResourceTable::new(), + wasi, + adapter: WasiPreview1Adapter::new(), + } + } +} + +impl WasiView for WasiP1Ctx { + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } + fn ctx(&mut self) -> &mut WasiCtx { + &mut self.wasi + } +} + #[derive(Debug)] struct File { /// The handle to the preview2 descriptor of type [`crate::filesystem::Descriptor::File`]. @@ -165,7 +304,7 @@ enum Descriptor { } #[derive(Debug, Default)] -pub struct WasiPreview1Adapter { +struct WasiPreview1Adapter { descriptors: Option, } @@ -315,18 +454,11 @@ impl Descriptors { } impl WasiPreview1Adapter { - pub fn new() -> Self { + fn new() -> Self { Self::default() } } -// Any context that needs to support preview 1 will impl this trait. They can -// construct the needed member with WasiPreview1Adapter::new(). -pub trait WasiPreview1View: WasiView { - fn adapter(&self) -> &WasiPreview1Adapter; - fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter; -} - /// A mutably-borrowed [`WasiPreview1View`] implementation, which provides access to the stored /// state. It can be thought of as an in-flight [`WasiPreview1Adapter`] transaction, all /// changes will be recorded in the underlying [`WasiPreview1Adapter`] returned by @@ -336,20 +468,20 @@ pub trait WasiPreview1View: WasiView { // of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`] // without having to rely on something like `Arc>`, while also being able to // call methods like [`Descriptor::is_file`] and hiding complexity from preview1 method implementations. -struct Transaction<'a, T: WasiPreview1View + ?Sized> { - view: &'a mut T, +struct Transaction<'a> { + view: &'a mut WasiP1Ctx, descriptors: Descriptors, } -impl Drop for Transaction<'_, T> { +impl Drop for Transaction<'_> { /// Record changes in the [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] fn drop(&mut self) { let descriptors = mem::take(&mut self.descriptors); - self.view.adapter_mut().descriptors = Some(descriptors); + self.view.adapter.descriptors = Some(descriptors); } } -impl Transaction<'_, T> { +impl Transaction<'_> { /// Borrows [`Descriptor`] corresponding to `fd`. /// /// # Errors @@ -429,22 +561,11 @@ impl Transaction<'_, T> { } } -trait WasiPreview1ViewExt: - WasiPreview1View - + preopens::Host - + stdin::Host - + stdout::Host - + stderr::Host - + terminal_input::Host - + terminal_output::Host - + terminal_stdin::Host - + terminal_stdout::Host - + terminal_stderr::Host -{ +impl WasiP1Ctx { /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`Transaction`] on success - fn transact(&mut self) -> Result, types::Error> { - let descriptors = if let Some(descriptors) = self.adapter_mut().descriptors.take() { + fn transact(&mut self) -> Result, types::Error> { + let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() { descriptors } else { Descriptors::new(self)? @@ -490,21 +611,150 @@ trait WasiPreview1ViewExt: } } -impl WasiPreview1ViewExt for T {} - -pub fn add_to_linker_async( +/// Adds asynchronous versions of all WASIp1 functions to the +/// [`wasmtime::Linker`] provided. +/// +/// This method will add WASIp1 functions to `linker`. The `f` closure provided +/// is used to project from the `T` state that `Linker` is associated with to a +/// [`WasiP1Ctx`]. If `T` is `WasiP1Ctx` itself then this is the identity +/// closure, but otherwise it must project out the field where `WasiP1Ctx` is +/// stored within `T`. +/// +/// The state provided by `f` is used to implement all WASIp1 functions and +/// provides configuration to know what to return. +/// +/// Note that this function is intended for use with +/// [`Config::async_support(true)`]. If you're looking for a synchronous version +/// see [`add_to_linker_sync`]. +/// +/// [`Config::async_support(true)`]: wasmtime::Config::async_support +/// +/// # Examples +/// +/// If the `T` in `Linker` is just `WasiP1Ctx`: +/// +/// ```no_run +/// use wasmtime::{Result, Linker, Engine, Config}; +/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// +/// fn main() -> Result<()> { +/// let mut config = Config::new(); +/// config.async_support(true); +/// let engine = Engine::new(&config)?; +/// +/// let mut linker: Linker = Linker::new(&engine); +/// preview1::add_to_linker_async(&mut linker, |cx| cx)?; +/// +/// // ... continue to add more to `linker` as necessary and use it ... +/// +/// Ok(()) +/// } +/// ``` +/// +/// If the `T` in `Linker` is custom state: +/// +/// ```no_run +/// use wasmtime::{Result, Linker, Engine, Config}; +/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// +/// struct MyState { +/// // .. other custom state here .. +/// +/// wasi: WasiP1Ctx, +/// } +/// +/// fn main() -> Result<()> { +/// let mut config = Config::new(); +/// config.async_support(true); +/// let engine = Engine::new(&config)?; +/// +/// let mut linker: Linker = Linker::new(&engine); +/// preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; +/// +/// // ... continue to add more to `linker` as necessary and use it ... +/// +/// Ok(()) +/// } +/// ``` +pub fn add_to_linker_async( linker: &mut wasmtime::Linker, - f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static, + f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { crate::preview1::wasi_snapshot_preview1::add_to_linker(linker, f) } -pub fn add_to_linker_sync( +/// Adds synchronous versions of all WASIp1 functions to the +/// [`wasmtime::Linker`] provided. +/// +/// This method will add WASIp1 functions to `linker`. The `f` closure provided +/// is used to project from the `T` state that `Linker` is associated with to a +/// [`WasiP1Ctx`]. If `T` is `WasiP1Ctx` itself then this is the identity +/// closure, but otherwise it must project out the field where `WasiP1Ctx` is +/// stored within `T`. +/// +/// The state provided by `f` is used to implement all WASIp1 functions and +/// provides configuration to know what to return. +/// +/// Note that this function is intended for use with +/// [`Config::async_support(false)`]. If you're looking for a synchronous version +/// see [`add_to_linker_async`]. +/// +/// [`Config::async_support(false)`]: wasmtime::Config::async_support +/// +/// # Examples +/// +/// If the `T` in `Linker` is just `WasiP1Ctx`: +/// +/// ```no_run +/// use wasmtime::{Result, Linker, Engine, Config}; +/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// +/// fn main() -> Result<()> { +/// let mut config = Config::new(); +/// config.async_support(true); +/// let engine = Engine::new(&config)?; +/// +/// let mut linker: Linker = Linker::new(&engine); +/// preview1::add_to_linker_async(&mut linker, |cx| cx)?; +/// +/// // ... continue to add more to `linker` as necessary and use it ... +/// +/// Ok(()) +/// } +/// ``` +/// +/// If the `T` in `Linker` is custom state: +/// +/// ```no_run +/// use wasmtime::{Result, Linker, Engine, Config}; +/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// +/// struct MyState { +/// // .. other custom state here .. +/// +/// wasi: WasiP1Ctx, +/// } +/// +/// fn main() -> Result<()> { +/// let mut config = Config::new(); +/// config.async_support(true); +/// let engine = Engine::new(&config)?; +/// +/// let mut linker: Linker = Linker::new(&engine); +/// preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; +/// +/// // ... continue to add more to `linker` as necessary and use it ... +/// +/// Ok(()) +/// } +/// ``` +pub fn add_to_linker_sync( linker: &mut wasmtime::Linker, - f: impl Fn(&mut T) -> &mut W + Copy + Send + Sync + 'static, + f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { crate::preview1::sync::add_wasi_snapshot_preview1_to_linker(linker, f) } + // Generate the wasi_snapshot_preview1::WasiSnapshotPreview1 trait, // and the module types. // None of the generated modules, traits, or types should be used externally @@ -837,19 +1087,7 @@ fn first_non_empty_iovec<'a>(iovs: &types::IovecArray<'a>) -> Result wasi_snapshot_preview1::WasiSnapshotPreview1 for T -{ +impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { #[instrument(skip(self))] fn args_get<'b>( &mut self, diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs index 01831b29c2d4..dcf595c7cdcd 100644 --- a/crates/wasi/tests/all/main.rs +++ b/crates/wasi/tests/all/main.rs @@ -4,35 +4,23 @@ use wasmtime::{ component::{Component, Linker, ResourceTable}, Config, Engine, Store, }; +use wasmtime_wasi::preview1::WasiP1Ctx; use wasmtime_wasi::{ - pipe::MemoryOutputPipe, - preview1::{WasiPreview1Adapter, WasiPreview1View}, - DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView, + pipe::MemoryOutputPipe, DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView, }; struct Ctx { - table: ResourceTable, - wasi: WasiCtx, stdout: MemoryOutputPipe, stderr: MemoryOutputPipe, - adapter: WasiPreview1Adapter, + wasi: WasiP1Ctx, } impl WasiView for Ctx { fn table(&mut self) -> &mut ResourceTable { - &mut self.table + self.wasi.table() } fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi - } -} - -impl WasiPreview1View for Ctx { - fn adapter(&self) -> &WasiPreview1Adapter { - &self.adapter - } - fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter { - &mut self.adapter + self.wasi.ctx() } } @@ -66,11 +54,9 @@ fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store } let ctx = Ctx { - table: ResourceTable::new(), - wasi: builder.build(), + wasi: builder.build_p1(), stderr, stdout, - adapter: WasiPreview1Adapter::new(), }; Ok((Store::new(&engine, ctx), workspace)) diff --git a/crates/wasi/tests/all/preview1.rs b/crates/wasi/tests/all/preview1.rs index f0d958fdf478..169c764ca2d5 100644 --- a/crates/wasi/tests/all/preview1.rs +++ b/crates/wasi/tests/all/preview1.rs @@ -10,8 +10,8 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> { let mut config = Config::new(); config.async_support(true); let engine = Engine::new(&config)?; - let mut linker = Linker::new(&engine); - add_to_linker_async(&mut linker, |t| t)?; + let mut linker = Linker::::new(&engine); + add_to_linker_async(&mut linker, |t| &mut t.wasi)?; let module = Module::from_file(&engine, path)?; let (mut store, _td) = store(&engine, name, inherit_stdio)?; diff --git a/examples/wasi-async/main.rs b/examples/wasi-async/main.rs index 9fea4ce8a228..57ad304d73be 100644 --- a/examples/wasi-async/main.rs +++ b/examples/wasi-async/main.rs @@ -9,32 +9,8 @@ You can execute this example with: use anyhow::Result; use wasmtime::{Config, Engine, Linker, Module, Store}; - -struct WasiHostCtx { - preview2_ctx: wasmtime_wasi::WasiCtx, - preview2_table: wasmtime::component::ResourceTable, - preview1_adapter: wasmtime_wasi::preview1::WasiPreview1Adapter, -} - -impl wasmtime_wasi::WasiView for WasiHostCtx { - fn table(&mut self) -> &mut wasmtime::component::ResourceTable { - &mut self.preview2_table - } - - fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx { - &mut self.preview2_ctx - } -} - -impl wasmtime_wasi::preview1::WasiPreview1View for WasiHostCtx { - fn adapter(&self) -> &wasmtime_wasi::preview1::WasiPreview1Adapter { - &self.preview1_adapter - } - - fn adapter_mut(&mut self) -> &mut wasmtime_wasi::preview1::WasiPreview1Adapter { - &mut self.preview1_adapter - } -} +use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +use wasmtime_wasi::WasiCtxBuilder; #[tokio::main] async fn main() -> Result<()> { @@ -45,18 +21,15 @@ async fn main() -> Result<()> { // Add the WASI preview1 API to the linker (will be implemented in terms of // the preview2 API) - let mut linker: Linker = Linker::new(&engine); - wasmtime_wasi::preview1::add_to_linker_async(&mut linker, |t| t)?; + let mut linker: Linker = Linker::new(&engine); + preview1::add_to_linker_async(&mut linker, |t| t)?; - // Add capabilities (e.g. filesystem access) to the WASI preview2 context here. - let wasi_ctx = wasmtime_wasi::WasiCtxBuilder::new().inherit_stdio().build(); + // Add capabilities (e.g. filesystem access) to the WASI preview2 context + // here. Here only stdio is inherited, but see docs of `WasiCtxBuilder` for + // more. + let wasi_ctx = WasiCtxBuilder::new().inherit_stdio().build_p1(); - let host_ctx = WasiHostCtx { - preview2_ctx: wasi_ctx, - preview2_table: wasmtime::component::ResourceTable::new(), - preview1_adapter: wasmtime_wasi::preview1::WasiPreview1Adapter::new(), - }; - let mut store: Store = Store::new(&engine, host_ctx); + let mut store = Store::new(&engine, wasi_ctx); // Instantiate our 'Hello World' wasm module. // Note: This is a module built against the preview1 WASI API. diff --git a/src/commands/run.rs b/src/commands/run.rs index 8717f70aafbc..4526f24a5d2e 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -15,6 +15,7 @@ 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::WasiView; #[cfg(feature = "wasi-nn")] use wasmtime_wasi_nn::WasiNnCtx; @@ -640,9 +641,13 @@ impl RunCommand { // default-disabled in the future. (Some(true), _) | (None, Some(false) | None) => { if self.run.common.wasi.preview0 != Some(false) { - wasmtime_wasi::preview0::add_to_linker_sync(linker, |t| t)?; + wasmtime_wasi::preview0::add_to_linker_sync(linker, |t| { + t.preview2_ctx() + })?; } - wasmtime_wasi::preview1::add_to_linker_sync(linker, |t| t)?; + wasmtime_wasi::preview1::add_to_linker_sync(linker, |t| { + t.preview2_ctx() + })?; self.set_preview2_ctx(store)?; } } @@ -854,7 +859,7 @@ impl RunCommand { builder.allow_udp(enable); } - let ctx = builder.build(); + let ctx = builder.build_p1(); store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx))); Ok(()) } @@ -867,16 +872,7 @@ struct Host { // 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 // access. - preview2_ctx: Option>>, - - // Resource table for preview2 if the `preview2_ctx` is in use, otherwise - // "just" an empty table. - preview2_table: Arc>, - - // State necessary for the preview1 implementation of WASI backed by the - // preview2 host implementation. Only used with the `--preview2` flag right - // now when running core modules. - preview2_adapter: Arc, + preview2_ctx: Option>>, #[cfg(feature = "wasi-nn")] wasi_nn: Option>, @@ -889,16 +885,12 @@ struct Host { guest_profiler: Option>, } -impl wasmtime_wasi::WasiView for Host { - fn table(&mut self) -> &mut wasmtime::component::ResourceTable { - Arc::get_mut(&mut self.preview2_table) - .expect("wasmtime_wasi is not compatible with threads") - .get_mut() - .unwrap() - } - - fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx { - let ctx = self.preview2_ctx.as_mut().unwrap(); +impl Host { + fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx { + let ctx = self + .preview2_ctx + .as_mut() + .expect("wasip2 is not configured"); Arc::get_mut(ctx) .expect("wasmtime_wasi is not compatible with threads") .get_mut() @@ -906,14 +898,13 @@ impl wasmtime_wasi::WasiView for Host { } } -impl wasmtime_wasi::preview1::WasiPreview1View for Host { - fn adapter(&self) -> &wasmtime_wasi::preview1::WasiPreview1Adapter { - &self.preview2_adapter +impl WasiView for Host { + fn table(&mut self) -> &mut wasmtime::component::ResourceTable { + self.preview2_ctx().table() } - fn adapter_mut(&mut self) -> &mut wasmtime_wasi::preview1::WasiPreview1Adapter { - Arc::get_mut(&mut self.preview2_adapter) - .expect("wasmtime_wasi is not compatible with threads") + fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx { + self.preview2_ctx().ctx() } } @@ -925,10 +916,7 @@ impl wasmtime_wasi_http::types::WasiHttpView for Host { } fn table(&mut self) -> &mut wasmtime::component::ResourceTable { - Arc::get_mut(&mut self.preview2_table) - .expect("preview2 is not compatible with threads") - .get_mut() - .unwrap() + self.preview2_ctx().table() } } From 5c69d936668f31c55c8a4301b5a6c430e12e9c53 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 25 Mar 2024 09:53:16 -0700 Subject: [PATCH 3/6] Small refactorings to `preopened_dir` --- crates/wasi/src/ctx.rs | 22 +++++++++++----------- crates/wasi/tests/all/api.rs | 2 +- crates/wasi/tests/all/main.rs | 2 +- src/commands/run.rs | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index a33266a5358f..9d0917d89e75 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -293,14 +293,14 @@ impl WasiCtxBuilder { /// * `host_path` - a path to a directory on the host to open and make /// accessible to WebAssembly. Note that the name of this directory in the /// guest is configured with `guest_path` below. - /// * `perms` - this is the permissions that wasm will have to operate on + /// * `guest_path` - the name of the preopened directory from WebAssembly's + /// perspective. Note that this does not need to match the host's name for + /// the directory. + /// * `dir_perms` - this is the permissions that wasm will have to operate on /// `dir`. This can be used, for example, to provide readonly access to a /// directory. /// * `file_perms` - similar to `perms` but corresponds to the maximum set /// of permissions that can be used for any file in this directory. - /// * `guest_path` - the name of the preopened directory from WebAssembly's - /// perspective. Note that this does not need to match the host's name for - /// the directory. /// /// # Errors /// @@ -316,32 +316,32 @@ impl WasiCtxBuilder { /// let mut wasi = WasiCtxBuilder::new(); /// /// // Make `./host-directory` available in the guest as `.` - /// wasi.preopened_dir("./host-directory", DirPerms::all(), FilePerms::all(), "."); + /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all()); /// /// // Make `./readonly` available in the guest as `./ro` - /// wasi.preopened_dir("./readonly", DirPerms::READ, FilePerms::READ, "./ro"); + /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ); /// # Ok(()) /// # } /// ``` pub fn preopened_dir( &mut self, host_path: impl AsRef, - perms: DirPerms, - file_perms: FilePerms, guest_path: impl AsRef, + dir_perms: DirPerms, + file_perms: FilePerms, ) -> Result<&mut Self> { let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; let mut open_mode = OpenMode::empty(); - if perms.contains(DirPerms::READ) { + if dir_perms.contains(DirPerms::READ) { open_mode |= OpenMode::READ; } - if perms.contains(DirPerms::MUTATE) { + if dir_perms.contains(DirPerms::MUTATE) { open_mode |= OpenMode::WRITE; } self.preopens.push(( Dir::new( dir, - perms, + dir_perms, file_perms, open_mode, self.allow_blocking_current_thread, diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs index 460c227a0daf..3cdc2e4814e6 100644 --- a/crates/wasi/tests/all/api.rs +++ b/crates/wasi/tests/all/api.rs @@ -97,7 +97,7 @@ async fn api_read_only() -> Result<()> { let table = ResourceTable::new(); let wasi = WasiCtxBuilder::new() - .preopened_dir(dir.path(), DirPerms::READ, FilePerms::READ, "/")? + .preopened_dir(dir.path(), "/", DirPerms::READ, FilePerms::READ)? .build(); let (mut store, command) = diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs index dcf595c7cdcd..312ba1c7a5f9 100644 --- a/crates/wasi/tests/all/main.rs +++ b/crates/wasi/tests/all/main.rs @@ -48,7 +48,7 @@ fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store .inherit_network() .allow_ip_name_lookup(true); println!("preopen: {:?}", workspace); - builder.preopened_dir(workspace.path(), DirPerms::all(), FilePerms::all(), ".")?; + builder.preopened_dir(workspace.path(), ".", DirPerms::all(), FilePerms::all())?; for (var, val) in test_programs_artifacts::wasi_tests_environment() { builder.env(var, val); } diff --git a/src/commands/run.rs b/src/commands/run.rs index 4526f24a5d2e..76310b3ad96c 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -840,9 +840,9 @@ impl RunCommand { for (host, guest) in self.dirs.iter() { builder.preopened_dir( host, + guest, wasmtime_wasi::DirPerms::all(), wasmtime_wasi::FilePerms::all(), - guest, )?; } From c210213a6e2e36225d231acf950e028206c1935b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 25 Mar 2024 09:54:13 -0700 Subject: [PATCH 4/6] Add `WasiCtx::builder`. --- crates/wasi/src/ctx.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index 9d0917d89e75..f2007f2bdd2c 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -611,6 +611,13 @@ pub struct WasiCtx { pub(crate) allow_blocking_current_thread: bool, } +impl WasiCtx { + /// Convenience function for calling [`WasiCtxBuilder::new`]. + pub fn builder() -> WasiCtxBuilder { + WasiCtxBuilder::new() + } +} + pub struct AllowedNetworkUses { pub ip_name_lookup: bool, pub udp: bool, From 707ce2ac5edffcbae2b825ad460a0548c4041552 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Mar 2024 07:22:57 -0700 Subject: [PATCH 5/6] Fix typo --- crates/wasi/src/bindings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs index faf1e77ce695..f5dc94cd5f28 100644 --- a/crates/wasi/src/bindings.rs +++ b/crates/wasi/src/bindings.rs @@ -42,7 +42,7 @@ pub mod sync { /// /// This structure is automatically generated by `bindgen!` and is intended /// to be used with [`Config::async_support(false)`][async]. For the - /// asynchronous version see [`binidngs::Command`](super::Command). + /// asynchronous version see [`bindings::Command`](super::Command). /// /// This can be used for a more "typed" view of executing a command /// component through the [`Command::wasi_cli_run`] method plus From 28fcef2f983975092af3da27479ac04a31874e0b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Mar 2024 07:29:36 -0700 Subject: [PATCH 6/6] Review comments --- crates/wasi/src/bindings.rs | 2 +- crates/wasi/src/filesystem.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs index f5dc94cd5f28..57c08329a0d3 100644 --- a/crates/wasi/src/bindings.rs +++ b/crates/wasi/src/bindings.rs @@ -15,7 +15,7 @@ pub mod sync { "wasi:filesystem/types/error-code" => FsError, }, with: { - // These interfaces comes from the outer module, as it's + // These interfaces come from the outer module, as it's // sync/async agnostic. "wasi:clocks": crate::bindings::clocks, "wasi:random": crate::bindings::random, diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index 652cd5174f15..49c03985cf12 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -147,8 +147,8 @@ enum SpawnBlocking { bitflags::bitflags! { /// Permission bits for operating on a directory. /// - /// Directories can be limited to being readonly meaning that new files - /// cannot be created for example. + /// Directories can be limited to being readonly. This will restrict what + /// can be done with them, for example preventing creation of new files. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct DirPerms: usize { /// This directory can be read, for example its entries can be iterated