diff --git a/examples/plugin-for-example.wasm b/examples/plugin-for-example.wasm index 4754287abdb..4719a0b63c3 100755 Binary files a/examples/plugin-for-example.wasm and b/examples/plugin-for-example.wasm differ diff --git a/examples/plugin-for-example/README.md b/examples/plugin-for-example/README.md index 42bc3ac269d..a405e250cc8 100644 --- a/examples/plugin-for-example/README.md +++ b/examples/plugin-for-example/README.md @@ -40,4 +40,6 @@ In this example, we instantiate a system with an extended (WASI)[wasi] ABI, allo Because the Rust WASI doesn't support the crate type of `cdylib`, we have to include a main function which we don't use. This is being discussed [here](https://github.com/WebAssembly/WASI/issues/24). +We call the main function to initialize WASI's libpreopen internal datastructures and have the module call back into the host to set swap out the modules implementation of stdout. The host then provides a wrapper around stdout, allowing the guest's writes to stdout to be formatted in a host-appropriate manner. + [wasi]: https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/ diff --git a/examples/plugin-for-example/src/main.rs b/examples/plugin-for-example/src/main.rs index 9ee284aab58..7479e9bcfc5 100644 --- a/examples/plugin-for-example/src/main.rs +++ b/examples/plugin-for-example/src/main.rs @@ -1,5 +1,6 @@ extern "C" { fn it_works() -> i32; + fn initialize(); } #[no_mangle] @@ -9,4 +10,6 @@ pub fn plugin_entrypoint(n: i32) -> i32 { result + n } -pub fn main() {} +pub fn main() { + unsafe { initialize() }; +} diff --git a/examples/plugin.rs b/examples/plugin.rs index c706c99e5d1..6d576914ea2 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -1,6 +1,10 @@ use wasmer_runtime::{func, imports, instantiate}; use wasmer_runtime_core::vm::Ctx; -use wasmer_wasi::generate_import_object; +use wasmer_wasi::{ + generate_import_object, + state::{self, WasiFile}, + types, +}; static PLUGIN_LOCATION: &'static str = "examples/plugin-for-example.wasm"; @@ -9,6 +13,107 @@ fn it_works(_ctx: &mut Ctx) -> i32 { 5 } +#[derive(Debug)] +pub struct LoggingWrapper { + pub wasm_module_name: String, +} + +// std io trait boiler plate so we can implement WasiFile +// LoggingWrapper is a write-only type so we just want to immediately +// fail when reading or Seeking +impl std::io::Read for LoggingWrapper { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from logging wrapper", + )) + } + fn read_to_end(&mut self, _buf: &mut Vec) -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from logging wrapper", + )) + } + fn read_to_string(&mut self, _buf: &mut String) -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from logging wrapper", + )) + } + fn read_exact(&mut self, _buf: &mut [u8]) -> std::io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from logging wrapper", + )) + } +} +impl std::io::Seek for LoggingWrapper { + fn seek(&mut self, _pos: std::io::SeekFrom) -> std::io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek logging wrapper", + )) + } +} +impl std::io::Write for LoggingWrapper { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let stdout = std::io::stdout(); + let mut out = stdout.lock(); + out.write(b"[")?; + out.write(self.wasm_module_name.as_bytes())?; + out.write(b"]: ")?; + out.write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + std::io::stdout().flush() + } + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut out = stdout.lock(); + out.write(b"[")?; + out.write(self.wasm_module_name.as_bytes())?; + out.write(b"]: ")?; + out.write_all(buf) + } + fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut out = stdout.lock(); + out.write(b"[")?; + out.write(self.wasm_module_name.as_bytes())?; + out.write(b"]: ")?; + out.write_fmt(fmt) + } +} + +// the WasiFile methods aren't relevant for a write-only Stdout-like implementation +impl WasiFile for LoggingWrapper { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } +} + +/// Called by the program when it wants to set itself up +fn initialize(ctx: &mut Ctx) { + let state = state::get_wasi_state(ctx); + let wasi_file_inner = LoggingWrapper { + wasm_module_name: "example module name".to_string(), + }; + // swap stdout with our new wasifile + let _old_stdout = state + .fs + .swap_file(types::__WASI_STDOUT_FILENO, Box::new(wasi_file_inner)) + .unwrap(); +} + fn main() { // Load the plugin data let wasm_bytes = std::fs::read(PLUGIN_LOCATION).expect(&format!( @@ -22,6 +127,7 @@ fn main() { let custom_imports = imports! { "env" => { "it_works" => func!(it_works), + "initialize" => func!(initialize), }, }; // The WASI imports object contains all required import functions for a WASI module to run. @@ -30,6 +136,8 @@ fn main() { let instance = instantiate(&wasm_bytes[..], &base_imports).expect("failed to instantiate wasm module"); + let main = instance.func::<(), ()>("_start").unwrap(); + main.call().expect("Could not initialize"); // get a reference to the function "plugin_entrypoint" which takes an i32 and returns an i32 let entry_point = instance.func::<(i32), i32>("plugin_entrypoint").unwrap(); // call the "entry_point" function in WebAssembly with the number "2" as the i32 argument diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state.rs index 372dacbb2de..051a468508d 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -19,9 +19,14 @@ use std::{ }; use wasmer_runtime_core::{debug, vm::Ctx}; +/// the fd value of the virtual root +pub const VIRTUAL_ROOT_FD: __wasi_fd_t = 4; +/// all the rights enabled +pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFFFFFF; + /// Get WasiState from a Ctx -pub unsafe fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState { - &mut *(ctx.data as *mut WasiState) +pub fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState { + unsafe { &mut *(ctx.data as *mut WasiState) } } /// A completely aribtrary "big enough" number used as the upper limit for @@ -607,16 +612,35 @@ impl WasiFs { fd: __wasi_fd_t, file: Box, ) -> Result>, WasiFsError> { - let base_fd = self.get_fd(fd).map_err(WasiFsError::from_wasi_err)?; - let base_inode = base_fd.inode; + match fd { + __WASI_STDIN_FILENO => { + let mut ret = file; + std::mem::swap(&mut self.stdin, &mut ret); + Ok(Some(ret)) + } + __WASI_STDOUT_FILENO => { + let mut ret = file; + std::mem::swap(&mut self.stdout, &mut ret); + Ok(Some(ret)) + } + __WASI_STDERR_FILENO => { + let mut ret = file; + std::mem::swap(&mut self.stderr, &mut ret); + Ok(Some(ret)) + } + _ => { + let base_fd = self.get_fd(fd).map_err(WasiFsError::from_wasi_err)?; + let base_inode = base_fd.inode; - match &mut self.inodes[base_inode].kind { - Kind::File { ref mut handle, .. } => { - let mut ret = Some(file); - std::mem::swap(handle, &mut ret); - Ok(ret) + match &mut self.inodes[base_inode].kind { + Kind::File { ref mut handle, .. } => { + let mut ret = Some(file); + std::mem::swap(handle, &mut ret); + Ok(ret) + } + _ => return Err(WasiFsError::NotAFile), + } } - _ => Err(WasiFsError::NotAFile), } }