Skip to main content

wasmtime_c_api/
wasi.rs

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