From 51faeed2cf3c97204308731fd862a157859868c8 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 26 Aug 2019 17:11:56 -0700 Subject: [PATCH 1/7] Add serialization for WASI state - wip --- Cargo.lock | 3 + lib/wasi/Cargo.toml | 12 +-- lib/wasi/src/lib.rs | 4 +- lib/wasi/src/state/mod.rs | 143 +++++++++++++++++++++++++-------- lib/wasi/src/state/types.rs | 54 ++++++++++++- lib/wasi/src/syscalls/mod.rs | 36 +++++---- lib/wasi/src/syscalls/types.rs | 3 +- 7 files changed, 196 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57bf514a06b..e8bf1ca9f78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,6 +535,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1651,11 +1652,13 @@ dependencies = [ name = "wasmer-wasi" version = "0.6.0" dependencies = [ + "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "generational-arena 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.6.0", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index 26a09c5ba83..0bc79362e31 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -8,14 +8,16 @@ repository = "https://github.com/wasmerio/wasmer" edition = "2018" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } +bincode = "1" +byteorder = "1.3.2" +generational-arena = { version = "0.2.2", features = ["serde"] } libc = "0.2.60" -rand = "0.7.0" -# wasmer-runtime-abi = { path = "../runtime-abi" } -generational-arena = "0.2.2" log = "0.4.8" -byteorder = "1.3.2" +rand = "0.7.0" time = "0.1.42" +serde = { version = "1", features = ["derive"] } +# wasmer-runtime-abi = { path = "../runtime-abi" } +wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } [target.'cfg(windows)'.dependencies] winapi = "0.3.7" diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index ac9b11a86b9..f1a19d67938 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -49,8 +49,8 @@ pub fn generate_import_object( let state = Box::new(WasiState { fs: WasiFs::new(&preopened_files, &mapped_dirs).unwrap(), - args: &args[..], - envs: &envs[..], + args, + envs, }); ( diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index dfcf5274109..fccdd94809c 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -8,6 +8,7 @@ pub use self::types::*; use crate::syscalls::types::*; use generational_arena::Arena; pub use generational_arena::Index as Inode; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::{ borrow::Borrow, @@ -35,7 +36,7 @@ pub unsafe fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState { pub const MAX_SYMLINKS: u32 = 128; /// A file that Wasi knows about that may or may not be open -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct InodeVal { pub stat: __wasi_filestat_t, pub is_preopened: bool, @@ -43,28 +44,10 @@ 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)] +#[derive(Debug, Serialize, Deserialize)] pub enum Kind { File { + #[serde(skip)] /// the open file, if it's open handle: Option>, /// The path on the host system where the file is located @@ -106,15 +89,24 @@ pub enum Kind { }, } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct Fd { pub rights: __wasi_rights_t, pub rights_inheriting: __wasi_rights_t, pub flags: __wasi_fdflags_t, pub offset: u64, + pub open_flags: u16, pub inode: Inode, } +impl Fd { + pub const READ: u16 = 1; + pub const WRITE: u16 = 2; + pub const APPEND: u16 = 4; + pub const TRUNCATE: u16 = 8; + pub const CREATE: u16 = 16; +} + #[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 @@ -176,7 +168,7 @@ impl WasiFs { & (!__WASI_RIGHT_PATH_REMOVE_DIRECTORY)*/; let inode = wasi_fs.create_virtual_root(); let fd = wasi_fs - .create_fd(root_rights, root_rights, 0, inode) + .create_fd(root_rights, root_rights, 0, Fd::READ, inode) .expect("Could not create root fd"); wasi_fs.preopen_fds.push(fd); inode @@ -211,7 +203,13 @@ impl WasiFs { ) })?; let fd = wasi_fs - .create_fd(default_rights, default_rights, 0, inode) + .create_fd( + default_rights, + default_rights, + 0, + Fd::READ | Fd::WRITE, + inode, + ) .expect("Could not open fd"); if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { // todo handle collisions @@ -249,7 +247,13 @@ impl WasiFs { ) })?; let fd = wasi_fs - .create_fd(default_rights, default_rights, 0, inode) + .create_fd( + default_rights, + default_rights, + 0, + Fd::READ | Fd::WRITE, + inode, + ) .expect("Could not open fd"); if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { // todo handle collisions @@ -276,6 +280,7 @@ impl WasiFs { &mut self, base: __wasi_fd_t, file: Box, + open_flags: u16, name: String, rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, @@ -312,7 +317,7 @@ impl WasiFs { _ => unreachable!("Dir or Root became not Dir or Root"), } - self.create_fd(rights, rights_inheriting, flags, inode) + self.create_fd(rights, rights_inheriting, flags, open_flags, inode) .map_err(WasiFsError::from_wasi_err) } _ => Err(WasiFsError::BaseNotDirectory), @@ -754,10 +759,13 @@ impl WasiFs { let inode = &mut self.inodes[fd.inode]; match &mut inode.kind { - Kind::File { - handle: Some(handle), - .. - } => handle.flush().map_err(|_| __WASI_EIO)?, + Kind::File { handle, .. } => { + if let Some(file) = handle { + file.flush().map_err(|_| __WASI_EIO)? + } else { + return Err(__WASI_EIO); + } + } // TODO: verify this behavior Kind::Dir { .. } => return Err(__WASI_EISDIR), Kind::Symlink { .. } => unimplemented!(), @@ -810,6 +818,7 @@ impl WasiFs { rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, flags: __wasi_fdflags_t, + open_flags: u16, inode: Inode, ) -> Result<__wasi_fd_t, __wasi_errno_t> { let idx = self.next_fd.get(); @@ -821,6 +830,7 @@ impl WasiFs { rights_inheriting, flags, offset: 0, + open_flags, inode, }, ); @@ -922,13 +932,80 @@ impl WasiFs { ..__wasi_filestat_t::default() }) } + + pub(crate) fn unfreeze(bytes: &[u8]) -> Option<(WasiFs, &[u8])> { + unimplemented!() + Some((Self { + preopen_fds, + name_map, + inodes, + fd_map, + next_fd, + inode_counter, + orphan_fds, + + stdout, + stderr, + stdin + }, unimplemented!())) + } + + pub(crate) fn freeze(&self) -> Option> { + let mut out = vec![]; + // store pointer to stdout, stderr, and stdin here I guess + // hmmm + out.append(&mut bincode::serialize(&self.preopen_fds).ok()?); + out.append(&mut bincode::serialize(&self.name_map).ok()?); + out.append(&mut bincode::serialize(&self.inodes).ok()?); + out.append(&mut bincode::serialize(&self.fd_map).ok()?); + out.append(&mut bincode::serialize(&self.next_fd).ok()?); + out.append(&mut bincode::serialize(&self.inode_counter).ok()?); + out.append(&mut bincode::serialize(&self.orphan_fds).ok()?); + out.append(&mut self.stdout.to_bytes()?); + out.append(&mut self.stderr.to_bytes()?); + out.append(&mut self.stdin.to_bytes()?); + + Some(out) + } } #[derive(Debug)] -pub struct WasiState<'a> { +pub struct WasiState { pub fs: WasiFs, - pub args: &'a [Vec], - pub envs: &'a [Vec], + pub args: Vec>, + pub envs: Vec>, +} + +impl WasiState { + /// Turn the WasiState into bytes + pub fn freeze(&self) -> Option> { + let wasi_fs = self.wasi_fs.freeze()?; + let args = bincode::serialize(&self.args).ok()?; + let envs = bincode::serialize(&self.envs).ok()?; + + Some( + wasi_fs + .into_iter() + .chain(args.into_iter()) + .chain(envs.into_iter()) + .collect(), + ) + } + + /// Get a WasiState from bytes + pub fn unfreeze(bytes: &[u8], deserialize_fns: Vec>) -> Option + where + F: Fn(&[u8]) -> Option>, + { + let (wasi_fs, remaining_bytes) = WasiFs::unfreeze(bytes)?; + let (args, envs): (Vec>, Vec>) = + bincode::deserialize(remaining_bytes).ok()?; + Some(Self { + fs: wasi_fs, + args, + envs, + }) + } } pub fn host_file_type_to_wasi_file_type(file_type: fs::FileType) -> __wasi_filetype_t { diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 09dfe424739..1b3c73c5a83 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -4,7 +4,7 @@ use crate::syscalls::types::*; use std::convert::TryInto; use std::{ fs, - io::{self, Read, Seek, Write}, + io::{self, Read, Seek, SeekFrom, Write}, path::PathBuf, time::SystemTime, }; @@ -178,6 +178,29 @@ pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { fn get_raw_fd(&self) -> Option { None } + + /// Try to turn the file into bytes + fn to_bytes(&self) -> Option>; +} + +/// Tries to read a WASI file out of bytes +/// with default parsers for stdin/stdout/stderr and `HostFile` +pub fn wasi_file_from_bytes(bytes: &[u8]) -> Option> { + Some(if b"stdout"[..] == bytes[.."stdout".len()] { + Box::new(Stdout(std::io::stdout())) + } else if b"stderr"[..] == bytes[.."stderr".len()] { + Box::new(Stderr(std::io::stderr())) + } else if b"stdin"[..] == bytes[.."stdin".len()] { + Box::new(Stdin(std::io::stdin())) + } else if b"host_file"[..] == bytes[.."host_file".len()] { + unimplemented!(); + } else { + return None; + }) +} + +pub(crate) fn serialize_file(file: Box) -> Result, E> { + file.to_bytes().ok_or_else(|| Default::default()) } #[derive(Debug, Clone)] @@ -485,6 +508,23 @@ impl WasiFile for HostFile { "HostFile::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } + + fn to_bytes(&self) -> Option> { + let mut out = vec![]; + for c in "host_file".chars() { + out.push(c as u8); + } + let cursor = self.inner.seek(SeekFrom::Current(0)).ok()?; + // store r/w/append info here? or is this handeled somewhere else? + for b in cursor.to_le_bytes().into_iter() { + out.push(*b); + } + for b in self.host_path.to_string_lossy().bytes() { + out.push(b); + } + + Some(out) + } } impl From for WasiFsError { @@ -591,6 +631,10 @@ impl WasiFile for Stdout { "Stdout::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } + + fn to_bytes(&self) -> Option> { + Some("stdout".chars().map(|c| c as u8).collect()) + } } #[derive(Debug)] @@ -670,6 +714,10 @@ impl WasiFile for Stderr { "Stderr::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } + + fn to_bytes(&self) -> Option> { + Some("stderr".chars().map(|c| c as u8).collect()) + } } #[derive(Debug)] @@ -773,6 +821,10 @@ impl WasiFile for Stdin { "Stdin::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } + + fn to_bytes(&self) -> Option> { + Some("stdin".chars().map(|c| c as u8).collect()) + } } /* diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index dc8bebff4ec..cc568997d1c 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -683,23 +683,11 @@ pub fn fd_pread( match &mut state.fs.inodes[inode].kind { Kind::File { handle, .. } => { if let Some(h) = handle { - let current_pos = - wasi_try!(h.seek(std::io::SeekFrom::Current(0)).ok(), __WASI_EIO); wasi_try!( h.seek(std::io::SeekFrom::Start(offset as u64)).ok(), __WASI_EIO ); let bytes_read = wasi_try!(read_bytes(h, memory, iov_cells)); - // reborrow so we can seek it back (the &mut gets moved into `read_bytes` - // and we can't use it after) - // If you're in the future and there's a nicer way to do this, please - // clean up this code - if let Some(h) = handle { - wasi_try!( - h.seek(std::io::SeekFrom::Start(current_pos)).ok(), - __WASI_EIO - ); - } bytes_read } else { return __WASI_EINVAL; @@ -1675,6 +1663,7 @@ pub fn path_open( if let Ok(m) = maybe_inode { &state.fs.inodes[m]; } + let mut open_flags = 0; // TODO: traverse rights of dirs properly // COMMENTED OUT: WASI isn't giving appropriate rights here when opening @@ -1702,7 +1691,16 @@ pub fn path_open( .write(adjusted_rights & __WASI_RIGHT_FD_WRITE != 0) .create(o_flags & __WASI_O_CREAT != 0) .truncate(o_flags & __WASI_O_TRUNC != 0); - + open_flags |= Fd::READ; + if adjusted_rights & __WASI_RIGHT_FD_WRITE != 0 { + open_flags |= Fd::WRITE; + } + if o_flags & __WASI_O_CREAT != 0 { + open_flags |= Fd::CREATE; + } + if o_flags & __WASI_O_TRUNC != 0 { + open_flags |= Fd::TRUNCATE; + } *handle = Some(Box::new(HostFile::new( wasi_try!(open_options.open(&path).map_err(|_| __WASI_EIO)), path.to_path_buf(), @@ -1762,6 +1760,7 @@ pub fn path_open( // write access is required for creating a file .write(true) .create_new(true); + open_flags |= Fd::READ | Fd::WRITE | Fd::CREATE | Fd::TRUNCATE; Some(Box::new(HostFile::new( wasi_try!(open_options.open(&new_file_host_path).map_err(|e| { @@ -1800,10 +1799,13 @@ pub fn path_open( // TODO: check and reduce these // TODO: ensure a mutable fd to root can never be opened - let out_fd = - wasi_try!(state - .fs - .create_fd(adjusted_rights, fs_rights_inheriting, fs_flags, inode)); + let out_fd = wasi_try!(state.fs.create_fd( + adjusted_rights, + fs_rights_inheriting, + fs_flags, + open_flags, + inode + )); fd_cell.set(out_fd); diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index a1c97308f49..d2aaa05540e 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -2,6 +2,7 @@ use crate::ptr::{Array, WasmPtr}; use byteorder::{ReadBytesExt, WriteBytesExt, LE}; +use serde::{Deserialize, Serialize}; use std::fmt; use std::mem; use wasmer_runtime_core::types::ValueType; @@ -316,7 +317,7 @@ pub type __wasi_filedelta_t = i64; pub type __wasi_filesize_t = u64; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[repr(C)] pub struct __wasi_filestat_t { pub st_dev: __wasi_device_t, From e88a08f973ba3f83382445b90d8d68ebc854d511 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Tue, 27 Aug 2019 15:18:12 -0700 Subject: [PATCH 2/7] Use typetag so that we can still use serde --- Cargo.lock | 77 +++++++++++ lib/wasi/Cargo.toml | 1 + lib/wasi/src/lib.rs | 4 +- lib/wasi/src/state/mod.rs | 74 ++--------- lib/wasi/src/state/types.rs | 243 +++++++++++++++++++---------------- lib/wasi/src/syscalls/mod.rs | 6 + 6 files changed, 225 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8bf1ca9f78..83301a5097d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,15 @@ dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ctor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.1" @@ -467,6 +476,14 @@ dependencies = [ "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "erased-serde" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "errno" version = "0.2.4" @@ -556,6 +573,16 @@ dependencies = [ "wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ghost" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.2.11" @@ -630,6 +657,26 @@ dependencies = [ "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "inventory" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ctor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "ghost 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "inventory-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "inventory-impl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itertools" version = "0.8.0" @@ -1345,6 +1392,28 @@ name = "typenum" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "typetag" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typetag-impl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.3.0" @@ -1660,6 +1729,7 @@ dependencies = [ "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.6.0", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1805,12 +1875,14 @@ dependencies = [ "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" +"checksum ctor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5b6b2f4752cc29efbfd03474c532ce8f916f2d44ec5bb8c21f93bc76e5365528" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dynasm 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f36d49ab6f8ecc642d2c6ee10fda04ba68003ef0277300866745cdde160e6b40" "checksum dynasmrt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c408a211e7f5762829f5e46bdff0c14bc3b1517a21a4bb781c716bf88b0c68" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7798e7da2d4cb0d6d6fc467e8d6b5bf247e9e989f786dde1732d79899c32bb10" "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3beee4bc16478a1b26f2e80ad819a52d24745e292f521a63c16eea5f74b7eb60" "checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" "checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" @@ -1822,6 +1894,7 @@ dependencies = [ "checksum generational-arena 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4024f96ffa0ebaaf36aa589cd41f2fd69f3a5e6fd02c86e11e12cdf41d5b46a3" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6171a6cc63fbabbe27c2b5ee268e8b7fe5dc1eb0dd2dfad537c1dfed6f69117e" +"checksum ghost 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "e3fa261d919c1ae9d1e4533c4a2f99e10938603c4208d56c05bec7a872b661b0" @@ -1831,6 +1904,8 @@ dependencies = [ "checksum indexmap 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6d89e0948bf10c08b9ecc8ac5b83f07f857ebe2c0cbe38de15b4e4f510356" "checksum inkwell 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)" = "" "checksum inkwell_internal_macros 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)" = "" +"checksum inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4cece20baea71d9f3435e7bbe9adf4765f091c5fe404975f844006964a71299" +"checksum inventory-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2869bf972e998977b1cb87e60df70341d48e48dca0823f534feb91ea44adaf9" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -1918,6 +1993,8 @@ dependencies = [ "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ebb2c484029d695fb68a06d80e1536c68d491b3e0cf874c66abed255e831cfe" +"checksum typetag-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b63fd4799e4d0ec5cf0b055ebb8e2c3a657bbf76a84f6edc77ca60780e000204" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index 0bc79362e31..da72bb3af06 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -15,6 +15,7 @@ libc = "0.2.60" log = "0.4.8" rand = "0.7.0" time = "0.1.42" +typetag = "0.1" serde = { version = "1", features = ["derive"] } # wasmer-runtime-abi = { path = "../runtime-abi" } wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index f1a19d67938..c5a8088749d 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -49,8 +49,8 @@ pub fn generate_import_object( let state = Box::new(WasiState { fs: WasiFs::new(&preopened_files, &mapped_dirs).unwrap(), - args, - envs, + args: args.clone(), + envs: envs.clone(), }); ( diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index fccdd94809c..a4154733de7 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -14,7 +14,7 @@ use std::{ borrow::Borrow, cell::Cell, fs, - io::{self, Write}, + io::Write, path::{Path, PathBuf}, time::SystemTime, }; @@ -47,7 +47,6 @@ pub struct InodeVal { #[derive(Debug, Serialize, Deserialize)] pub enum Kind { File { - #[serde(skip)] /// the open file, if it's open handle: Option>, /// The path on the host system where the file is located @@ -107,7 +106,7 @@ impl Fd { pub const CREATE: u16 = 16; } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] /// 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 { @@ -142,9 +141,9 @@ impl WasiFs { inode_counter: Cell::new(1024), orphan_fds: HashMap::new(), - stdin: Box::new(Stdin(io::stdin())), - stdout: Box::new(Stdout(io::stdout())), - stderr: Box::new(Stderr(io::stderr())), + stdin: Box::new(Stdin), + stdout: Box::new(Stdout), + stderr: Box::new(Stderr), }; // create virtual root let root_inode = { @@ -932,44 +931,9 @@ impl WasiFs { ..__wasi_filestat_t::default() }) } - - pub(crate) fn unfreeze(bytes: &[u8]) -> Option<(WasiFs, &[u8])> { - unimplemented!() - Some((Self { - preopen_fds, - name_map, - inodes, - fd_map, - next_fd, - inode_counter, - orphan_fds, - - stdout, - stderr, - stdin - }, unimplemented!())) - } - - pub(crate) fn freeze(&self) -> Option> { - let mut out = vec![]; - // store pointer to stdout, stderr, and stdin here I guess - // hmmm - out.append(&mut bincode::serialize(&self.preopen_fds).ok()?); - out.append(&mut bincode::serialize(&self.name_map).ok()?); - out.append(&mut bincode::serialize(&self.inodes).ok()?); - out.append(&mut bincode::serialize(&self.fd_map).ok()?); - out.append(&mut bincode::serialize(&self.next_fd).ok()?); - out.append(&mut bincode::serialize(&self.inode_counter).ok()?); - out.append(&mut bincode::serialize(&self.orphan_fds).ok()?); - out.append(&mut self.stdout.to_bytes()?); - out.append(&mut self.stderr.to_bytes()?); - out.append(&mut self.stdin.to_bytes()?); - - Some(out) - } } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct WasiState { pub fs: WasiFs, pub args: Vec>, @@ -979,32 +943,12 @@ pub struct WasiState { impl WasiState { /// Turn the WasiState into bytes pub fn freeze(&self) -> Option> { - let wasi_fs = self.wasi_fs.freeze()?; - let args = bincode::serialize(&self.args).ok()?; - let envs = bincode::serialize(&self.envs).ok()?; - - Some( - wasi_fs - .into_iter() - .chain(args.into_iter()) - .chain(envs.into_iter()) - .collect(), - ) + bincode::serialize(self).ok() } /// Get a WasiState from bytes - pub fn unfreeze(bytes: &[u8], deserialize_fns: Vec>) -> Option - where - F: Fn(&[u8]) -> Option>, - { - let (wasi_fs, remaining_bytes) = WasiFs::unfreeze(bytes)?; - let (args, envs): (Vec>, Vec>) = - bincode::deserialize(remaining_bytes).ok()?; - Some(Self { - fs: wasi_fs, - args, - envs, - }) + pub fn unfreeze(bytes: &[u8]) -> Option { + bincode::deserialize(bytes).ok() } } diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 1b3c73c5a83..1032e22795a 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -1,10 +1,11 @@ /// types for use in the WASI filesystem use crate::syscalls::types::*; +use serde::{Deserialize, Serialize}; #[cfg(unix)] use std::convert::TryInto; use std::{ fs, - io::{self, Read, Seek, SeekFrom, Write}, + io::{self, Read, Seek, Write}, path::PathBuf, time::SystemTime, }; @@ -117,6 +118,7 @@ impl WasiFsError { } /// This trait relies on your file closing when it goes out of scope via `Drop` +#[typetag::serde(tag = "type")] 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) -> __wasi_timestamp_t; @@ -178,29 +180,6 @@ pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { fn get_raw_fd(&self) -> Option { None } - - /// Try to turn the file into bytes - fn to_bytes(&self) -> Option>; -} - -/// Tries to read a WASI file out of bytes -/// with default parsers for stdin/stdout/stderr and `HostFile` -pub fn wasi_file_from_bytes(bytes: &[u8]) -> Option> { - Some(if b"stdout"[..] == bytes[.."stdout".len()] { - Box::new(Stdout(std::io::stdout())) - } else if b"stderr"[..] == bytes[.."stderr".len()] { - Box::new(Stderr(std::io::stderr())) - } else if b"stdin"[..] == bytes[.."stdin".len()] { - Box::new(Stdin(std::io::stdin())) - } else if b"host_file"[..] == bytes[.."host_file".len()] { - unimplemented!(); - } else { - return None; - }) -} - -pub(crate) fn serialize_file(file: Box) -> Result, E> { - file.to_bytes().ok_or_else(|| Default::default()) } #[derive(Debug, Clone)] @@ -362,18 +341,90 @@ pub(crate) fn poll( pub trait WasiPath {} /// A thin wrapper around `std::fs::File` -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct HostFile { + #[serde(skip_serializing)] pub inner: fs::File, pub host_path: PathBuf, + flags: u16, +} + +struct HostFileVisitor; + +impl<'de> serde::de::Visitor<'de> for HostFileVisitor { + type Value = HostFile; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a HostFile struct") + } + + /*fn visit_bytes(self, v: &[u8]) -> Result { + let host_path = unimplemented!(); + let flags = unimplemented!(); + Ok(HostFile { + inner, + host_path, + flags, + }) + }*/ + + fn visit_seq(self, mut seq: V) -> Result + where + V: serde::de::SeqAccess<'de>, + { + let host_path = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; + let flags = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; + let inner = std::fs::OpenOptions::new() + .read(flags & HostFile::READ != 0) + .write(flags & HostFile::WRITE != 0) + .append(flags & HostFile::APPEND != 0) + .open(&host_path) + .map_err(|_| serde::de::Error::custom("Could not open file on this system"))?; + Ok(HostFile { + inner, + host_path, + flags, + }) + } } +impl<'de> Deserialize<'de> for HostFile { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_i32(HostFileVisitor) + } +} + +// manually implement Deserialize here such that it uses actual data to open a file; +// I guess we need to add r/w flags and stuff here too.. + impl HostFile { + const READ: u16 = 1; + const WRITE: u16 = 2; + const APPEND: u16 = 4; + /// creates a new host file from a `std::fs::File` and a path - pub fn new(file: fs::File, host_path: PathBuf) -> Self { + pub fn new(file: fs::File, host_path: PathBuf, read: bool, write: bool, append: bool) -> Self { + let mut flags = 0; + if read { + flags |= Self::READ; + } + if write { + flags |= Self::WRITE; + } + if append { + flags |= Self::APPEND; + } Self { inner: file, host_path, + flags, } } @@ -416,6 +467,7 @@ impl Write for HostFile { } } +#[typetag::serde] impl WasiFile for HostFile { fn last_accessed(&self) -> u64 { self.metadata() @@ -508,23 +560,6 @@ impl WasiFile for HostFile { "HostFile::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } - - fn to_bytes(&self) -> Option> { - let mut out = vec![]; - for c in "host_file".chars() { - out.push(c as u8); - } - let cursor = self.inner.seek(SeekFrom::Current(0)).ok()?; - // store r/w/append info here? or is this handeled somewhere else? - for b in cursor.to_le_bytes().into_iter() { - out.push(*b); - } - for b in self.host_path.to_string_lossy().bytes() { - out.push(b); - } - - Some(out) - } } impl From for WasiFsError { @@ -554,57 +589,55 @@ impl From for WasiFsError { } } -#[derive(Debug)] -pub struct Stdout(pub std::io::Stdout); +#[derive(Debug, Serialize, Deserialize)] +pub struct Stdout; impl Read for Stdout { fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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", - )) + Err(io::Error::new(io::ErrorKind::Other, "can not seek stdout")) } } impl Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) + io::stdout().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.0.flush() + io::stdout().flush() } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.0.write_all(buf) + io::stdout().write_all(buf) } fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - self.0.write_fmt(fmt) + io::stdout().write_fmt(fmt) } } +#[typetag::serde] impl WasiFile for Stdout { fn last_accessed(&self) -> u64 { 0 @@ -622,7 +655,7 @@ impl WasiFile for Stdout { #[cfg(unix)] fn get_raw_fd(&self) -> Option { use std::os::unix::io::AsRawFd; - Some(self.0.as_raw_fd()) + Some(io::stdout().as_raw_fd()) } #[cfg(not(unix))] @@ -631,63 +664,57 @@ impl WasiFile for Stdout { "Stdout::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } - - fn to_bytes(&self) -> Option> { - Some("stdout".chars().map(|c| c as u8).collect()) - } } -#[derive(Debug)] -pub struct Stderr(pub std::io::Stderr); +#[derive(Debug, Serialize, Deserialize)] +pub struct Stderr; impl Read for Stderr { fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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", - )) + Err(io::Error::new(io::ErrorKind::Other, "can not seek stderr")) } } impl Write for Stderr { fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) + io::stderr().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.0.flush() + io::stderr().flush() } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.0.write_all(buf) + io::stderr().write_all(buf) } fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - self.0.write_fmt(fmt) + io::stderr().write_fmt(fmt) } } +#[typetag::serde] impl WasiFile for Stderr { fn last_accessed(&self) -> u64 { 0 @@ -705,7 +732,7 @@ impl WasiFile for Stderr { #[cfg(unix)] fn get_raw_fd(&self) -> Option { use std::os::unix::io::AsRawFd; - Some(self.0.as_raw_fd()) + Some(io::stderr().as_raw_fd()) } #[cfg(not(unix))] @@ -714,63 +741,57 @@ impl WasiFile for Stderr { "Stderr::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } - - fn to_bytes(&self) -> Option> { - Some("stderr".chars().map(|c| c as u8).collect()) - } } -#[derive(Debug)] -pub struct Stdin(pub std::io::Stdin); +#[derive(Debug, Serialize, Deserialize)] +pub struct Stdin; impl Read for Stdin { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) + io::stdin().read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - self.0.read_to_end(buf) + io::stdin().read_to_end(buf) } fn read_to_string(&mut self, buf: &mut String) -> io::Result { - self.0.read_to_string(buf) + io::stdin().read_to_string(buf) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - self.0.read_exact(buf) + io::stdin().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", - )) + Err(io::Error::new(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, + Err(io::Error::new( + io::ErrorKind::Other, "can not write to stdin", )) } fn flush(&mut self) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + 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, + Err(io::Error::new( + 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, + Err(io::Error::new( + io::ErrorKind::Other, "can not write to stdin", )) } } +#[typetag::serde] impl WasiFile for Stdin { fn last_accessed(&self) -> u64 { 0 @@ -788,7 +809,7 @@ impl WasiFile for Stdin { #[cfg(unix)] fn bytes_available(&self) -> Result { use std::os::unix::io::AsRawFd; - let host_fd = self.0.as_raw_fd(); + let host_fd = io::stdin().as_raw_fd(); let mut bytes_found = 0 as libc::c_int; let result = unsafe { libc::ioctl(host_fd, libc::FIONREAD, &mut bytes_found) }; @@ -812,7 +833,7 @@ impl WasiFile for Stdin { #[cfg(unix)] fn get_raw_fd(&self) -> Option { use std::os::unix::io::AsRawFd; - Some(self.0.as_raw_fd()) + Some(io::stdin().as_raw_fd()) } #[cfg(not(unix))] @@ -821,10 +842,6 @@ impl WasiFile for Stdin { "Stdin::get_raw_fd in WasiFile is not implemented for non-Unix-like targets yet" ); } - - fn to_bytes(&self) -> Option> { - Some("stdin".chars().map(|c| c as u8).collect()) - } } /* diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index cc568997d1c..b619c530a97 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -1704,6 +1704,9 @@ pub fn path_open( *handle = Some(Box::new(HostFile::new( wasi_try!(open_options.open(&path).map_err(|_| __WASI_EIO)), path.to_path_buf(), + true, + adjusted_rights & __WASI_RIGHT_FD_WRITE != 0, + false, ))); } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), @@ -1768,6 +1771,9 @@ pub fn path_open( __WASI_EIO })), new_file_host_path.clone(), + true, + true, + true, )) as Box) }; From 634aca8b9d8766ca736999f4538e5461130d0d7e Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 28 Aug 2019 10:50:59 -0700 Subject: [PATCH 3/7] Add WASI serializing test --- Cargo.lock | 1 + Makefile | 1 + lib/wasi-tests/Cargo.toml | 1 + lib/wasi-tests/src/lib.rs | 61 ++++++++++- lib/wasi-tests/tests/wasitests/fd_read.rs | 14 +++ lib/wasi-tests/tests/wasitests/mod.rs | 1 + lib/wasi-tests/wasitests/fd_read.out | 3 + lib/wasi-tests/wasitests/fd_read.rs | 86 +++++++++++++++ lib/wasi-tests/wasitests/fd_read.wasm | Bin 0 -> 80430 bytes lib/wasi/src/lib.rs | 2 +- lib/wasi/src/state/types.rs | 128 ++++++++++++++-------- 11 files changed, 248 insertions(+), 50 deletions(-) create mode 100644 lib/wasi-tests/tests/wasitests/fd_read.rs create mode 100644 lib/wasi-tests/wasitests/fd_read.out create mode 100644 lib/wasi-tests/wasitests/fd_read.rs create mode 100755 lib/wasi-tests/wasitests/fd_read.wasm diff --git a/Cargo.lock b/Cargo.lock index 83301a5097d..07692259699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1742,6 +1742,7 @@ dependencies = [ "wasmer-clif-backend 0.6.0", "wasmer-dev-utils 0.6.0", "wasmer-llvm-backend 0.6.0", + "wasmer-runtime 0.6.0", "wasmer-runtime-core 0.6.0", "wasmer-singlepass-backend 0.6.0", "wasmer-wasi 0.6.0", diff --git a/Makefile b/Makefile index a12d14fe591..da8fc73c35a 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,7 @@ wasitests-llvm: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1 wasitests-unit: + cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 cargo test --manifest-path lib/wasi/Cargo.toml --release wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llvm diff --git a/lib/wasi-tests/Cargo.toml b/lib/wasi-tests/Cargo.toml index 9cf2c37e1ac..0bfe102bd12 100644 --- a/lib/wasi-tests/Cargo.toml +++ b/lib/wasi-tests/Cargo.toml @@ -10,6 +10,7 @@ build = "build/mod.rs" [dependencies] wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } +wasmer-runtime = { path = "../runtime", version = "0.6.0" } wasmer-wasi = { path = "../wasi", version = "0.6.0" } # hack to get tests to work wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true } diff --git a/lib/wasi-tests/src/lib.rs b/lib/wasi-tests/src/lib.rs index 5df7576133a..4508e432e02 100644 --- a/lib/wasi-tests/src/lib.rs +++ b/lib/wasi-tests/src/lib.rs @@ -1 +1,60 @@ -// nothing to see here +#![cfg(test)] +use wasmer_runtime::{compile, Func}; +use wasmer_runtime_core::vm::Ctx; +use wasmer_wasi::{state::*, *}; + +use std::ffi::c_void; + +#[test] +fn serializing_works() { + let args = vec![ + b"program_name".into_iter().cloned().collect(), + b"arg1".into_iter().cloned().collect(), + ]; + let envs = vec![ + b"PATH=/bin".into_iter().cloned().collect(), + b"GOROOT=$HOME/.cargo/bin".into_iter().cloned().collect(), + ]; + let wasm_binary = include_bytes!("../wasitests/fd_read.wasm"); + let import_object = generate_import_object( + args.clone(), + envs.clone(), + vec![], + vec![( + ".".to_string(), + std::path::PathBuf::from("wasitests/test_fs/hamlet"), + )], + ); + let module = compile(&wasm_binary[..]) + .map_err(|e| format!("Can't compile module: {:?}", e)) + .unwrap(); + + let state_bytes = { + let instance = module.instantiate(&import_object).unwrap(); + + let start: Func<(), ()> = instance.func("_start").unwrap(); + start.call().unwrap(); + let state = get_wasi_state(instance.context()); + + assert_eq!(state.args, args); + assert_eq!(state.envs, envs); + let bytes = state.freeze().unwrap(); + + bytes + }; + + let mut instance = module.instantiate(&import_object).unwrap(); + + let wasi_state = Box::new(WasiState::unfreeze(&state_bytes).unwrap()); + + instance.context_mut().data = Box::into_raw(wasi_state) as *mut c_void; + + let second_entry: Func<(), i32> = instance.func("second_entry").unwrap(); + let result = second_entry.call().unwrap(); + assert_eq!(result, true as i32); +} + +#[allow(clippy::mut_from_ref)] +pub(crate) fn get_wasi_state(ctx: &Ctx) -> &mut WasiState { + unsafe { state::get_wasi_state(&mut *(ctx as *const Ctx as *mut Ctx)) } +} diff --git a/lib/wasi-tests/tests/wasitests/fd_read.rs b/lib/wasi-tests/tests/wasitests/fd_read.rs new file mode 100644 index 00000000000..2cd68669aed --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_read.rs @@ -0,0 +1,14 @@ +#[test] +fn test_fd_read() { + assert_wasi_output!( + "../../wasitests/fd_read.wasm", + "fd_read", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/fd_read.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/mod.rs b/lib/wasi-tests/tests/wasitests/mod.rs index a1b1d378174..2849ccd5e68 100644 --- a/lib/wasi-tests/tests/wasitests/mod.rs +++ b/lib/wasi-tests/tests/wasitests/mod.rs @@ -9,6 +9,7 @@ mod create_dir; mod envvar; mod fd_allocate; mod fd_pread; +mod fd_read; mod fd_sync; mod file_metadata; mod fs_sandbox_test; diff --git a/lib/wasi-tests/wasitests/fd_read.out b/lib/wasi-tests/wasitests/fd_read.out new file mode 100644 index 00000000000..f2b9f216979 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_read.out @@ -0,0 +1,3 @@ +SCENE IV. The Queen's closet. + + Enter QUEEN GERTRUDE and POLO \ No newline at end of file diff --git a/lib/wasi-tests/wasitests/fd_read.rs b/lib/wasi-tests/wasitests/fd_read.rs new file mode 100644 index 00000000000..c0a229f8562 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_read.rs @@ -0,0 +1,86 @@ +// Args: +// mapdir: .:wasitests/test_fs/hamlet + +// this program is used in the pause/resume test + +use std::fs; +#[cfg(target_os = "wasi")] +use std::os::wasi::prelude::AsRawFd; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[repr(C)] +struct WasiIovec { + pub buf: u32, + pub buf_len: u32, +} + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_read(fd: u32, iovs: u32, iovs_len: u32, nread: u32) -> u16; +} + +#[cfg(target_os = "wasi")] +fn read(fd: u32, iovs: &[&mut [u8]]) -> u32 { + let mut nread = 0; + let mut processed_iovs = vec![]; + + for iov in iovs { + processed_iovs.push(WasiIovec { + buf: iov.as_ptr() as usize as u32, + buf_len: iov.len() as u32, + }) + } + + unsafe { + fd_read( + fd, + processed_iovs.as_ptr() as usize as u32, + processed_iovs.len() as u32, + &mut nread as *mut u32 as usize as u32, + ); + } + nread +} + +fn main() { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs/hamlet"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + + base.push("act3/scene4.txt"); + let mut file = fs::File::open(&base).expect("Could not open file"); + let mut buffer = [0u8; 64]; + + #[cfg(target_os = "wasi")] + { + let raw_fd = file.as_raw_fd(); + assert_eq!(read(raw_fd, &[&mut buffer]), 64); + let str_val = std::str::from_utf8(&buffer[..]).unwrap().to_string(); + println!("{}", &str_val); + } + // leak the file handle so that we can use it later + std::mem::forget(file); + + #[cfg(not(target_os = "wasi"))] + { + // eh, just print the output directly + print!( + "SCENE IV. The Queen's closet. + + Enter QUEEN GERTRUDE and POLO" + ); + } +} + +#[cfg(target_os = "wasi")] +#[no_mangle] +fn second_entry() -> bool { + let raw_fd = 5; + let mut buffer = [0u8; 8]; + let result = read(raw_fd, &[&mut buffer]); + + &buffer == b"NIUS \n\nL" +} diff --git a/lib/wasi-tests/wasitests/fd_read.wasm b/lib/wasi-tests/wasitests/fd_read.wasm new file mode 100755 index 0000000000000000000000000000000000000000..7cd2891ceab648b88600bd399fa0f95bd23ab5b3 GIT binary patch literal 80430 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 UgwGX4J>>kG``ZlspPGUH0m#>$82|tP literal 0 HcmV?d00001 diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index c5a8088749d..508766cdb38 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -54,7 +54,7 @@ pub fn generate_import_object( }); ( - Box::leak(state) as *mut WasiState as *mut c_void, + Box::into_raw(state) as *mut c_void, state_destructor as fn(*mut c_void), ) }; diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 1032e22795a..94051757e54 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -1,6 +1,6 @@ /// types for use in the WASI filesystem use crate::syscalls::types::*; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Serialize}; #[cfg(unix)] use std::convert::TryInto; use std::{ @@ -349,61 +349,93 @@ pub struct HostFile { flags: u16, } -struct HostFileVisitor; - -impl<'de> serde::de::Visitor<'de> for HostFileVisitor { - type Value = HostFile; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a HostFile struct") - } - - /*fn visit_bytes(self, v: &[u8]) -> Result { - let host_path = unimplemented!(); - let flags = unimplemented!(); - Ok(HostFile { - inner, - host_path, - flags, - }) - }*/ - - fn visit_seq(self, mut seq: V) -> Result - where - V: serde::de::SeqAccess<'de>, - { - let host_path = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; - let flags = seq - .next_element()? - .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; - let inner = std::fs::OpenOptions::new() - .read(flags & HostFile::READ != 0) - .write(flags & HostFile::WRITE != 0) - .append(flags & HostFile::APPEND != 0) - .open(&host_path) - .map_err(|_| serde::de::Error::custom("Could not open file on this system"))?; - Ok(HostFile { - inner, - host_path, - flags, - }) - } -} - impl<'de> Deserialize<'de> for HostFile { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - deserializer.deserialize_i32(HostFileVisitor) + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + HostPath, + Flags, + } + + struct HostFileVisitor; + + impl<'de> de::Visitor<'de> for HostFileVisitor { + type Value = HostFile; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct HostFile") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: de::SeqAccess<'de>, + { + let host_path = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let flags = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + let inner = std::fs::OpenOptions::new() + .read(flags & HostFile::READ != 0) + .write(flags & HostFile::WRITE != 0) + .append(flags & HostFile::APPEND != 0) + .open(&host_path) + .map_err(|_| de::Error::custom("Could not open file on this system"))?; + Ok(HostFile { + inner, + host_path, + flags, + }) + } + + fn visit_map(self, mut map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut host_path = None; + let mut flags = None; + while let Some(key) = map.next_key()? { + match key { + Field::HostPath => { + if host_path.is_some() { + return Err(de::Error::duplicate_field("host_path")); + } + host_path = Some(map.next_value()?); + } + Field::Flags => { + if flags.is_some() { + return Err(de::Error::duplicate_field("flags")); + } + flags = Some(map.next_value()?); + } + } + } + let host_path = host_path.ok_or_else(|| de::Error::missing_field("host_path"))?; + let flags = flags.ok_or_else(|| de::Error::missing_field("flags"))?; + let inner = std::fs::OpenOptions::new() + .read(flags & HostFile::READ != 0) + .write(flags & HostFile::WRITE != 0) + .append(flags & HostFile::APPEND != 0) + .open(&host_path) + .map_err(|_| de::Error::custom("Could not open file on this system"))?; + Ok(HostFile { + inner, + host_path, + flags, + }) + } + } + + const FIELDS: &'static [&'static str] = &["host_path", "flags"]; + deserializer.deserialize_struct("HostFile", FIELDS, HostFileVisitor) } } -// manually implement Deserialize here such that it uses actual data to open a file; -// I guess we need to add r/w flags and stuff here too.. - impl HostFile { const READ: u16 = 1; const WRITE: u16 = 2; From a188e7f6528822155eb8580506ebb37d621c3f7e Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 28 Aug 2019 11:19:59 -0700 Subject: [PATCH 4/7] Add debug code for CI --- Makefile | 4 ++-- lib/wasi/src/lib.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index da8fc73c35a..a0e2ff85bca 100644 --- a/Makefile +++ b/Makefile @@ -70,13 +70,13 @@ wasitests-singlepass: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features singlepass -- --test-threads=1 wasitests-cranelift: wasitests-setup - cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 + cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 --nocapture wasitests-llvm: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1 wasitests-unit: - cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 + cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 --nocapture cargo test --manifest-path lib/wasi/Cargo.toml --release wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llvm diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 508766cdb38..f8294f83ecf 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -42,6 +42,8 @@ pub fn generate_import_object( ) -> ImportObject { let state_gen = move || { fn state_destructor(data: *mut c_void) { + dbg!("IN DESTRUCTOR"); + dbg!(&data); unsafe { drop(Box::from_raw(data as *mut WasiState)); } @@ -52,6 +54,7 @@ pub fn generate_import_object( args: args.clone(), envs: envs.clone(), }); + dbg!("IN CONSTRUCTOR"); ( Box::into_raw(state) as *mut c_void, From 8a2dba534ac2be43ffb577f314acfb38bc64c49e Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 28 Aug 2019 13:19:06 -0700 Subject: [PATCH 5/7] Improve error handling, fix probable lifetime issue --- lib/wasi/src/lib.rs | 8 ++++---- lib/wasi/src/state/mod.rs | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index f8294f83ecf..ce2059c6f82 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -41,20 +41,20 @@ pub fn generate_import_object( mapped_dirs: Vec<(String, PathBuf)>, ) -> ImportObject { let state_gen = move || { + // TODO: look into removing all these unnecessary clones fn state_destructor(data: *mut c_void) { - dbg!("IN DESTRUCTOR"); - dbg!(&data); unsafe { drop(Box::from_raw(data as *mut WasiState)); } } + let preopened_files = preopened_files.clone(); + let mapped_dirs = mapped_dirs.clone(); let state = Box::new(WasiState { - fs: WasiFs::new(&preopened_files, &mapped_dirs).unwrap(), + fs: WasiFs::new(&preopened_files, &mapped_dirs).expect("Could not create WASI FS"), args: args.clone(), envs: envs.clone(), }); - dbg!("IN CONSTRUCTOR"); ( Box::into_raw(state) as *mut c_void, diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index a4154733de7..64a5aee84ca 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -168,7 +168,7 @@ impl WasiFs { let inode = wasi_fs.create_virtual_root(); let fd = wasi_fs .create_fd(root_rights, root_rights, 0, Fd::READ, inode) - .expect("Could not create root fd"); + .map_err(|e| format!("Could not create root fd: {}", e))?; wasi_fs.preopen_fds.push(fd); inode }; @@ -179,7 +179,13 @@ impl WasiFs { // TODO: think about this let default_rights = 0x1FFFFFFF; // all rights let cur_dir = PathBuf::from(dir); - let cur_dir_metadata = cur_dir.metadata().expect("Could not find directory"); + let cur_dir_metadata = cur_dir.metadata().map_err(|e| { + format!( + "Could not get metadata for file {:?}: {}", + dir, + e.to_string() + ) + })?; let kind = if cur_dir_metadata.is_dir() { Kind::Dir { parent: Some(root_inode), @@ -209,7 +215,7 @@ impl WasiFs { Fd::READ | Fd::WRITE, inode, ) - .expect("Could not open fd"); + .map_err(|e| format!("Could not open fd for file {:?}: {}", dir, e))?; if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { // todo handle collisions assert!(entries.insert(dir.to_string(), inode).is_none()) @@ -221,9 +227,13 @@ impl WasiFs { debug!("Attempting to open {:?} at {}", real_dir, alias); // TODO: think about this let default_rights = 0x1FFFFFFF; // all rights - let cur_dir_metadata = real_dir - .metadata() - .expect("mapped dir not at previously verified location"); + let cur_dir_metadata = real_dir.metadata().map_err(|e| { + format!( + "Could not get metadata for file {:?}: {}", + &real_dir, + e.to_string() + ) + })?; let kind = if cur_dir_metadata.is_dir() { Kind::Dir { parent: Some(root_inode), @@ -253,7 +263,7 @@ impl WasiFs { Fd::READ | Fd::WRITE, inode, ) - .expect("Could not open fd"); + .map_err(|e| format!("Could not open fd for file {:?}: {}", &real_dir, e))?; if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { // todo handle collisions assert!(entries.insert(alias.clone(), inode).is_none()); From e4a51484a7c96da2fe50d180f67f8214990417e1 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 28 Aug 2019 13:36:30 -0700 Subject: [PATCH 6/7] Add wasitests-setup to unit testing because it runs other tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a0e2ff85bca..fa3ab7e936c 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ wasitests-cranelift: wasitests-setup wasitests-llvm: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1 -wasitests-unit: +wasitests-unit: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 --nocapture cargo test --manifest-path lib/wasi/Cargo.toml --release From 48509e97bd27e243b65442b6d8abc08623f747ca Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Wed, 28 Aug 2019 14:47:38 -0700 Subject: [PATCH 7/7] Update plugin example to use serde and typetag for WASI --- Cargo.lock | 2 ++ Cargo.toml | 4 ++++ examples/plugin.rs | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 07692259699..b4a0770a717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1498,7 +1498,9 @@ dependencies = [ "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-backend 0.6.0", "wasmer-dev-utils 0.6.0", diff --git a/Cargo.toml b/Cargo.toml index b2992bc6453..c5f0567c18a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,10 @@ wabt = "0.9.1" glob = "0.3.0" rustc_version = "0.2.3" +[dev-dependencies] +serde = { version = "1", features = ["derive"] } # used by the plugin example +typetag = "0.1" # used by the plugin example + [features] default = ["fast-tests", "wasi", "backend-cranelift"] "loader-kernel" = ["wasmer-kernel-loader"] diff --git a/examples/plugin.rs b/examples/plugin.rs index c4f1d2de3ec..19f16e5287d 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use wasmer_runtime::{func, imports, instantiate}; use wasmer_runtime_core::vm::Ctx; use wasmer_wasi::{ @@ -13,7 +14,7 @@ fn it_works(_ctx: &mut Ctx) -> i32 { 5 } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct LoggingWrapper { pub wasm_module_name: String, } @@ -86,6 +87,8 @@ impl std::io::Write for LoggingWrapper { } // the WasiFile methods aren't relevant for a write-only Stdout-like implementation +// we must use typetag and serde so that our trait objects can be safely Serialized and Deserialized +#[typetag::serde] impl WasiFile for LoggingWrapper { fn last_accessed(&self) -> u64 { 0