diff --git a/Cargo.lock b/Cargo.lock index b70ea827fa0..9746f992237 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" @@ -535,6 +552,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]] @@ -555,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" @@ -629,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" @@ -1344,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" @@ -1428,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", @@ -1661,12 +1733,15 @@ 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)", + "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-runtime-core 0.6.0", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1679,6 +1754,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", @@ -1812,12 +1888,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" @@ -1829,6 +1907,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" @@ -1838,6 +1917,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" @@ -1925,6 +2006,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/Cargo.toml b/Cargo.toml index 1e24c07b65e..bbd596087c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,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/Makefile b/Makefile index d8553e592d9..007e89853b0 100644 --- a/Makefile +++ b/Makefile @@ -70,12 +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: +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 wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llvm 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 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 00000000000..7cd2891ceab Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_read.wasm differ diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index 86801f5e3e7..cbe4d8a6924 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -8,14 +8,17 @@ 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" +typetag = "0.1" +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.8" diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 2bdca703cc7..c447980255b 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -44,20 +44,23 @@ 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) { 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(), - args: &args[..], - envs: &envs[..], + fs: WasiFs::new(&preopened_files, &mapped_dirs).expect("Could not create WASI FS"), + args: args.clone(), + envs: envs.clone(), }); ( - 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/mod.rs b/lib/wasi/src/state/mod.rs index dfcf5274109..64a5aee84ca 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -8,12 +8,13 @@ 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, cell::Cell, fs, - io::{self, Write}, + io::Write, path::{Path, PathBuf}, time::SystemTime, }; @@ -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,26 +44,7 @@ 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 { /// the open file, if it's open @@ -106,16 +88,25 @@ 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, } -#[derive(Debug)] +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, 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 { @@ -150,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 = { @@ -176,8 +167,8 @@ 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) - .expect("Could not create root fd"); + .create_fd(root_rights, root_rights, 0, Fd::READ, inode) + .map_err(|e| format!("Could not create root fd: {}", e))?; wasi_fs.preopen_fds.push(fd); inode }; @@ -188,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), @@ -211,8 +208,14 @@ impl WasiFs { ) })?; let fd = wasi_fs - .create_fd(default_rights, default_rights, 0, inode) - .expect("Could not open fd"); + .create_fd( + default_rights, + default_rights, + 0, + Fd::READ | Fd::WRITE, + inode, + ) + .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()) @@ -224,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), @@ -249,8 +256,14 @@ impl WasiFs { ) })?; let fd = wasi_fs - .create_fd(default_rights, default_rights, 0, inode) - .expect("Could not open fd"); + .create_fd( + default_rights, + default_rights, + 0, + Fd::READ | Fd::WRITE, + inode, + ) + .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()); @@ -276,6 +289,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 +326,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 +768,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 +827,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 +839,7 @@ impl WasiFs { rights_inheriting, flags, offset: 0, + open_flags, inode, }, ); @@ -924,11 +943,23 @@ impl WasiFs { } } -#[derive(Debug)] -pub struct WasiState<'a> { +#[derive(Debug, Serialize, Deserialize)] +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> { + bincode::serialize(self).ok() + } + + /// Get a WasiState from bytes + pub fn unfreeze(bytes: &[u8]) -> Option { + bincode::deserialize(bytes).ok() + } } 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 85b84a811b0..0210f681463 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -1,5 +1,6 @@ /// types for use in the WASI filesystem use crate::syscalls::types::*; +use serde::{de, Deserialize, Serialize}; #[cfg(unix)] use std::convert::TryInto; use std::{ @@ -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; @@ -339,18 +341,122 @@ 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, +} + +impl<'de> Deserialize<'de> for HostFile { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[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) + } } 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, } } @@ -393,6 +499,7 @@ impl Write for HostFile { } } +#[typetag::serde] impl WasiFile for HostFile { fn last_accessed(&self) -> u64 { self.metadata() @@ -519,57 +626,55 @@ fn host_file_bytes_available(_raw_fd: i32) -> Result { unimplemented!("host_file_bytes_available not yet implemented for non-Unix-like targets. This probably means the program tried to use wasi::poll_oneoff") } -#[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 @@ -594,7 +699,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))] @@ -605,57 +710,55 @@ impl WasiFile for Stdout { } } -#[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 @@ -680,7 +783,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))] @@ -691,57 +794,55 @@ impl WasiFile for Stderr { } } -#[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 @@ -766,7 +867,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))] diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 1ac27c89be2..9801d4ac048 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -689,23 +689,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; @@ -1681,6 +1669,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 @@ -1708,10 +1697,22 @@ 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(), + true, + adjusted_rights & __WASI_RIGHT_FD_WRITE != 0, + false, ))); } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), @@ -1768,6 +1769,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| { @@ -1775,6 +1777,9 @@ pub fn path_open( __WASI_EIO })), new_file_host_path.clone(), + true, + true, + true, )) as Box) }; @@ -1806,10 +1811,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,