1#![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
47pub 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
73pub 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
93pub 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
107pub 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#[must_use = "WasiProcess does nothing without being polled or spawned. Try calling `.spawn()`"]
123pub struct WasiProcess {
124 pub stdin: Option<WasiStdin>,
126 pub stdout: Option<WasiStdout>,
128 pub stderr: Option<WasiStderr>,
130 handle: Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + Sync>>,
131}
132
133#[derive(Debug, Copy, Clone)]
135pub struct MaxBufSize {
136 pub stdin: usize,
138 pub stdout: usize,
140 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 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 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 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#[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#[derive(Debug)]
225pub enum SpawnError {
226 Wasi(RuntimeError),
228 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 {}