wasi_process/
lib.rs

1//! A library to run wasi modules as pseudo-processes.
2//!
3//! ```
4//! # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
5//! # use tokio::io::AsyncReadExt;
6//! use wasmer_wasi::{WasiEnv, WasiState, WasiVersion};
7//! use wasi_process::WasiProcess;
8//! let store = wasmer::Store::default();
9//! let wasm = include_bytes!("../helloworld.wasm"); // just write(1, "Hello, World!\n", 14)
10//! let module = wasmer::Module::new(&store, wasm)?;
11//! let mut state = WasiState::new("progg");
12//! wasi_process::add_stdio(&mut state);
13//! state.args(&["foo", "bar"]);
14//! let imports = wasmer_wasi::generate_import_object_from_env(
15//!     &store,
16//!     WasiEnv::new(state.build()?),
17//!     wasmer_wasi::get_wasi_version(&module, false).unwrap_or(WasiVersion::Latest),
18//! );
19//! let instance = wasmer::Instance::new(&module, &imports)?;
20//! let mut wasi = WasiProcess::new(&instance, wasi_process::MaxBufSize::default())?;
21//! let mut stdout = wasi.stdout.take().unwrap();
22//! wasi.spawn();
23//! let mut out = String::new();
24//! stdout.read_to_string(&mut out).await?;
25//! assert_eq!(out, "Hello, World!\n");
26//! # Ok(())
27//! # }
28//! ```
29#![deny(missing_docs)]
30
31use std::fmt;
32use std::future::Future;
33use std::pin::Pin;
34use std::task::{Context, Poll};
35use tokio::io::{AsyncRead, AsyncWrite};
36use tokio::{io, task};
37use wasmer::RuntimeError;
38use wasmer_wasi::WasiStateBuilder;
39
40mod pipe;
41mod stdio;
42
43pub use stdio::{Stderr, Stdin, Stdout};
44
45use pipe::LockPipe;
46
47/// Use the wasi-process stdio pseudo-files for a wasi environment.
48///
49/// # Examples
50/// ```
51/// # fn main() -> Result<(), wasmer_wasi::WasiStateCreationError> {
52/// use wasmer_wasi::WasiState;
53/// let mut state = WasiState::new("programname");
54/// wasi_process::add_stdio(&mut state);
55/// let state = state.arg("foo").build()?;
56/// # let _ = state;
57/// # Ok(())
58/// # }
59/// ```
60pub fn add_stdio(state: &mut WasiStateBuilder) -> &mut WasiStateBuilder {
61    state
62        .stdin(Box::new(stdio::Stdin))
63        .stdout(Box::new(stdio::Stdout))
64        .stderr(Box::new(stdio::Stderr))
65}
66
67tokio::task_local! {
68    static STDIN: LockPipe;
69    static STDOUT: LockPipe;
70    static STDERR: LockPipe;
71}
72
73/// An AsyncWrite type representing a wasi stdin stream.
74pub struct WasiStdin {
75    inner: LockPipe,
76}
77
78impl AsyncWrite for WasiStdin {
79    #[inline]
80    fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
81        Pin::new(&mut &self.inner).poll_write(cx, buf)
82    }
83    #[inline]
84    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
85        Pin::new(&mut &self.inner).poll_flush(cx)
86    }
87    #[inline]
88    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
89        Pin::new(&mut &self.inner).poll_shutdown(cx)
90    }
91}
92
93/// An AsyncRead type representing a wasi stdout stream.
94pub struct WasiStdout {
95    inner: LockPipe,
96}
97impl AsyncRead for WasiStdout {
98    fn poll_read(
99        self: Pin<&mut Self>,
100        cx: &mut Context<'_>,
101        buf: &mut io::ReadBuf,
102    ) -> Poll<io::Result<()>> {
103        Pin::new(&mut &self.inner).poll_read(cx, buf)
104    }
105}
106
107/// An AsyncRead type representing a wasi stderr stream.
108pub struct WasiStderr {
109    inner: LockPipe,
110}
111impl AsyncRead for WasiStderr {
112    fn poll_read(
113        self: Pin<&mut Self>,
114        cx: &mut Context<'_>,
115        buf: &mut io::ReadBuf,
116    ) -> Poll<io::Result<()>> {
117        Pin::new(&mut &self.inner).poll_read(cx, buf)
118    }
119}
120
121/// A wasi process. See crate documentation for more details and examples.
122#[must_use = "WasiProcess does nothing without being polled or spawned. Try calling `.spawn()`"]
123pub struct WasiProcess {
124    /// An stdin reader for the wasi process
125    pub stdin: Option<WasiStdin>,
126    /// An stdout writer for the wasi process
127    pub stdout: Option<WasiStdout>,
128    /// An stderr writer for the wasi process
129    pub stderr: Option<WasiStderr>,
130    handle: Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + Sync>>,
131}
132
133/// A struct to configure the sizes of the internal buffers used for stdio.
134#[derive(Debug, Copy, Clone)]
135pub struct MaxBufSize {
136    /// The maximum size of the internal buffer for stdin
137    pub stdin: usize,
138    /// The maximum size of the internal buffer for stdout
139    pub stdout: usize,
140    /// The maximum size of the internal buffer for stderr
141    pub stderr: usize,
142}
143
144const DEFAULT_BUF_SIZE: usize = 1024;
145
146impl Default for MaxBufSize {
147    fn default() -> Self {
148        MaxBufSize {
149            stdin: DEFAULT_BUF_SIZE,
150            stdout: DEFAULT_BUF_SIZE,
151            stderr: DEFAULT_BUF_SIZE,
152        }
153    }
154}
155
156impl WasiProcess {
157    /// Create a WasiProcess from a wasm instance. See the crate documentation for more details.
158    /// Returns an error if the instance doesn't have a `_start` function exported.
159    pub fn new(
160        instance: &wasmer::Instance,
161        buf_size: MaxBufSize,
162    ) -> Result<Self, wasmer::ExportError> {
163        let start = instance.exports.get_function("_start")?.clone();
164        Ok(Self::with_function(start, buf_size))
165    }
166
167    /// Create a WasiProcess from a wasm instance, given a `_start` function. See the crate
168    /// documentation for more details.
169    pub fn with_function(start_function: wasmer::Function, buf_size: MaxBufSize) -> Self {
170        let stdin = LockPipe::new(buf_size.stdin);
171        let stdout = LockPipe::new(buf_size.stdout);
172        let stderr = LockPipe::new(buf_size.stderr);
173        let handle = STDIN.scope(
174            stdin.clone(),
175            STDOUT.scope(
176                stdout.clone(),
177                STDERR.scope(stderr.clone(), async move {
178                    task::block_in_place(|| start_function.call(&[]).map(drop))
179                }),
180            ),
181        );
182
183        Self {
184            stdin: Some(WasiStdin { inner: stdin }),
185            stdout: Some(WasiStdout { inner: stdout }),
186            stderr: Some(WasiStderr { inner: stderr }),
187            handle: Box::pin(handle),
188        }
189    }
190
191    /// Spawn the process on a tokio task. It's okay to let this drop; that just means that you
192    /// don't care about exactly when or how the process finishes, and you'll know you're done when
193    /// an stdio stream closes;
194    pub fn spawn(self) -> SpawnHandle {
195        let inner = tokio::spawn(self);
196        SpawnHandle { inner }
197    }
198}
199
200impl Future for WasiProcess {
201    type Output = Result<(), RuntimeError>;
202    fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
203        self.handle.as_mut().poll(cx)
204    }
205}
206
207/// A handle to a spawned a wasi process.
208#[derive(Debug)]
209pub struct SpawnHandle {
210    inner: tokio::task::JoinHandle<<WasiProcess as Future>::Output>,
211}
212
213impl Future for SpawnHandle {
214    type Output = Result<(), SpawnError>;
215    fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
216        Pin::new(&mut self.inner)
217            .poll(cx)
218            .map(|res| res.map_err(SpawnError::Join)?.map_err(SpawnError::Wasi))
219    }
220}
221
222/// An error returned from a spawned process. Either an error from tokio's `task::spawn`, such as a
223/// panic or cancellation, or a wasm/wasi error, like an `_exit()` call or an unreachable.
224#[derive(Debug)]
225pub enum SpawnError {
226    /// An error received from wasmer
227    Wasi(RuntimeError),
228    /// An error from `tokio::task::spawn`
229    Join(tokio::task::JoinError),
230}
231
232impl fmt::Display for SpawnError {
233    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234        match self {
235            Self::Wasi(w) => write!(f, "runtime wasi/wasm error: {}", w),
236            Self::Join(j) => write!(f, "error while joining the tokio task: {}", j),
237        }
238    }
239}
240
241impl std::error::Error for SpawnError {}