1use 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}