diff --git a/CHANGELOG.md b/CHANGELOG.md index 113f7b82492..3460e4bf7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All PRs to the Wasmer repository must add to this file. Blocks of changes will separated by version increments. ## **[Unreleased]** +- [#595](https://github.com/wasmerio/wasmer/pull/595) Add unstable public API for interfacing with the WASI file system in plugin-like usecases - [#598](https://github.com/wasmerio/wasmer/pull/598) LLVM Backend is now supported in Windows - [#599](https://github.com/wasmerio/wasmer/pull/599) Fix llvm backend failures in fat spec tests and simd_binaryen spec test. - [#579](https://github.com/wasmerio/wasmer/pull/579) Fix bug in caching with LLVM and Singlepass backends. 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/lib.rs b/lib/wasi/src/lib.rs index 51371480fd9..a88065b1e72 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -6,11 +6,12 @@ extern crate winapi; #[macro_use] mod macros; mod ptr; -mod state; +pub mod state; mod syscalls; mod utils; use self::state::{WasiFs, WasiState}; +pub use self::syscalls::types; use self::syscalls::*; use std::ffi::c_void; diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state.rs index 1c428a2af69..051a468508d 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -1,3 +1,6 @@ +//! WARNING: the API exposed here is unstable and very experimental. Certain thins will not +//! yet and may be broken in patch releases. If you're using this and have any specific needs, +//! please let us know here https://github.com/wasmerio/wasmer/issues/583 or by filing an issue. // use wasmer_runtime_abi::vfs::{ // vfs::Vfs, // file_like::{FileLike, Metadata}; @@ -5,7 +8,7 @@ use crate::syscalls::types::*; use generational_arena::Arena; pub use generational_arena::Index as Inode; -use hashbrown::hash_map::{Entry, HashMap}; +use hashbrown::hash_map::HashMap; use std::{ borrow::Borrow, cell::Cell, @@ -14,80 +17,310 @@ use std::{ path::{Path, PathBuf}, time::SystemTime, }; -use wasmer_runtime_core::debug; +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 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 /// the number of symlinks that can be traversed when resolving a path pub const MAX_SYMLINKS: u32 = 128; -#[derive(Debug)] -pub enum WasiFile { - HostFile(fs::File), +/// Error type for external users +#[derive(Debug, PartialEq, Eq)] +#[allow(dead_code)] +// dead code beacuse this is for external use +pub enum WasiFsError { + /// The fd given as a base was not a directory so the operation was not possible + BaseNotDirectory, + /// Expected a file but found not a file + NotAFile, + /// The fd given was not usable + InvalidFd, + /// File exists + AlreadyExists, + /// Something failed when doing IO. These errors can generally not be handled. + /// It may work if tried again. + IOError, + /// A WASI error without an external name. If you encounter this it means + /// that there's probably a bug on our side (maybe as simple as forgetting to wrap + /// this error, but perhaps something broke) + UnknownError(__wasi_errno_t), } -impl WasiFile { - pub fn close(self) {} +impl WasiFsError { + pub fn from_wasi_err(err: __wasi_errno_t) -> WasiFsError { + match err { + __WASI_EBADF => WasiFsError::InvalidFd, + __WASI_EEXIST => WasiFsError::AlreadyExists, + __WASI_EIO => WasiFsError::IOError, + _ => WasiFsError::UnknownError(err), + } + } } -impl Write for WasiFile { - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - WasiFile::HostFile(hf) => hf.write(buf), - } +/// This trait relies on your file closing when it goes out of scope via `Drop` +pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { + /// the last time the file was accessed in nanoseconds as a UNIX timestamp + fn last_accessed(&self) -> u64; + /// the last time the file was modified in nanoseconds as a UNIX timestamp + fn last_modified(&self) -> u64; + /// the time at which the file was created in nanoseconds as a UNIX timestamp + fn created_time(&self) -> u64; + /// the size of the file in bytes + fn size(&self) -> u64; +} + +impl WasiFile for fs::File { + fn last_accessed(&self) -> u64 { + self.metadata() + .unwrap() + .accessed() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0) } - fn flush(&mut self) -> io::Result<()> { - match self { - WasiFile::HostFile(hf) => hf.flush(), - } + fn last_modified(&self) -> u64 { + self.metadata() + .unwrap() + .modified() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0) + } + + fn created_time(&self) -> u64 { + self.metadata() + .unwrap() + .created() + .ok() + .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|ct| ct.as_nanos() as u64) + .unwrap_or(0) + } + + fn size(&self) -> u64 { + self.metadata().unwrap().len() } +} +#[derive(Debug)] +pub struct Stdout(std::io::Stdout); +impl Read for Stdout { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } + fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } + fn read_to_string(&mut self, _buf: &mut String) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } + fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stdout", + )) + } +} +impl Seek for Stdout { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek stdout", + )) + } +} +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - match self { - WasiFile::HostFile(hf) => hf.write_all(buf), - } + self.0.write_all(buf) + } + fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { + self.0.write_fmt(fmt) + } +} + +impl WasiFile for Stdout { + fn last_accessed(&self) -> u64 { + 0 } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } +} +#[derive(Debug)] +pub struct Stderr(std::io::Stderr); +impl Read for Stderr { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } + fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } + fn read_to_string(&mut self, _buf: &mut String) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } + fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not read from stderr", + )) + } +} +impl Seek for Stderr { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek stderr", + )) + } +} +impl Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.0.write_all(buf) + } fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - match self { - WasiFile::HostFile(hf) => hf.write_fmt(fmt), - } + self.0.write_fmt(fmt) } } -impl Read for WasiFile { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - WasiFile::HostFile(hf) => hf.read(buf), - } +impl WasiFile for Stderr { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 } +} +#[derive(Debug)] +pub struct Stdin(std::io::Stdin); +impl Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - match self { - WasiFile::HostFile(hf) => hf.read_to_end(buf), - } + self.0.read_to_end(buf) } - fn read_to_string(&mut self, buf: &mut String) -> io::Result { - match self { - WasiFile::HostFile(hf) => hf.read_to_string(buf), - } + self.0.read_to_string(buf) } - fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - match self { - WasiFile::HostFile(hf) => hf.read_exact(buf), - } + self.0.read_exact(buf) + } +} +impl Seek for Stdin { + fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not seek stdin", + )) + } +} +impl Write for Stdin { + fn write(&mut self, _buf: &[u8]) -> io::Result { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } + fn flush(&mut self) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } + fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) + } + fn write_fmt(&mut self, _fmt: ::std::fmt::Arguments) -> io::Result<()> { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "can not write to stdin", + )) } } -impl Seek for WasiFile { - fn seek(&mut self, pos: io::SeekFrom) -> io::Result { - match self { - WasiFile::HostFile(hf) => hf.seek(pos), - } +impl WasiFile for Stdin { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 } + fn size(&self) -> u64 { + 0 + } +} + +/* +TODO: Think about using this +trait WasiFdBacking: std::fmt::Debug { + fn get_stat(&self) -> &__wasi_filestat_t; + fn get_stat_mut(&mut self) -> &mut __wasi_filestat_t; + fn is_preopened(&self) -> bool; + fn get_name(&self) -> &str; } +*/ /// A file that Wasi knows about that may or may not be open #[derive(Debug)] @@ -98,12 +331,30 @@ pub struct InodeVal { pub kind: Kind, } +/*impl WasiFdBacking for InodeVal { + fn get_stat(&self) -> &__wasi_filestat_t { + &self.stat + } + + fn get_stat_mut(&mut self) -> &mut __wasi_filestat_t { + &mut self.stat + } + + fn is_preopened(&self) -> bool { + self.is_preopened + } + + fn get_name(&self) -> &str { + self.name.as_ref() + } +}*/ + #[allow(dead_code)] #[derive(Debug)] pub enum Kind { File { /// the open file, if it's open - handle: Option, + handle: Option>, /// the path to the file path: PathBuf, }, @@ -152,6 +403,8 @@ pub struct Fd { } #[derive(Debug)] +/// Warning, modifying these fields directly may cause invariants to break and +/// should be considered unsafe. These fields may be made private in a future release pub struct WasiFs { //pub repo: Repo, pub preopen_fds: Vec, @@ -160,6 +413,10 @@ pub struct WasiFs { pub fd_map: HashMap, pub next_fd: Cell, inode_counter: Cell, + + pub stdout: Box, + pub stderr: Box, + pub stdin: Box, } impl WasiFs { @@ -176,6 +433,10 @@ impl WasiFs { fd_map: HashMap::new(), next_fd: Cell::new(3), inode_counter: Cell::new(1024), + + stdin: Box::new(Stdin(io::stdin())), + stdout: Box::new(Stdout(io::stdout())), + stderr: Box::new(Stderr(io::stderr())), }; // create virtual root let root_inode = { @@ -291,117 +552,97 @@ impl WasiFs { next } + /// Opens a user-supplied file in the directory specified with the + /// name and flags given + // dead code because this is an API for external use #[allow(dead_code)] - fn get_inode(&mut self, path: &str) -> Option { - Some(match self.name_map.entry(path.to_string()) { - Entry::Occupied(o) => *o.get(), - Entry::Vacant(_v) => { - return None; - // let file = if let Ok(file) = OpenOptions::new() - // .read(true) - // .write(true) - // .create(false) - // .open(&mut self.repo, path) - // { - // file - // } else { - // return None; - // }; - - // let metadata = file.metadata().unwrap(); - // let inode_index = { - // let index = self.inode_counter.get(); - // self.inode_counter.replace(index + 1) - // }; - - // let systime_to_nanos = |systime: SystemTime| { - // let duration = systime - // .duration_since(SystemTime::UNIX_EPOCH) - // .expect("should always be after unix epoch"); - // duration.as_nanos() as u64 - // }; - - // let inode = self.inodes.insert(InodeVal { - // stat: __wasi_filestat_t { - // st_dev: 0, - // st_ino: inode_index, - // st_filetype: match metadata.file_type() { - // FileType::File => __WASI_FILETYPE_REGULAR_FILE, - // FileType::Dir => __WASI_FILETYPE_DIRECTORY, - // }, - // st_nlink: 0, - // st_size: metadata.content_len() as u64, - // st_atim: systime_to_nanos(SystemTime::now()), - // st_mtim: systime_to_nanos(metadata.modified_at()), - // st_ctim: systime_to_nanos(metadata.created_at()), - // }, - // is_preopened: false, - // name: path.to_string(), - // kind: match metadata.file_type() { - // FileType::File => Kind::File { handle: file }, - // FileType::Dir => Kind::Dir { - // handle: file, - // entries: HashMap::new(), - // }, - // }, - // }); - // v.insert(inode); - // inode - } - }) - } + pub fn open_file_at( + &mut self, + base: __wasi_fd_t, + file: Box, + name: String, + rights: __wasi_rights_t, + rights_inheriting: __wasi_rights_t, + flags: __wasi_fdflags_t, + ) -> Result<__wasi_fd_t, WasiFsError> { + let base_fd = self.get_fd(base).map_err(WasiFsError::from_wasi_err)?; + // TODO: check permissions here? probably not, but this should be + // an explicit choice, so justify it in a comment when we remove this one + let base_inode = base_fd.inode; - /* - #[allow(dead_code)] - fn filestat_inode( - &self, - inode: Inode, - flags: __wasi_lookupflags_t, - ) -> Result<__wasi_filestat_t, __wasi_errno_t> { - let inode_val = &self.inodes[inode]; - if let ( - true, - Kind::Symlink { - mut forwarded, - path, - }, - ) = (flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, &inode_val.kind) - { - // Time to follow the symlink. - let mut counter = 0; - - while counter <= MAX_SYMLINKS { - let inode_val = &self.inodes[forwarded]; - if let &Kind::Symlink { - forwarded: new_forwarded, - } = &inode_val.kind - { - counter += 1; - forwarded = new_forwarded; - } else { - return Ok(inode_val.stat); + match &self.inodes[base_inode].kind { + Kind::Dir { ref entries, .. } | Kind::Root { ref entries } => { + if let Some(_entry) = entries.get(&name) { + // TODO: eventually change the logic here to allow overwrites + return Err(WasiFsError::AlreadyExists); } - } - Err(__WASI_EMLINK) - } else { - Ok(inode_val.stat) + let kind = Kind::File { + handle: Some(file), + path: PathBuf::from(""), + }; + + let inode = self + .create_inode(kind, false, name.clone()) + .map_err(|_| WasiFsError::IOError)?; + // reborrow to insert + match &mut self.inodes[base_inode].kind { + Kind::Dir { + ref mut entries, .. + } + | Kind::Root { ref mut entries } => { + entries.insert(name, inode).ok_or(WasiFsError::IOError)?; + } + _ => unreachable!("Dir or Root became not Dir or Root"), + } + + self.create_fd(rights, rights_inheriting, flags, inode) + .map_err(WasiFsError::from_wasi_err) + } + _ => Err(WasiFsError::BaseNotDirectory), } } + /// Change the backing of a given file descriptor + /// Returns the old backing + /// TODO: add examples #[allow(dead_code)] - pub fn filestat_path( + pub fn swap_file( &mut self, - preopened_fd: __wasi_fd_t, - flags: __wasi_lookupflags_t, - path: &str, - ) -> Result<__wasi_filestat_t, __wasi_errno_t> { - warn!("Should use preopned_fd: {}", preopened_fd); - let inode = self.get_inode(path).ok_or(__WASI_EINVAL)?; + fd: __wasi_fd_t, + file: Box, + ) -> Result>, WasiFsError> { + 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; - self.filestat_inode(inode, flags) + 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), + } + } + } } - */ fn get_inode_at_path_inner( &mut self, @@ -666,9 +907,9 @@ impl WasiFs { pub fn flush(&mut self, fd: __wasi_fd_t) -> Result<(), __wasi_errno_t> { match fd { - 0 => (), - 1 => io::stdout().flush().map_err(|_| __WASI_EIO)?, - 2 => io::stderr().flush().map_err(|_| __WASI_EIO)?, + __WASI_STDIN_FILENO => (), + __WASI_STDOUT_FILENO => self.stdout.flush().map_err(|_| __WASI_EIO)?, + __WASI_STDERR_FILENO => self.stderr.flush().map_err(|_| __WASI_EIO)?, _ => { let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; if fd.rights & __WASI_RIGHT_FD_DATASYNC == 0 { @@ -717,7 +958,7 @@ impl WasiFs { rights_inheriting: __wasi_rights_t, flags: __wasi_fdflags_t, inode: Inode, - ) -> Result { + ) -> Result<__wasi_fd_t, __wasi_errno_t> { let idx = self.next_fd.get(); self.next_fd.set(idx + 1); self.fd_map.insert( @@ -762,7 +1003,17 @@ impl WasiFs { pub fn get_stat_for_kind(&self, kind: &Kind) -> Option<__wasi_filestat_t> { let md = match kind { Kind::File { handle, path } => match handle { - Some(WasiFile::HostFile(hf)) => hf.metadata().ok()?, + Some(wf) => { + return Some(__wasi_filestat_t { + st_filetype: __WASI_FILETYPE_REGULAR_FILE, + st_size: wf.size(), + st_atim: wf.last_accessed(), + st_mtim: wf.last_modified(), + st_ctim: wf.created_time(), + + ..__wasi_filestat_t::default() + }) + } None => path.metadata().ok()?, }, Kind::Dir { path, .. } => path.metadata().ok()?, diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 2df8b747777..2dee1ac143b 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -9,7 +9,7 @@ use self::types::*; use crate::{ ptr::{Array, WasmPtr}, state::{ - host_file_type_to_wasi_file_type, Fd, Inode, InodeVal, Kind, WasiFile, WasiState, + self, host_file_type_to_wasi_file_type, Fd, Inode, InodeVal, Kind, WasiFile, WasiState, MAX_SYMLINKS, }, ExitCode, @@ -27,9 +27,10 @@ pub use unix::*; #[cfg(any(target_os = "windows"))] pub use windows::*; +/// This function is not safe #[allow(clippy::mut_from_ref)] -fn get_wasi_state(ctx: &Ctx) -> &mut WasiState { - unsafe { &mut *(ctx.data as *mut WasiState) } +pub(crate) fn get_wasi_state(ctx: &Ctx) -> &mut WasiState { + unsafe { state::get_wasi_state(&mut *(ctx as *const Ctx as *mut Ctx)) } } fn write_bytes( @@ -327,11 +328,6 @@ pub fn fd_close(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { Kind::File { ref mut handle, .. } => { let mut empty_handle = None; std::mem::swap(handle, &mut empty_handle); - if let Some(handle_inner) = empty_handle { - handle_inner.close() - } else { - return __WASI_EINVAL; - } } Kind::Dir { .. } => return __WASI_EISDIR, Kind::Root { .. } => return __WASI_EACCES, @@ -642,24 +638,13 @@ pub fn fd_pwrite( let memory = ctx.memory(0); let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len)); let nwritten_cell = wasi_try!(nwritten.deref(memory)); + let state = get_wasi_state(ctx); let bytes_written = match fd { - 0 => return __WASI_EINVAL, - 1 => { - let stdout = io::stdout(); - let mut handle = stdout.lock(); - - wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) - } - - 2 => { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - - wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) - } + __WASI_STDIN_FILENO => return __WASI_EINVAL, + __WASI_STDOUT_FILENO => wasi_try!(write_bytes(&mut state.fs.stdout, memory, iovs_arr_cell)), + __WASI_STDERR_FILENO => wasi_try!(write_bytes(&mut state.fs.stderr, memory, iovs_arr_cell)), _ => { - let state = get_wasi_state(ctx); let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { @@ -740,17 +725,12 @@ pub fn fd_read( } Ok(bytes_read) } + let state = get_wasi_state(ctx); let bytes_read = match fd { - 0 => { - let stdin = io::stdin(); - let mut handle = stdin.lock(); - - wasi_try!(read_bytes(handle, memory, iovs_arr_cell)) - } - 1 | 2 => return __WASI_EINVAL, + __WASI_STDIN_FILENO => wasi_try!(read_bytes(&mut state.fs.stdin, memory, iovs_arr_cell)), + __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return __WASI_EINVAL, _ => { - let state = get_wasi_state(ctx); let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) { @@ -1062,22 +1042,12 @@ pub fn fd_write( let memory = ctx.memory(0); let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len)); let nwritten_cell = wasi_try!(nwritten.deref(memory)); + let state = get_wasi_state(ctx); let bytes_written = match fd { - 0 => return __WASI_EINVAL, - 1 => { - let stdout = io::stdout(); - let mut handle = stdout.lock(); - - wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) - } - - 2 => { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - - wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) - } + __WASI_STDIN_FILENO => return __WASI_EINVAL, + __WASI_STDOUT_FILENO => wasi_try!(write_bytes(&mut state.fs.stdout, memory, iovs_arr_cell)), + __WASI_STDERR_FILENO => wasi_try!(write_bytes(&mut state.fs.stderr, memory, iovs_arr_cell)), _ => { let state = get_wasi_state(ctx); let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); @@ -1436,7 +1406,7 @@ pub fn path_open( .create(o_flags & __WASI_O_CREAT != 0) .truncate(o_flags & __WASI_O_TRUNC != 0); - *handle = Some(WasiFile::HostFile(wasi_try!(open_options + *handle = Some(Box::new(wasi_try!(open_options .open(&path) .map_err(|_| __WASI_EIO)))); } @@ -1495,12 +1465,14 @@ pub fn path_open( .write(true) .create_new(true); - Some(WasiFile::HostFile(wasi_try!(open_options - .open(&new_file_host_path) - .map_err(|e| { - debug!("Error opening file {}", e); - __WASI_EIO - })))) + Some( + Box::new(wasi_try!(open_options.open(&new_file_host_path).map_err( + |e| { + debug!("Error opening file {}", e); + __WASI_EIO + } + ))) as Box, + ) }; let new_inode = {