wasmtime_wasi/p3/mod.rs
1//! Experimental, unstable and incomplete implementation of wasip3 version of WASI.
2//!
3//! This module is under heavy development.
4//! It is not compliant with semver and is not ready
5//! for production use.
6//!
7//! Bug and security fixes limited to wasip3 will not be given patch releases.
8//!
9//! Documentation of this module may be incorrect or out-of-sync with the implementation.
10
11pub mod bindings;
12pub mod cli;
13pub mod clocks;
14pub mod filesystem;
15pub mod random;
16pub mod sockets;
17
18use crate::p3::bindings::LinkOptions;
19use crate::{NamedId, WasiNamedView, WasiView};
20use core::pin::Pin;
21use core::task::{Context, Poll};
22use tokio::sync::oneshot;
23use wasmtime::StoreContextMut;
24use wasmtime::component::{
25 Component, Destination, Linker, StreamProducer, StreamResult, VecBuffer,
26};
27
28// Default buffer capacity to use for reads of byte-sized values.
29const DEFAULT_BUFFER_CAPACITY: usize = 8192;
30
31/// Helper structure to convert an iterator of `Result<T, E>` into a `stream<T>`
32/// plus a `future<result<_, T>>` in WIT.
33///
34/// This will drain the iterator on calls to `poll_produce` and place as many
35/// items as the input buffer has capacity for into the result. This will avoid
36/// doing anything if the async read is cancelled.
37///
38/// Note that this does not actually do anything async, it's assuming that the
39/// internal `iter` is either fast or intended to block.
40struct FallibleIteratorProducer<I, E> {
41 iter: I,
42 result: Option<oneshot::Sender<Result<(), E>>>,
43}
44
45impl<I, T, E, D> StreamProducer<D> for FallibleIteratorProducer<I, E>
46where
47 I: Iterator<Item = Result<T, E>> + Send + Unpin + 'static,
48 T: Send + Sync + 'static,
49 E: Send + 'static,
50{
51 type Item = T;
52 type Buffer = VecBuffer<T>;
53
54 fn poll_produce<'a>(
55 mut self: Pin<&mut Self>,
56 _: &mut Context<'_>,
57 mut store: StoreContextMut<'a, D>,
58 mut dst: Destination<'a, Self::Item, Self::Buffer>,
59 // Explicitly ignore `_finish` because this implementation never
60 // returns `Poll::Pending` anyway meaning that it never "blocks" in the
61 // async sense.
62 _finish: bool,
63 ) -> Poll<wasmtime::Result<StreamResult>> {
64 // Take up to `count` items as requested by the guest, or pick some
65 // reasonable-ish number for the host.
66 let count = dst.remaining(&mut store).unwrap_or(32);
67
68 // Handle 0-length reads which test for readiness as saying "we're
69 // always ready" since, in theory, this is.
70 if count == 0 {
71 return Poll::Ready(Ok(StreamResult::Completed));
72 }
73
74 // Drain `self.iter`. Successful results go into `buf`. Any errors make
75 // their way to the `oneshot` result inside this structure. Otherwise
76 // this only gets dropped if `None` is seen or an error. Also this'll
77 // terminate once `buf` grows too large.
78 let mut buf = Vec::new();
79 let result = loop {
80 match self.iter.next() {
81 Some(Ok(item)) => buf.push(item),
82 Some(Err(e)) => {
83 self.close(Err(e));
84 break StreamResult::Dropped;
85 }
86
87 None => {
88 self.close(Ok(()));
89 break StreamResult::Dropped;
90 }
91 }
92 if buf.len() >= count {
93 break StreamResult::Completed;
94 }
95 };
96
97 dst.set_buffer(buf.into());
98 return Poll::Ready(Ok(result));
99 }
100}
101
102impl<I, E> FallibleIteratorProducer<I, E> {
103 fn new(iter: I, result: oneshot::Sender<Result<(), E>>) -> Self {
104 Self {
105 iter,
106 result: Some(result),
107 }
108 }
109
110 fn close(&mut self, result: Result<(), E>) {
111 // Ignore send failures because it means the other end wasn't interested
112 // in the final error, if any.
113 let _ = self.result.take().unwrap().send(result);
114 }
115}
116
117impl<I, E> Drop for FallibleIteratorProducer<I, E> {
118 fn drop(&mut self) {
119 if self.result.is_some() {
120 self.close(Ok(()));
121 }
122 }
123}
124
125/// Add all WASI interfaces from this module into the `linker` provided.
126///
127/// This function will add all interfaces implemented by this module to the
128/// [`Linker`], which corresponds to the `wasi:cli/imports` world supported by
129/// this module.
130///
131/// # Example
132///
133/// ```
134/// use wasmtime::{Engine, Result, Store, Config};
135/// use wasmtime::component::{Linker, ResourceTable};
136/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
137///
138/// fn main() -> Result<()> {
139/// let mut config = Config::new();
140/// config.wasm_component_model_async(true);
141/// let engine = Engine::new(&config)?;
142///
143/// let mut linker = Linker::<MyState>::new(&engine);
144/// wasmtime_wasi::p3::add_to_linker(&mut linker)?;
145/// // ... add any further functionality to `linker` if desired ...
146///
147/// let mut store = Store::new(
148/// &engine,
149/// MyState::default(),
150/// );
151///
152/// // ... use `linker` to instantiate within `store` ...
153///
154/// Ok(())
155/// }
156///
157/// #[derive(Default)]
158/// struct MyState {
159/// ctx: WasiCtx,
160/// table: ResourceTable,
161/// }
162///
163/// impl WasiView for MyState {
164/// fn ctx(&mut self) -> WasiCtxView<'_> {
165/// WasiCtxView{
166/// ctx: &mut self.ctx,
167/// table: &mut self.table,
168/// }
169/// }
170/// }
171/// ```
172pub fn add_to_linker<T>(linker: &mut Linker<T>) -> wasmtime::Result<()>
173where
174 T: WasiView + 'static,
175{
176 let options = LinkOptions::default();
177 add_to_linker_with_options(linker, &options)
178}
179
180/// Similar to [`add_to_linker`], but with the ability to enable unstable features.
181pub fn add_to_linker_with_options<T>(
182 linker: &mut Linker<T>,
183 _options: &LinkOptions,
184) -> wasmtime::Result<()>
185where
186 T: WasiView + 'static,
187{
188 cli::add_to_linker(linker)?;
189 clocks::add_to_linker(linker)?;
190 filesystem::add_to_linker(linker)?;
191 random::add_to_linker(linker)?;
192 sockets::add_to_linker(linker)?;
193 Ok(())
194}
195
196/// Interfaces that are added via [`add_named_to_linker`].
197#[derive(Copy, Clone, PartialEq, Eq, Debug)]
198pub enum Interface {
199 /// `wasi:clocks/monotonic-clock`
200 ClocksMonotonicClock,
201 /// `wasi:clocks/system-clock`
202 ClocksSystemClock,
203 /// `wasi:random/random`
204 RandomRandom,
205 /// `wasi:random/insecure`
206 RandomInsecure,
207 /// `wasi:random/insecure-seed`
208 RandomInsecureSeed,
209 /// `wasi:cli/exit`
210 CliExit,
211 /// `wasi:cli/environment`
212 CliEnvironment,
213 /// `wasi:cli/stdin`
214 CliStdin,
215 /// `wasi:cli/stdout`
216 CliStdout,
217 /// `wasi:cli/stderr`
218 CliStderr,
219 /// `wasi:cli/terminal-input`
220 CliTerminalInput,
221 /// `wasi:cli/terminal-output`
222 CliTerminalOutput,
223 /// `wasi:cli/terminal-stdin`
224 CliTerminalStdin,
225 /// `wasi:cli/terminal-stdout`
226 CliTerminalStdout,
227 /// `wasi:cli/terminal-stderr`
228 CliTerminalStderr,
229 /// `wasi:filesystem/types`
230 FilesystemTypes,
231 /// `wasi:filesystem/preopens`
232 FilesystemPreopens,
233 /// `wasi:sockets/types`
234 SocketsTypes,
235 /// `wasi:sockets/ip-name-lookup`
236 SocketsIpNameLookup,
237}
238
239/// Add all WASI interfaces from this module into the `linker` provided for any
240/// named imports that a component has.
241///
242/// This function is similar to [`add_to_linker`] except that it's specifically
243/// designed to work with named imports of WASI interfaces that components may
244/// have. This requires a [`Component`] parameter to be passed in when
245/// populating the [`Linker`] provided to see what the [`Component`] actually
246/// imports.
247///
248/// All interfaces implemented by this module are added here, so the
249/// per-package functions such as [`cli::add_named_to_linker`] do not
250/// additionally need to be invoked. If this isn't low level enough you can
251/// invoke those functions, or the bindgen-generated `add_to_linker` functions
252/// within the [`named_imports`] module, directly instead.
253///
254/// [`named_imports`]: crate::p3::bindings::named_imports
255///
256/// The `lookup` function provided here is invoked for every named import found
257/// for a particular interface. The [`Interface`] given is what's being bound,
258/// and the `&str` argument is the name that the component imports it as. The
259/// embedder can then decide how it would like to allocate a [`NamedId`] for
260/// this import. If `Ok` is returned then the linker is populated with this
261/// name, and imported functions will pass the [`NamedId`] later to the
262/// implementation of [`WasiNamedView`] on `T` when invoked. If `Err` is
263/// returned then the error will cause this entire function to fail and this
264/// function call will return the same error.
265///
266/// # Example
267///
268/// ```
269/// use std::collections::HashMap;
270/// use wasmtime::component::{Component, Linker, ResourceTable};
271/// use wasmtime::{Engine, Result, Store, Config};
272/// use wasmtime_wasi::{NamedId, WasiCtx, WasiCtxView, WasiNamedView};
273///
274/// fn main() -> Result<()> {
275/// let mut config = Config::new();
276/// config.wasm_component_model_async(true);
277/// let engine = Engine::new(&config)?;
278/// let component = Component::new(&engine, "(component)")?;
279///
280/// let mut linker = Linker::<MyState>::new(&engine);
281///
282/// // ... add default functionality to `linker` as needed ...
283///
284/// // and then additionally fill in any specific named imports `component`
285/// // might have for WASI interfaces.
286/// let mut name_map = HashMap::new();
287/// wasmtime_wasi::p3::add_named_to_linker(&mut linker, &component, |_i, name| {
288/// let len = name_map.len();
289/// Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
290/// })?;
291///
292/// // Here a `WasiCtx` is allocated per-named-import and will then be
293/// // referred to internally by the [`NamedId`] allocated above. You could
294/// // also use `name_map` to configure each context differently.
295/// let mut my_state = MyState::default();
296/// for _ in 0..name_map.len() {
297/// my_state.contexts.push(WasiCtx::default());
298/// }
299/// let mut store = Store::new(&engine, my_state);
300///
301/// // ... use `linker` to instantiate within `store` ...
302///
303/// Ok(())
304/// }
305///
306/// #[derive(Default)]
307/// struct MyState {
308/// table: ResourceTable,
309/// contexts: Vec<WasiCtx>,
310/// }
311///
312/// impl WasiNamedView for MyState {
313/// fn ctx(&mut self, id: NamedId) -> WasiCtxView<'_> {
314/// WasiCtxView {
315/// ctx: &mut self.contexts[id.0],
316/// table: &mut self.table,
317/// }
318/// }
319/// }
320/// ```
321pub fn add_named_to_linker<T>(
322 linker: &mut Linker<T>,
323 component: &Component,
324 lookup: impl FnMut(Interface, &str) -> wasmtime::Result<NamedId>,
325) -> wasmtime::Result<()>
326where
327 T: WasiNamedView + 'static,
328{
329 let options = LinkOptions::default();
330 add_named_to_linker_with_options(linker, &options, component, lookup)
331}
332
333/// Same as [`add_named_to_linker`] except [`LinkOptions`] can be specified to
334/// configure interfaces that are added.
335pub fn add_named_to_linker_with_options<T>(
336 linker: &mut Linker<T>,
337 _options: &LinkOptions,
338 component: &Component,
339 mut lookup: impl FnMut(Interface, &str) -> wasmtime::Result<NamedId>,
340) -> wasmtime::Result<()>
341where
342 T: WasiNamedView + 'static,
343{
344 cli::add_named_to_linker(linker, component, &mut lookup)?;
345 clocks::add_named_to_linker(linker, component, &mut lookup)?;
346 filesystem::add_named_to_linker(linker, component, &mut lookup)?;
347 random::add_named_to_linker(linker, component, &mut lookup)?;
348 sockets::add_named_to_linker(linker, component, &mut lookup)?;
349 Ok(())
350}