Skip to main content

wasmer_c_api/wasm_c_api/wasi/
mod.rs

1//! Unofficial API for WASI integrating with the standard Wasm C API.
2//!
3//! This API will be superseded by a standard WASI API when/if such a standard is created.
4
5pub 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)]
165//pub extern "C" fn wasi_config_capture_stdin(config: &mut wasi_config_t) {
166//    config.inherit_stdin = false;
167//}
168
169#[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    /// cbindgen:ignore
177    pub(super) inner: WasiFunctionEnv,
178    pub(super) store: StoreRef,
179}
180
181/// Create a new WASI environment.
182///
183/// It take ownership over the `wasi_config_t`.
184#[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    // TODO: impl capturer for stdin
215
216    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/// Delete a [`wasi_env_t`].
230#[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/// Set the memory on a [`wasi_env_t`].
239// NOTE: Only here to not break the C API.
240// This was previously supported, but is no longer possible due to WASIX changes.
241// Customizing memories should be done through the builder or the runtime.
242#[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/// The version of WASI. This is determined by the imports namespace
316/// string.
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318#[repr(C)]
319#[allow(non_camel_case_types)]
320pub enum wasi_version_t {
321    /// An invalid version.
322    INVALID_VERSION = -1,
323
324    /// Latest version.
325    ///
326    /// It's a “floating” version, i.e. it's an alias to the latest
327    /// version (for the moment, `Snapshot1`). Using this version is a
328    /// way to ensure that modules will run only if they come with the
329    /// latest WASI version (in case of security issues for instance),
330    /// by just updating the runtime.
331    ///
332    /// Note that this version is never returned by an API. It is
333    /// provided only by the user.
334    LATEST = 0,
335
336    /// `wasi_unstable`.
337    SNAPSHOT0 = 1,
338
339    /// `wasi_snapshot_preview1`.
340    SNAPSHOT1 = 2,
341
342    /// `wasix_32v1`.
343    WASIX32V1 = 3,
344
345    /// `wasix_64v1`.
346    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/// Non-standard function to get the imports needed for the WASI
384/// implementation ordered as expected by the `wasm_module_t`.
385#[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}