wasmtime_c_api/
wasi.rs

1//! The WASI embedding API definitions for Wasmtime.
2
3use crate::wasm_byte_vec_t;
4use anyhow::Result;
5use bytes::Bytes;
6use std::ffi::{CStr, c_char, c_void};
7use std::fs::File;
8use std::path::Path;
9use std::pin::Pin;
10use std::slice;
11use std::task::{Context, Poll};
12use tokio::io::{self, AsyncWrite};
13use wasmtime_wasi::WasiCtxBuilder;
14use wasmtime_wasi::p1::WasiP1Ctx;
15use wasmtime_wasi_io::streams::StreamError;
16
17unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
18    CStr::from_ptr(path).to_str().map(Path::new).ok()
19}
20
21unsafe fn cstr_to_str<'a>(s: *const c_char) -> Option<&'a str> {
22    CStr::from_ptr(s).to_str().ok()
23}
24
25unsafe fn open_file(path: *const c_char) -> Option<File> {
26    File::open(cstr_to_path(path)?).ok()
27}
28
29unsafe fn create_file(path: *const c_char) -> Option<File> {
30    File::create(cstr_to_path(path)?).ok()
31}
32
33#[repr(C)]
34pub struct wasi_config_t {
35    builder: WasiCtxBuilder,
36}
37
38wasmtime_c_api_macros::declare_own!(wasi_config_t);
39
40impl wasi_config_t {
41    pub fn into_wasi_ctx(mut self) -> Result<WasiP1Ctx> {
42        Ok(self.builder.build_p1())
43    }
44}
45
46#[unsafe(no_mangle)]
47pub extern "C" fn wasi_config_new() -> Box<wasi_config_t> {
48    Box::new(wasi_config_t {
49        builder: WasiCtxBuilder::new(),
50    })
51}
52
53#[unsafe(no_mangle)]
54pub unsafe extern "C" fn wasi_config_set_argv(
55    config: &mut wasi_config_t,
56    argc: usize,
57    argv: *const *const c_char,
58) -> bool {
59    for arg in slice::from_raw_parts(argv, argc) {
60        let arg = match CStr::from_ptr(*arg).to_str() {
61            Ok(s) => s,
62            Err(_) => return false,
63        };
64        config.builder.arg(arg);
65    }
66    true
67}
68
69#[unsafe(no_mangle)]
70pub extern "C" fn wasi_config_inherit_argv(config: &mut wasi_config_t) {
71    config.builder.inherit_args();
72}
73
74#[unsafe(no_mangle)]
75pub unsafe extern "C" fn wasi_config_set_env(
76    config: &mut wasi_config_t,
77    envc: usize,
78    names: *const *const c_char,
79    values: *const *const c_char,
80) -> bool {
81    let names = slice::from_raw_parts(names, envc);
82    let values = slice::from_raw_parts(values, envc);
83
84    for (k, v) in names.iter().zip(values) {
85        let k = match cstr_to_str(*k) {
86            Some(s) => s,
87            None => return false,
88        };
89        let v = match cstr_to_str(*v) {
90            Some(s) => s,
91            None => return false,
92        };
93        config.builder.env(k, v);
94    }
95    true
96}
97
98#[unsafe(no_mangle)]
99pub extern "C" fn wasi_config_inherit_env(config: &mut wasi_config_t) {
100    config.builder.inherit_env();
101}
102
103#[unsafe(no_mangle)]
104pub unsafe extern "C" fn wasi_config_set_stdin_file(
105    config: &mut wasi_config_t,
106    path: *const c_char,
107) -> bool {
108    let file = match open_file(path) {
109        Some(f) => f,
110        None => return false,
111    };
112
113    let file = tokio::fs::File::from_std(file);
114    let stdin_stream = wasmtime_wasi::cli::AsyncStdinStream::new(file);
115    config.builder.stdin(stdin_stream);
116
117    true
118}
119
120#[unsafe(no_mangle)]
121pub unsafe extern "C" fn wasi_config_set_stdin_bytes(
122    config: &mut wasi_config_t,
123    binary: &mut wasm_byte_vec_t,
124) {
125    let binary = binary.take();
126    let binary = wasmtime_wasi::p2::pipe::MemoryInputPipe::new(binary);
127    config.builder.stdin(binary);
128}
129
130#[unsafe(no_mangle)]
131pub extern "C" fn wasi_config_inherit_stdin(config: &mut wasi_config_t) {
132    config.builder.inherit_stdin();
133}
134
135#[unsafe(no_mangle)]
136pub unsafe extern "C" fn wasi_config_set_stdout_file(
137    config: &mut wasi_config_t,
138    path: *const c_char,
139) -> bool {
140    let file = match create_file(path) {
141        Some(f) => f,
142        None => return false,
143    };
144
145    config
146        .builder
147        .stdout(wasmtime_wasi::cli::OutputFile::new(file));
148
149    true
150}
151
152#[unsafe(no_mangle)]
153pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) {
154    config.builder.inherit_stdout();
155}
156
157struct CustomOutputStreamInner {
158    foreign_data: crate::ForeignData,
159    callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
160}
161
162impl CustomOutputStreamInner {
163    pub fn raw_write(&self, buf: &[u8]) -> io::Result<usize> {
164        let wrote = (self.callback)(self.foreign_data.data, buf.as_ptr(), buf.len());
165
166        if wrote >= 0 {
167            Ok(wrote as _)
168        } else {
169            Err(io::Error::from_raw_os_error(wrote.abs() as _))
170        }
171    }
172}
173
174#[derive(Clone)]
175pub struct CustomOutputStream {
176    inner: std::sync::Arc<CustomOutputStreamInner>,
177}
178
179impl CustomOutputStream {
180    pub fn new(
181        foreign_data: crate::ForeignData,
182        callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
183    ) -> Self {
184        Self {
185            inner: std::sync::Arc::new(CustomOutputStreamInner {
186                foreign_data,
187                callback,
188            }),
189        }
190    }
191}
192
193#[async_trait::async_trait]
194impl wasmtime_wasi::p2::Pollable for CustomOutputStream {
195    async fn ready(&mut self) {}
196}
197
198#[async_trait::async_trait]
199impl wasmtime_wasi::p2::OutputStream for CustomOutputStream {
200    fn write(&mut self, bytes: Bytes) -> Result<(), StreamError> {
201        let wrote = self
202            .inner
203            .raw_write(&bytes)
204            .map_err(|e| StreamError::LastOperationFailed(e.into()))?;
205
206        if wrote != bytes.len() {
207            return Err(StreamError::LastOperationFailed(anyhow::anyhow!(
208                "Partial writes in wasip2 implementation are not allowed"
209            )));
210        }
211
212        Ok(())
213    }
214    fn flush(&mut self) -> Result<(), StreamError> {
215        Ok(())
216    }
217    fn check_write(&mut self) -> Result<usize, StreamError> {
218        Ok(usize::MAX)
219    }
220}
221
222impl AsyncWrite for CustomOutputStream {
223    fn poll_write(
224        self: Pin<&mut Self>,
225        _cx: &mut Context<'_>,
226        buf: &[u8],
227    ) -> Poll<io::Result<usize>> {
228        Poll::Ready(self.inner.raw_write(buf))
229    }
230    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
231        Poll::Ready(Ok(()))
232    }
233    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
234        Poll::Ready(Ok(()))
235    }
236}
237
238impl wasmtime_wasi::cli::IsTerminal for CustomOutputStream {
239    fn is_terminal(&self) -> bool {
240        false
241    }
242}
243
244impl wasmtime_wasi::cli::StdoutStream for CustomOutputStream {
245    fn async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync> {
246        Box::new(self.clone())
247    }
248}
249
250#[unsafe(no_mangle)]
251pub extern "C" fn wasi_config_set_stdout_custom(
252    config: &mut wasi_config_t,
253    callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
254    data: *mut c_void,
255    finalizer: Option<extern "C" fn(*mut c_void)>,
256) {
257    config.builder.stdout(CustomOutputStream::new(
258        crate::ForeignData { data, finalizer },
259        callback,
260    ));
261}
262
263#[unsafe(no_mangle)]
264pub unsafe extern "C" fn wasi_config_set_stderr_file(
265    config: &mut wasi_config_t,
266    path: *const c_char,
267) -> bool {
268    let file = match create_file(path) {
269        Some(f) => f,
270        None => return false,
271    };
272
273    config
274        .builder
275        .stderr(wasmtime_wasi::cli::OutputFile::new(file));
276
277    true
278}
279
280#[unsafe(no_mangle)]
281pub extern "C" fn wasi_config_inherit_stderr(config: &mut wasi_config_t) {
282    config.builder.inherit_stderr();
283}
284
285#[unsafe(no_mangle)]
286pub extern "C" fn wasi_config_set_stderr_custom(
287    config: &mut wasi_config_t,
288    callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
289    data: *mut c_void,
290    finalizer: Option<extern "C" fn(*mut c_void)>,
291) {
292    config.builder.stderr(CustomOutputStream::new(
293        crate::ForeignData { data, finalizer },
294        callback,
295    ));
296}
297
298#[unsafe(no_mangle)]
299pub unsafe extern "C" fn wasi_config_preopen_dir(
300    config: &mut wasi_config_t,
301    path: *const c_char,
302    guest_path: *const c_char,
303    dir_perms: usize,
304    file_perms: usize,
305) -> bool {
306    let guest_path = match cstr_to_str(guest_path) {
307        Some(p) => p,
308        None => return false,
309    };
310
311    let host_path = match cstr_to_path(path) {
312        Some(p) => p,
313        None => return false,
314    };
315
316    let dir_perms = match wasmtime_wasi::DirPerms::from_bits(dir_perms) {
317        Some(p) => p,
318        None => return false,
319    };
320
321    let file_perms = match wasmtime_wasi::FilePerms::from_bits(file_perms) {
322        Some(p) => p,
323        None => return false,
324    };
325
326    config
327        .builder
328        .preopened_dir(host_path, guest_path, dir_perms, file_perms)
329        .is_ok()
330}