From b45b1c12f14b05af43a8618049283e61a2d36796 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 1 Oct 2019 12:08:45 -0700 Subject: [PATCH] Add WASI support to runtime-c-api --- Cargo.lock | 1 + lib/runtime-c-api/Cargo.toml | 9 ++- lib/runtime-c-api/src/import.rs | 104 ++++++++++++++++++++++++++++++++ lib/runtime-c-api/src/lib.rs | 20 ++++++ lib/runtime-c-api/wasmer.h | 36 +++++++++++ lib/runtime-c-api/wasmer.hh | 26 ++++++++ 6 files changed, 195 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index fb642644e59..c104006bdf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1639,6 +1639,7 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime 0.7.0", "wasmer-runtime-core 0.7.0", + "wasmer-wasi 0.7.0", ] [[package]] diff --git a/lib/runtime-c-api/Cargo.toml b/lib/runtime-c-api/Cargo.toml index bcfa4bec5d1..75d421414b5 100644 --- a/lib/runtime-c-api/Cargo.toml +++ b/lib/runtime-c-api/Cargo.toml @@ -24,12 +24,19 @@ default-features = false path = "../runtime-core" version = "0.7.0" +[dependencies.wasmer-wasi] +default-features = false +path = "../wasi" +version = "0.7.0" +optional = true + [features] -default = ["cranelift-backend"] +default = ["cranelift-backend", "wasi"] debug = ["wasmer-runtime/debug"] cranelift-backend = ["wasmer-runtime/cranelift", "wasmer-runtime/default-backend-cranelift"] llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm"] singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass"] +wasi = ["wasmer-wasi"] [build-dependencies] cbindgen = "0.9" diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import.rs index 2d6f9e99d31..abd35c9d075 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import.rs @@ -51,6 +51,110 @@ pub unsafe extern "C" fn wasmer_import_object_new() -> *mut wasmer_import_object Box::into_raw(import_object) as *mut wasmer_import_object_t } +#[cfg(feature = "wasi")] +mod wasi { + use super::*; + use std::path::PathBuf; + + /// Opens a directory that's visible to the WASI module as `alias` but + /// is backed by the host file at `host_file_path` + #[repr(C)] + pub struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + pub alias: wasmer_byte_array, + /// The backing file that the WASI module will interact with via the alias + pub host_file_path: wasmer_byte_array, + } + + impl wasmer_wasi_map_dir_entry_t { + /// Converts the data into owned, Rust types + pub unsafe fn as_tuple(&self) -> Result<(String, PathBuf), std::str::Utf8Error> { + let alias = self.alias.as_str()?.to_owned(); + let host_path = std::path::PathBuf::from(self.host_file_path.as_str()?); + + Ok((alias, host_path)) + } + } + + /// Creates a WASI import object + #[no_mangle] + pub unsafe extern "C" fn wasmer_wasi_generate_import_object( + args: *const wasmer_byte_array, + args_len: c_uint, + envs: *const wasmer_byte_array, + envs_len: c_uint, + preopened_files: *const wasmer_byte_array, + preopened_files_len: c_uint, + mapped_dirs: *const wasmer_wasi_map_dir_entry_t, + mapped_dirs_len: c_uint, + ) -> *mut wasmer_import_object_t { + let arg_list = std::slice::from_raw_parts(args, args_len as usize); + let env_list = std::slice::from_raw_parts(envs, envs_len as usize); + let preopened_file_list = + std::slice::from_raw_parts(preopened_files, preopened_files_len as usize); + let mapped_dir_list = std::slice::from_raw_parts(mapped_dirs, mapped_dirs_len as usize); + + wasmer_wasi_generate_import_object_inner( + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(std::ptr::null_mut()) + } + + /// Inner function that wraps error handling + fn wasmer_wasi_generate_import_object_inner( + arg_list: &[wasmer_byte_array], + env_list: &[wasmer_byte_array], + preopened_file_list: &[wasmer_byte_array], + mapped_dir_list: &[wasmer_wasi_map_dir_entry_t], + ) -> Result<*mut wasmer_import_object_t, std::str::Utf8Error> { + let arg_vec = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); + let env_vec = env_list + .iter() + .map(|env_var| unsafe { env_var.as_vec() }) + .collect(); + let po_file_vec = preopened_file_list + .iter() + .map(|po_file| Ok(unsafe { po_file.as_str()? }.to_owned())) + .collect::, _>>()?; + let mapped_dir_vec = mapped_dir_list + .iter() + .map(|entry| unsafe { entry.as_tuple() }) + .collect::, _>>()?; + + let import_object = Box::new(wasmer_wasi::generate_import_object( + arg_vec, + env_vec, + po_file_vec, + mapped_dir_vec, + )); + Ok(Box::into_raw(import_object) as *mut wasmer_import_object_t) + } + + /// Convenience function that creates a WASI import object with no arguments, + /// environment variables, preopened files, or mapped directories. + /// + /// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all + /// empty values. + #[no_mangle] + pub unsafe extern "C" fn wasmer_wasi_generate_default_import_object( + ) -> *mut wasmer_import_object_t { + let import_object = Box::new(wasmer_wasi::generate_import_object( + vec![], + vec![], + vec![], + vec![], + )); + + Box::into_raw(import_object) as *mut wasmer_import_object_t + } +} + +#[cfg(feature = "wasi")] +pub use self::wasi::*; + /// Extends an existing import object with new imports #[allow(clippy::cast_ptr_alignment)] #[no_mangle] diff --git a/lib/runtime-c-api/src/lib.rs b/lib/runtime-c-api/src/lib.rs index 4dae18cd590..a2433afa20a 100644 --- a/lib/runtime-c-api/src/lib.rs +++ b/lib/runtime-c-api/src/lib.rs @@ -129,3 +129,23 @@ pub struct wasmer_byte_array { pub bytes: *const u8, pub bytes_len: u32, } + +impl wasmer_byte_array { + /// Get the data as a slice + pub unsafe fn as_slice<'a>(&self) -> &'a [u8] { + std::slice::from_raw_parts(self.bytes, self.bytes_len as usize) + } + + /// Copy the data into an owned Vec + pub unsafe fn as_vec(&self) -> Vec { + let mut out = Vec::with_capacity(self.bytes_len as usize); + out.extend_from_slice(self.as_slice()); + + out + } + + /// Read the data as a &str, returns an error if the string is not valid UTF8 + pub unsafe fn as_str<'a>(&self) -> Result<&'a str, std::str::Utf8Error> { + std::str::from_utf8(self.as_slice()) + } +} diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 447d3107597..a13a40c92a9 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -170,6 +170,21 @@ typedef struct { } wasmer_trampoline_buffer_t; +/** + * Opens a directory that's visible to the WASI module as `alias` but + * is backed by the host file at `host_file_path` + */ +typedef struct { + /** + * What the WASI module will see in its virtual root + */ + wasmer_byte_array alias; + /** + * The backing file that the WASI module will interact with via the alias + */ + wasmer_byte_array host_file_path; +} wasmer_wasi_map_dir_entry_t; + /** * Creates a new Module from the given wasm bytes. * @@ -756,4 +771,25 @@ void *wasmer_trampoline_get_context(void); */ bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); +/** + * Convenience function that creates a WASI import object with no arguments, + * environment variables, preopened files, or mapped directories. + * + * This function is the same as calling [`wasmer_wasi_generate_import_object`] with all + * empty values. + */ +wasmer_import_object_t *wasmer_wasi_generate_default_import_object(void); + +/** + * Creates a WASI import object + */ +wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); + #endif /* WASMER_H */ diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index d372d8eb3e2..51cdaa5c959 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -154,6 +154,15 @@ struct wasmer_trampoline_buffer_t { }; +/// Opens a directory that's visible to the WASI module as `alias` but +/// is backed by the host file at `host_file_path` +struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + wasmer_byte_array alias; + /// The backing file that the WASI module will interact with via the alias + wasmer_byte_array host_file_path; +}; + extern "C" { /// Creates a new Module from the given wasm bytes. @@ -590,6 +599,23 @@ void *wasmer_trampoline_get_context(); /// Returns true for valid wasm bytes and false for invalid bytes bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); +/// Convenience function that creates a WASI import object with no arguments, +/// environment variables, preopened files, or mapped directories. +/// +/// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all +/// empty values. +wasmer_import_object_t *wasmer_wasi_generate_default_import_object(); + +/// Creates a WASI import object +wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); + } // extern "C" #endif // WASMER_H