wasmer_c_api/wasm_c_api/wasi/
mod.rs1pub use super::unstable::wasi::wasi_get_unordered_imports;
6use super::{
7 externals::{wasm_extern_t, wasm_extern_vec_t, wasm_func_t, wasm_memory_t},
8 instance::wasm_instance_t,
9 module::wasm_module_t,
10 store::{StoreRef, wasm_store_t},
11};
12use crate::error::update_last_error;
13use std::convert::TryFrom;
14use std::ffi::CStr;
15use std::os::raw::c_char;
16use std::slice;
17use std::sync::Arc;
18use wasmer_wasix::{
19 Pipe, PluggableRuntime, WasiEnv, WasiEnvBuilder, WasiFunctionEnv, WasiVersion,
20 default_fs_backing, get_wasi_version,
21 runtime::task_manager::{block_on, tokio::TokioTaskManager},
22 virtual_fs::AsyncReadExt,
23 virtual_fs::VirtualFile,
24};
25
26#[derive(Debug)]
27#[allow(non_camel_case_types)]
28pub struct wasi_config_t {
29 inherit_stdout: bool,
30 inherit_stderr: bool,
31 inherit_stdin: bool,
32 builder: WasiEnvBuilder,
33 runtime: Option<tokio::runtime::Runtime>,
34}
35
36#[unsafe(no_mangle)]
37pub unsafe extern "C" fn wasi_config_new(
38 program_name: *const c_char,
39) -> Option<Box<wasi_config_t>> {
40 debug_assert!(!program_name.is_null());
41
42 let name_c_str = unsafe { CStr::from_ptr(program_name) };
43 let prog_name = c_try!(name_c_str.to_str());
44
45 let runtime = tokio::runtime::Builder::new_multi_thread()
46 .enable_all()
47 .build()
48 .unwrap();
49 let _guard = runtime.enter();
50
51 Some(Box::new(wasi_config_t {
52 inherit_stdout: true,
53 inherit_stderr: true,
54 inherit_stdin: true,
55 builder: WasiEnv::builder(prog_name).fs(default_fs_backing()),
56 runtime: Some(runtime),
57 }))
58}
59
60#[unsafe(no_mangle)]
61pub unsafe extern "C" fn wasi_config_env(
62 config: &mut wasi_config_t,
63 key: *const c_char,
64 value: *const c_char,
65) {
66 debug_assert!(!key.is_null());
67 debug_assert!(!value.is_null());
68
69 let key_cstr = unsafe { CStr::from_ptr(key) };
70 let key_bytes = key_cstr.to_bytes();
71 let value_cstr = unsafe { CStr::from_ptr(value) };
72 let value_bytes = value_cstr.to_bytes();
73
74 config.builder.add_env(key_bytes, value_bytes);
75}
76
77#[unsafe(no_mangle)]
78pub unsafe extern "C" fn wasi_config_arg(config: &mut wasi_config_t, arg: *const c_char) {
79 debug_assert!(!arg.is_null());
80
81 let arg_cstr = unsafe { CStr::from_ptr(arg) };
82 let arg_bytes = arg_cstr.to_bytes();
83
84 config.builder.add_arg(arg_bytes);
85}
86
87#[unsafe(no_mangle)]
88pub unsafe extern "C" fn wasi_config_preopen_dir(
89 config: &mut wasi_config_t,
90 dir: *const c_char,
91) -> bool {
92 let dir_cstr = unsafe { CStr::from_ptr(dir) };
93 let dir_bytes = dir_cstr.to_bytes();
94 let dir_str = match std::str::from_utf8(dir_bytes) {
95 Ok(dir_str) => dir_str,
96 Err(e) => {
97 update_last_error(e);
98 return false;
99 }
100 };
101
102 if let Err(e) = config.builder.add_preopen_dir(dir_str) {
103 update_last_error(e);
104 return false;
105 }
106
107 true
108}
109
110#[unsafe(no_mangle)]
111pub unsafe extern "C" fn wasi_config_mapdir(
112 config: &mut wasi_config_t,
113 alias: *const c_char,
114 dir: *const c_char,
115) -> bool {
116 let alias_cstr = unsafe { CStr::from_ptr(alias) };
117 let alias_bytes = alias_cstr.to_bytes();
118 let alias_str = match std::str::from_utf8(alias_bytes) {
119 Ok(alias_str) => alias_str,
120 Err(e) => {
121 update_last_error(e);
122 return false;
123 }
124 };
125
126 let dir_cstr = unsafe { CStr::from_ptr(dir) };
127 let dir_bytes = dir_cstr.to_bytes();
128 let dir_str = match std::str::from_utf8(dir_bytes) {
129 Ok(dir_str) => dir_str,
130 Err(e) => {
131 update_last_error(e);
132 return false;
133 }
134 };
135
136 if let Err(e) = config.builder.add_map_dir(alias_str, dir_str) {
137 update_last_error(e);
138 return false;
139 }
140
141 true
142}
143
144#[unsafe(no_mangle)]
145pub extern "C" fn wasi_config_capture_stdout(config: &mut wasi_config_t) {
146 config.inherit_stdout = false;
147}
148
149#[unsafe(no_mangle)]
150pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) {
151 config.inherit_stdout = true;
152}
153
154#[unsafe(no_mangle)]
155pub extern "C" fn wasi_config_capture_stderr(config: &mut wasi_config_t) {
156 config.inherit_stderr = false;
157}
158
159#[unsafe(no_mangle)]
160pub extern "C" fn wasi_config_inherit_stderr(config: &mut wasi_config_t) {
161 config.inherit_stderr = true;
162}
163
164#[unsafe(no_mangle)]
170pub extern "C" fn wasi_config_inherit_stdin(config: &mut wasi_config_t) {
171 config.inherit_stdin = true;
172}
173
174#[allow(non_camel_case_types)]
175pub struct wasi_env_t {
176 pub(super) inner: WasiFunctionEnv,
178 pub(super) store: StoreRef,
179}
180
181#[unsafe(no_mangle)]
185pub unsafe extern "C" fn wasi_env_new(
186 store: Option<&mut wasm_store_t>,
187 mut config: Box<wasi_config_t>,
188) -> Option<Box<wasi_env_t>> {
189 let store = &mut store?.inner;
190 let mut store_mut = unsafe { store.store_mut() };
191
192 let runtime = config.runtime.take();
193
194 let runtime = runtime.unwrap_or_else(|| {
195 tokio::runtime::Builder::new_multi_thread()
196 .enable_all()
197 .build()
198 .unwrap()
199 });
200
201 let handle = runtime.handle().clone();
202 let _guard = handle.enter();
203 let mut rt = PluggableRuntime::new(Arc::new(TokioTaskManager::new(runtime)));
204 rt.set_engine(store_mut.engine().clone());
205
206 if !config.inherit_stdout {
207 config.builder.set_stdout(Box::new(Pipe::channel().0));
208 }
209
210 if !config.inherit_stderr {
211 config.builder.set_stderr(Box::new(Pipe::channel().0));
212 }
213
214 let env = c_try!(
217 config
218 .builder
219 .runtime(Arc::new(rt))
220 .finalize(&mut store_mut)
221 );
222
223 Some(Box::new(wasi_env_t {
224 inner: env,
225 store: store.clone(),
226 }))
227}
228
229#[unsafe(no_mangle)]
231pub extern "C" fn wasi_env_delete(state: Option<Box<wasi_env_t>>) {
232 if let Some(mut env) = state {
233 let mut store = unsafe { env.store.store_mut() };
234 env.inner.on_exit(&mut store, None);
235 }
236}
237
238#[unsafe(no_mangle)]
243#[deprecated(since = "4.0.0")]
244pub unsafe extern "C" fn wasi_env_set_memory(_env: &mut wasi_env_t, _memory: &wasm_memory_t) {
245 panic!("wasmer_env_set_memory() is not supported");
246}
247
248#[unsafe(no_mangle)]
249pub unsafe extern "C" fn wasi_env_read_stdout(
250 env: &mut wasi_env_t,
251 buffer: *mut c_char,
252 buffer_len: usize,
253) -> isize {
254 let inner_buffer = unsafe { slice::from_raw_parts_mut(buffer as *mut _, buffer_len) };
255 let store = unsafe { env.store.store() };
256
257 let stdout = {
258 let data = env.inner.data(&store);
259 data.stdout()
260 };
261
262 if let Ok(mut stdout) = stdout {
263 if let Some(stdout) = stdout.as_mut() {
264 read_inner(stdout, inner_buffer)
265 } else {
266 update_last_error("could not find a file handle for `stdout`");
267 -1
268 }
269 } else {
270 update_last_error("could not find a file handle for `stdout`");
271 -1
272 }
273}
274
275#[unsafe(no_mangle)]
276pub unsafe extern "C" fn wasi_env_read_stderr(
277 env: &mut wasi_env_t,
278 buffer: *mut c_char,
279 buffer_len: usize,
280) -> isize {
281 let inner_buffer = unsafe { slice::from_raw_parts_mut(buffer as *mut _, buffer_len) };
282 let store = unsafe { env.store.store() };
283 let stderr = {
284 let data = env.inner.data(&store);
285 data.stderr()
286 };
287 if let Ok(mut stderr) = stderr {
288 if let Some(stderr) = stderr.as_mut() {
289 read_inner(stderr, inner_buffer)
290 } else {
291 update_last_error("could not find a file handle for `stderr`");
292 -1
293 }
294 } else {
295 update_last_error("could not find a file handle for `stderr`");
296 -1
297 }
298}
299
300fn read_inner(
301 wasi_file: &mut Box<dyn VirtualFile + Send + Sync + 'static>,
302 inner_buffer: &mut [u8],
303) -> isize {
304 block_on(async {
305 match wasi_file.read(inner_buffer).await {
306 Ok(a) => a as isize,
307 Err(err) => {
308 update_last_error(format!("failed to read wasi_file: {err}"));
309 -1
310 }
311 }
312 })
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318#[repr(C)]
319#[allow(non_camel_case_types)]
320pub enum wasi_version_t {
321 INVALID_VERSION = -1,
323
324 LATEST = 0,
335
336 SNAPSHOT0 = 1,
338
339 SNAPSHOT1 = 2,
341
342 WASIX32V1 = 3,
344
345 WASIX64V1 = 4,
347}
348
349impl From<WasiVersion> for wasi_version_t {
350 fn from(other: WasiVersion) -> Self {
351 match other {
352 WasiVersion::Snapshot0 => wasi_version_t::SNAPSHOT0,
353 WasiVersion::Snapshot1 => wasi_version_t::SNAPSHOT1,
354 WasiVersion::Wasix32v1 => wasi_version_t::WASIX32V1,
355 WasiVersion::Wasix64v1 => wasi_version_t::WASIX64V1,
356 WasiVersion::Latest => wasi_version_t::LATEST,
357 }
358 }
359}
360
361impl TryFrom<wasi_version_t> for WasiVersion {
362 type Error = &'static str;
363
364 fn try_from(other: wasi_version_t) -> Result<Self, Self::Error> {
365 Ok(match other {
366 wasi_version_t::INVALID_VERSION => return Err("Invalid WASI version cannot be used"),
367 wasi_version_t::SNAPSHOT0 => WasiVersion::Snapshot0,
368 wasi_version_t::SNAPSHOT1 => WasiVersion::Snapshot1,
369 wasi_version_t::WASIX32V1 => WasiVersion::Wasix32v1,
370 wasi_version_t::WASIX64V1 => WasiVersion::Wasix64v1,
371 wasi_version_t::LATEST => WasiVersion::Latest,
372 })
373 }
374}
375
376#[unsafe(no_mangle)]
377pub unsafe extern "C" fn wasi_get_wasi_version(module: &wasm_module_t) -> wasi_version_t {
378 get_wasi_version(&module.inner, false)
379 .map(Into::into)
380 .unwrap_or(wasi_version_t::INVALID_VERSION)
381}
382
383#[unsafe(no_mangle)]
386pub unsafe extern "C" fn wasi_get_imports(
387 _store: Option<&wasm_store_t>,
388 wasi_env: Option<&mut wasi_env_t>,
389 module: Option<&wasm_module_t>,
390 imports: &mut wasm_extern_vec_t,
391) -> bool {
392 unsafe { wasi_get_imports_inner(wasi_env, module, imports) }.is_some()
393}
394
395unsafe fn wasi_get_imports_inner(
396 wasi_env: Option<&mut wasi_env_t>,
397 module: Option<&wasm_module_t>,
398 imports: &mut wasm_extern_vec_t,
399) -> Option<()> {
400 let wasi_env = wasi_env?;
401 let store = &mut wasi_env.store;
402 let module = module?;
403
404 let mut import_object = {
405 let mut store_mut = unsafe { store.store_mut() };
406 c_try!(wasi_env.inner.import_object(&mut store_mut, &module.inner))
407 };
408
409 let shared_memory = module.inner.imports().memories().next().map(|a| *a.ty());
410
411 let spawn_type = match shared_memory {
412 Some(ty) => wasmer_wasix::runtime::SpawnType::CreateMemoryOfType(ty),
413 None => wasmer_wasix::runtime::SpawnType::CreateMemory,
414 };
415
416 let tasks = {
417 let store_ref = unsafe { store.store() };
418 wasi_env
419 .inner
420 .data(&store_ref)
421 .runtime
422 .task_manager()
423 .clone()
424 };
425 let memory = {
426 let mut store_mut = unsafe { store.store_mut() };
427 tasks.build_memory(&mut store_mut, spawn_type).unwrap()
428 };
429
430 if let Some(memory) = memory {
431 import_object.define("env", "memory", memory);
432 }
433
434 imports_set_buffer(store, &module.inner, import_object, imports)?;
435
436 Some(())
437}
438
439pub(crate) fn imports_set_buffer(
440 store: &StoreRef,
441 module: &wasmer_api::Module,
442 import_object: wasmer_api::Imports,
443 imports: &mut wasm_extern_vec_t,
444) -> Option<()> {
445 imports.set_buffer(c_try!(
446 module
447 .imports()
448 .map(|import_type| {
449 let ext = import_object
450 .get_export(import_type.module(), import_type.name())
451 .ok_or_else(|| {
452 format!(
453 "Failed to resolve import \"{}\" \"{}\"",
454 import_type.module(),
455 import_type.name()
456 )
457 })?;
458
459 Ok(Some(Box::new(wasm_extern_t::new(store.clone(), ext))))
460 })
461 .collect::<Result<Vec<_>, String>>()
462 ));
463
464 Some(())
465}
466
467#[unsafe(no_mangle)]
468pub unsafe extern "C" fn wasi_env_initialize_instance(
469 wasi_env: &mut wasi_env_t,
470 store: &mut wasm_store_t,
471 instance: &mut wasm_instance_t,
472) -> bool {
473 let mut store_mut = unsafe { store.inner.store_mut() };
474 wasi_env
475 .inner
476 .initialize(&mut store_mut, instance.inner.clone())
477 .unwrap();
478 true
479}
480
481#[unsafe(no_mangle)]
482pub unsafe extern "C" fn wasi_get_start_function(
483 instance: &mut wasm_instance_t,
484) -> Option<Box<wasm_func_t>> {
485 let start = c_try!(instance.inner.exports.get_function("_start"));
486
487 Some(Box::new(wasm_func_t {
488 extern_: wasm_extern_t::new(instance.store.clone(), start.clone().into()),
489 }))
490}
491
492#[cfg(test)]
493mod tests {
494 use inline_c::assert_c;
495
496 #[allow(
497 unexpected_cfgs,
498 reason = "tools like cargo-llvm-coverage pass --cfg coverage"
499 )]
500 #[cfg_attr(coverage_nightly, coverage(off))]
501 #[test]
502 fn test_wasi_get_wasi_version_snapshot0() {
503 (assert_c! {
504 #include "tests/wasmer.h"
505
506 int main() {
507 wasm_engine_t* engine = wasm_engine_new();
508 wasm_store_t* store = wasm_store_new(engine);
509 wasmer_funcenv_t* env = wasmer_funcenv_new(store, 0);
510
511 wasm_byte_vec_t wat;
512 wasmer_byte_vec_new_from_string(&wat, "(module (import \"wasi_unstable\" \"args_get\" (func (param i32 i32) (result i32))))");
513 wasm_byte_vec_t wasm;
514 wat2wasm(&wat, &wasm);
515
516 wasm_module_t* module = wasm_module_new(store, &wasm);
517 assert(module);
518
519 assert(wasi_get_wasi_version(module) == SNAPSHOT0);
520
521 wasm_module_delete(module);
522 wasm_byte_vec_delete(&wasm);
523 wasm_byte_vec_delete(&wat);
524 wasmer_funcenv_delete(env);
525 wasm_store_delete(store);
526 wasm_engine_delete(engine);
527
528 return 0;
529 }
530 })
531 .success();
532 }
533
534 #[allow(
535 unexpected_cfgs,
536 reason = "tools like cargo-llvm-coverage pass --cfg coverage"
537 )]
538 #[cfg_attr(coverage_nightly, coverage(off))]
539 #[test]
540 fn test_wasi_get_wasi_version_snapshot1() {
541 (assert_c! {
542 #include "tests/wasmer.h"
543
544 int main() {
545 wasm_engine_t* engine = wasm_engine_new();
546 wasm_store_t* store = wasm_store_new(engine);
547 wasmer_funcenv_t* env = wasmer_funcenv_new(store, 0);
548
549 wasm_byte_vec_t wat;
550 wasmer_byte_vec_new_from_string(&wat, "(module (import \"wasi_snapshot_preview1\" \"args_get\" (func (param i32 i32) (result i32))))");
551 wasm_byte_vec_t wasm;
552 wat2wasm(&wat, &wasm);
553
554 wasm_module_t* module = wasm_module_new(store, &wasm);
555 assert(module);
556
557 assert(wasi_get_wasi_version(module) == SNAPSHOT1);
558
559 wasm_module_delete(module);
560 wasm_byte_vec_delete(&wasm);
561 wasm_byte_vec_delete(&wat);
562 wasmer_funcenv_delete(env);
563 wasm_store_delete(store);
564 wasm_engine_delete(engine);
565
566 return 0;
567 }
568 })
569 .success();
570 }
571
572 #[allow(
573 unexpected_cfgs,
574 reason = "tools like cargo-llvm-coverage pass --cfg coverage"
575 )]
576 #[cfg_attr(coverage_nightly, coverage(off))]
577 #[test]
578 fn test_wasi_get_wasi_version_invalid() {
579 (assert_c! {
580 #include "tests/wasmer.h"
581
582 int main() {
583 wasm_engine_t* engine = wasm_engine_new();
584 wasm_store_t* store = wasm_store_new(engine);
585 wasmer_funcenv_t* env = wasmer_funcenv_new(store, 0);
586
587 wasm_byte_vec_t wat;
588 wasmer_byte_vec_new_from_string(&wat, "(module (import \"wasi_snpsht_prvw1\" \"args_get\" (func (param i32 i32) (result i32))))");
589 wasm_byte_vec_t wasm;
590 wat2wasm(&wat, &wasm);
591
592 wasm_module_t* module = wasm_module_new(store, &wasm);
593 assert(module);
594
595 assert(wasi_get_wasi_version(module) == INVALID_VERSION);
596
597 wasm_module_delete(module);
598 wasm_byte_vec_delete(&wasm);
599 wasm_byte_vec_delete(&wat);
600 wasmer_funcenv_delete(env);
601 wasm_store_delete(store);
602 wasm_engine_delete(engine);
603
604 return 0;
605 }
606 })
607 .success();
608 }
609}