use crate::cli::{StdinStream, StdoutStream, WasiCliCtx};1use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};2use crate::filesystem::{Dir, WasiFilesystemCtx};3use crate::random::WasiRandomCtx;4use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx};5use crate::{DirPerms, FilePerms, OpenMode};6use cap_rand::RngCore;7use cap_std::ambient_authority;8use std::future::Future;9use std::mem;10use std::net::SocketAddr;11use std::path::Path;12use std::pin::Pin;13use tokio::io::{stderr, stdin, stdout};14use wasmtime::Result;1516/// Builder-style structure used to create a [`WasiCtx`].17///18/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`]19/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the20/// building process and produce a finalized [`WasiCtx`].21///22/// # Examples23///24/// ```25/// use wasmtime_wasi::WasiCtx;26///27/// let mut wasi = WasiCtx::builder();28/// wasi.arg("./foo.wasm");29/// wasi.arg("--help");30/// wasi.env("FOO", "bar");31///32/// let wasi: WasiCtx = wasi.build();33/// ```34///35/// [`Store`]: wasmtime::Store36#[derive(Default)]37pub struct WasiCtxBuilder {38cli: WasiCliCtx,39clocks: WasiClocksCtx,40filesystem: WasiFilesystemCtx,41random: WasiRandomCtx,42sockets: WasiSocketsCtx,43built: bool,44}4546impl WasiCtxBuilder {47/// Creates a builder for a new context with default parameters set.48///49/// The current defaults are:50///51/// * stdin is closed52/// * stdout and stderr eat all input and it doesn't go anywhere53/// * no env vars54/// * no arguments55/// * no preopens56/// * clocks use the host implementation of wall/monotonic clocks57/// * RNGs are all initialized with random state and suitable generator58/// quality to satisfy the requirements of WASI APIs.59/// * TCP/UDP are allowed but all addresses are denied by default.60/// * `wasi:sockets/ip-name-lookup` is denied by default.61///62/// These defaults can all be updated via the various builder configuration63/// methods below.64pub fn new() -> Self {65Self::default()66}6768/// Provides a custom implementation of stdin to use.69///70/// By default stdin is closed but an example of using the host's native71/// stdin looks like:72///73/// ```74/// use wasmtime_wasi::WasiCtx;75/// use wasmtime_wasi::cli::stdin;76///77/// let mut wasi = WasiCtx::builder();78/// wasi.stdin(stdin());79/// ```80///81/// Note that inheriting the process's stdin can also be done through82/// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin).83pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self {84self.cli.stdin = Box::new(stdin);85self86}8788/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.89pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self {90self.cli.stdout = Box::new(stdout);91self92}9394/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.95pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self {96self.cli.stderr = Box::new(stderr);97self98}99100/// Configures this context's stdin stream to read the host process's101/// stdin.102///103/// Note that concurrent reads of stdin can produce surprising results so104/// when using this it's typically best to have a single wasm instance in105/// the process using this.106pub fn inherit_stdin(&mut self) -> &mut Self {107self.stdin(stdin())108}109110/// Configures this context's stdout stream to write to the host process's111/// stdout.112///113/// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin)114/// multiple instances printing to stdout works well.115pub fn inherit_stdout(&mut self) -> &mut Self {116self.stdout(stdout())117}118119/// Configures this context's stderr stream to write to the host process's120/// stderr.121///122/// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin)123/// multiple instances printing to stderr works well.124pub fn inherit_stderr(&mut self) -> &mut Self {125self.stderr(stderr())126}127128/// Configures all of stdin, stdout, and stderr to be inherited from the129/// host process.130///131/// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale132/// on why this should only be done in situations of133/// one-instance-per-process.134pub fn inherit_stdio(&mut self) -> &mut Self {135self.inherit_stdin().inherit_stdout().inherit_stderr()136}137138/// Configures whether or not blocking operations made through this139/// `WasiCtx` are allowed to block the current thread.140///141/// WASI is currently implemented on top of the Rust142/// [Tokio](https://tokio.rs/) library. While most WASI APIs are143/// non-blocking some are instead blocking from the perspective of144/// WebAssembly. For example opening a file is a blocking operation with145/// respect to WebAssembly but it's implemented as an asynchronous operation146/// on the host. This is currently done with Tokio's147/// [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html).148///149/// When WebAssembly is used in a synchronous context then this asynchronous150/// operation is quickly turned back into a synchronous operation with a151/// `block_on` in Rust. This switching back-and-forth between a blocking a152/// non-blocking context can have overhead, and this option exists to help153/// alleviate this overhead.154///155/// This option indicates that for WASI functions that are blocking from the156/// perspective of WebAssembly it's ok to block the native thread as well.157/// This means that this back-and-forth between async and sync won't happen158/// and instead blocking operations are performed on-thread (such as opening159/// a file). This can improve the performance of WASI operations when async160/// support is disabled.161pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self {162self.filesystem.allow_blocking_current_thread = enable;163self164}165166/// Appends multiple environment variables at once for this builder.167///168/// All environment variables are appended to the list of environment169/// variables that this builder will configure.170///171/// At this time environment variables are not deduplicated and if the same172/// key is set twice then the guest will see two entries for the same key.173///174/// # Examples175///176/// ```177/// use wasmtime_wasi::WasiCtxBuilder;178///179/// let mut wasi = WasiCtxBuilder::new();180/// wasi.envs(&[181/// ("FOO", "bar"),182/// ("HOME", "/somewhere"),183/// ]);184/// ```185pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {186self.cli.environment.extend(187env.iter()188.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())),189);190self191}192193/// Appends a single environment variable for this builder.194///195/// At this time environment variables are not deduplicated and if the same196/// key is set twice then the guest will see two entries for the same key.197///198/// # Examples199///200/// ```201/// use wasmtime_wasi::WasiCtxBuilder;202///203/// let mut wasi = WasiCtxBuilder::new();204/// wasi.env("FOO", "bar");205/// ```206pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {207self.cli208.environment209.push((k.as_ref().to_owned(), v.as_ref().to_owned()));210self211}212213/// Configures all environment variables to be inherited from the calling214/// process into this configuration.215///216/// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined217/// environment variables.218pub fn inherit_env(&mut self) -> &mut Self {219self.cli.environment.extend(std::env::vars());220self221}222223/// Appends a list of arguments to the argument array to pass to wasm.224pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {225self.cli226.arguments227.extend(args.iter().map(|a| a.as_ref().to_owned()));228self229}230231/// Appends a single argument to get passed to wasm.232pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {233self.cli.arguments.push(arg.as_ref().to_owned());234self235}236237/// Appends all host process arguments to the list of arguments to get238/// passed to wasm.239pub fn inherit_args(&mut self) -> &mut Self {240self.cli.arguments.extend(std::env::args());241self242}243244/// Configures a "preopened directory" to be available to WebAssembly.245///246/// By default WebAssembly does not have access to the filesystem because247/// there are no preopened directories. All filesystem operations, such as248/// opening a file, are done through a preexisting handle. This means that249/// to provide WebAssembly access to a directory it must be configured250/// through this API.251///252/// WASI will also prevent access outside of files provided here. For253/// example `..` can't be used to traverse up from the `host_path` provided here254/// to the containing directory.255///256/// * `host_path` - a path to a directory on the host to open and make257/// accessible to WebAssembly. Note that the name of this directory in the258/// guest is configured with `guest_path` below.259/// * `guest_path` - the name of the preopened directory from WebAssembly's260/// perspective. Note that this does not need to match the host's name for261/// the directory.262/// * `dir_perms` - this is the permissions that wasm will have to operate on263/// `guest_path`. This can be used, for example, to provide readonly access to a264/// directory.265/// * `file_perms` - similar to `dir_perms` but corresponds to the maximum set266/// of permissions that can be used for any file in this directory.267///268/// # Errors269///270/// This method will return an error if `host_path` cannot be opened.271///272/// # Examples273///274/// ```275/// use wasmtime_wasi::WasiCtxBuilder;276/// use wasmtime_wasi::{DirPerms, FilePerms};277///278/// # fn main() {}279/// # fn foo() -> wasmtime::Result<()> {280/// let mut wasi = WasiCtxBuilder::new();281///282/// // Make `./host-directory` available in the guest as `.`283/// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all());284///285/// // Make `./readonly` available in the guest as `./ro`286/// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ);287/// # Ok(())288/// # }289/// ```290pub fn preopened_dir(291&mut self,292host_path: impl AsRef<Path>,293guest_path: impl AsRef<str>,294dir_perms: DirPerms,295file_perms: FilePerms,296) -> Result<&mut Self> {297let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?;298let mut open_mode = OpenMode::empty();299if dir_perms.contains(DirPerms::READ) {300open_mode |= OpenMode::READ;301}302if dir_perms.contains(DirPerms::MUTATE) {303open_mode |= OpenMode::WRITE;304}305self.filesystem.preopens.push((306Dir::new(307dir,308dir_perms,309file_perms,310open_mode,311self.filesystem.allow_blocking_current_thread,312),313guest_path.as_ref().to_owned(),314));315Ok(self)316}317318/// Set the generator for the `wasi:random/random` number generator to the319/// custom generator specified.320///321/// Note that contexts have a default RNG configured which is a suitable322/// generator for WASI and is configured with a random seed per-context.323///324/// Guest code may rely on this random number generator to produce fresh325/// unpredictable random data in order to maintain its security invariants,326/// and ideally should use the insecure random API otherwise, so using any327/// prerecorded or otherwise predictable data may compromise security.328pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self {329self.random.random = Box::new(random);330self331}332333/// Configures the generator for `wasi:random/insecure`.334///335/// The `insecure_random` generator provided will be used for all randomness336/// requested by the `wasi:random/insecure` interface.337pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {338self.random.insecure_random = Box::new(insecure_random);339self340}341342/// Configures the seed to be returned from `wasi:random/insecure-seed` to343/// the specified custom value.344///345/// By default this number is randomly generated when a builder is created.346pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {347self.random.insecure_random_seed = insecure_random_seed;348self349}350351/// Configures `wasi:clocks/wall-clock` to use the `clock` specified.352///353/// By default the host's wall clock is used.354pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {355self.clocks.wall_clock = Box::new(clock);356self357}358359/// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified.360///361/// By default the host's monotonic clock is used.362pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {363self.clocks.monotonic_clock = Box::new(clock);364self365}366367/// Allow all network addresses accessible to the host.368///369/// This method will inherit all network addresses meaning that any address370/// can be bound by the guest or connected to by the guest using any371/// protocol.372///373/// See also [`WasiCtxBuilder::socket_addr_check`].374pub fn inherit_network(&mut self) -> &mut Self {375self.socket_addr_check(|_, _| Box::pin(async { true }))376}377378/// A check that will be called for each socket address that is used.379///380/// Returning `true` will permit socket connections to the `SocketAddr`,381/// while returning `false` will reject the connection.382pub fn socket_addr_check<F>(&mut self, check: F) -> &mut Self383where384F: Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>385+ Send386+ Sync387+ 'static,388{389self.sockets.socket_addr_check = SocketAddrCheck::new(check);390self391}392393/// Allow usage of `wasi:sockets/ip-name-lookup`394///395/// By default this is disabled.396pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {397self.sockets.allowed_network_uses.ip_name_lookup = enable;398self399}400401/// Allow usage of UDP.402///403/// This is enabled by default, but can be disabled if UDP should be blanket404/// disabled.405pub fn allow_udp(&mut self, enable: bool) -> &mut Self {406self.sockets.allowed_network_uses.udp = enable;407self408}409410/// Allow usage of TCP411///412/// This is enabled by default, but can be disabled if TCP should be blanket413/// disabled.414pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {415self.sockets.allowed_network_uses.tcp = enable;416self417}418419/// Uses the configured context so far to construct the final [`WasiCtx`].420///421/// Note that each `WasiCtxBuilder` can only be used to "build" once, and422/// calling this method twice will panic.423///424/// # Panics425///426/// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be427/// used to create only a single [`WasiCtx`]. Repeated usage of this method428/// is not allowed and should use a second builder instead.429pub fn build(&mut self) -> WasiCtx {430assert!(!self.built);431432let Self {433cli,434clocks,435filesystem,436random,437sockets,438built: _,439} = mem::replace(self, Self::new());440self.built = true;441442WasiCtx {443cli,444clocks,445filesystem,446random,447sockets,448}449}450/// Builds a WASIp1 context instead of a [`WasiCtx`].451///452/// This method is the same as [`build`](WasiCtxBuilder::build) but it453/// creates a [`WasiP1Ctx`] instead. This is intended for use with the454/// [`p1`] module of this crate455///456/// [`WasiP1Ctx`]: crate::p1::WasiP1Ctx457/// [`p1`]: crate::p1458///459/// # Panics460///461/// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be462/// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated463/// usage of this method is not allowed and should use a second builder464/// instead.465#[cfg(feature = "p1")]466pub fn build_p1(&mut self) -> crate::p1::WasiP1Ctx {467let wasi = self.build();468crate::p1::WasiP1Ctx::new(wasi)469}470}471472/// Per-[`Store`] state which holds state necessary to implement WASI from this473/// crate.474///475/// This structure is created through [`WasiCtxBuilder`] and is stored within476/// the `T` of [`Store<T>`][`Store`]. Access to the structure is provided477/// through the [`WasiView`](crate::WasiView) trait as an implementation on `T`.478///479/// Note that this structure itself does not have any accessors, it's here for480/// internal use within the `wasmtime-wasi` crate's implementation of481/// bindgen-generated traits.482///483/// [`Store`]: wasmtime::Store484///485/// # Example486///487/// ```488/// use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxView, WasiView, WasiCtxBuilder};489///490/// struct MyState {491/// ctx: WasiCtx,492/// table: ResourceTable,493/// }494///495/// impl WasiView for MyState {496/// fn ctx(&mut self) -> WasiCtxView<'_> {497/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }498/// }499/// }500///501/// impl MyState {502/// fn new() -> MyState {503/// let mut wasi = WasiCtxBuilder::new();504/// wasi.arg("./foo.wasm");505/// wasi.arg("--help");506/// wasi.env("FOO", "bar");507///508/// MyState {509/// ctx: wasi.build(),510/// table: ResourceTable::new(),511/// }512/// }513/// }514/// ```515#[derive(Default)]516pub struct WasiCtx {517pub(crate) cli: WasiCliCtx,518pub(crate) clocks: WasiClocksCtx,519pub(crate) filesystem: WasiFilesystemCtx,520pub(crate) random: WasiRandomCtx,521pub(crate) sockets: WasiSocketsCtx,522}523524impl WasiCtx {525/// Convenience function for calling [`WasiCtxBuilder::new`].526pub fn builder() -> WasiCtxBuilder {527WasiCtxBuilder::new()528}529530/// Returns access to the underlying [`WasiRandomCtx`].531pub fn random(&mut self) -> &mut WasiRandomCtx {532&mut self.random533}534535/// Returns access to the underlying [`WasiClocksCtx`].536pub fn clocks(&mut self) -> &mut WasiClocksCtx {537&mut self.clocks538}539540/// Returns access to the underlying [`WasiFilesystemCtx`].541pub fn filesystem(&mut self) -> &mut WasiFilesystemCtx {542&mut self.filesystem543}544545/// Returns access to the underlying [`WasiCliCtx`].546pub fn cli(&mut self) -> &mut WasiCliCtx {547&mut self.cli548}549550/// Returns access to the underlying [`WasiSocketsCtx`].551pub fn sockets(&mut self) -> &mut WasiSocketsCtx {552&mut self.sockets553}554}555556557