diff --git a/Cargo.lock b/Cargo.lock index 83301a509..076922596 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 a12d14fe5..da8fc73c3 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 9cf2c37e1..0bfe102bd 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 5df757613..4508e432e 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 000000000..2cd68669a --- /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 a1b1d3781..2849ccd5e 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 000000000..f2b9f2169 --- /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 000000000..c0a229f85 --- /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 000000000..7cd2891ce Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_read.wasm differ diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index c5a808874..508766cdb 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 1032e2279..94051757e 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;