diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b4051de1a5..ad600bdc154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,11 @@ Blocks of changes will separated by version increments. ## **[Unreleased]** -- *empty* +- [#451](https://github.com/wasmerio/wasmer/pull/451) Add `--mapdir=src:dest` flag to rename host directories in the guest context +- [#457](https://github.com/wasmerio/wasmer/pull/457) Implement file metadata for WASI, fix bugs in WASI clock code for Unix platforms ## 0.4.2 - 2019-05-16 -- [#457](https://github.com/wasmerio/wasmer/pull/457) Implement file metadata for WASI, fix bugs in WASI clock code for Unix platforms - [#416](https://github.com/wasmerio/wasmer/pull/416) Remote code loading framework - [#449](https://github.com/wasmerio/wasmer/pull/449) Fix bugs: opening host files in filestat and opening with write permissions unconditionally in path_open - [#442](https://github.com/wasmerio/wasmer/pull/442) Misc. WASI FS fixes and implement readdir diff --git a/examples/plugin.rs b/examples/plugin.rs index dbb06fe6165..c706c99e5d1 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -17,7 +17,7 @@ fn main() { )); // WASI imports - let mut base_imports = generate_import_object(vec![], vec![], vec![]); + let mut base_imports = generate_import_object(vec![], vec![], vec![], vec![]); // env is the default namespace for extern functions let custom_imports = imports! { "env" => { diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 86f9ceaac4a..8621dc55e8b 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -14,6 +14,7 @@ use self::state::{WasiFs, WasiState}; use self::syscalls::*; use std::ffi::c_void; +use std::path::PathBuf; pub use self::utils::is_wasi_module; @@ -29,6 +30,7 @@ pub fn generate_import_object( args: Vec>, envs: Vec>, preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, ) -> ImportObject { let state_gen = move || { fn state_destructor(data: *mut c_void) { @@ -38,7 +40,7 @@ pub fn generate_import_object( } let state = Box::new(WasiState { - fs: WasiFs::new(&preopened_files).unwrap(), + fs: WasiFs::new(&preopened_files, &mapped_dirs).unwrap(), args: &args[..], envs: &envs[..], }); diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state.rs index b2820b20926..979d63d4d98 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -175,21 +175,20 @@ pub struct WasiFs { } impl WasiFs { - pub fn new(preopened_dirs: &[String]) -> Result { - /*let repo = RepoOpener::new() - .create(true) - .open("mem://wasmer-test-fs", "") - .map_err(|e| e.to_string())?;*/ + pub fn new( + preopened_dirs: &[String], + mapped_dirs: &[(String, PathBuf)], + ) -> Result { debug!("wasi::fs::inodes"); let inodes = Arena::new(); let mut wasi_fs = Self { - //repo: repo, name_map: HashMap::new(), inodes: inodes, fd_map: HashMap::new(), next_fd: Cell::new(3), inode_counter: Cell::new(1000), }; + debug!("wasi::fs::preopen_dirs"); for dir in preopened_dirs { debug!("Attempting to preopen {}", &dir); // TODO: think about this @@ -218,6 +217,36 @@ impl WasiFs { .create_fd(default_rights, default_rights, 0, inode) .expect("Could not open fd"); } + debug!("wasi::fs::mapped_dirs"); + for (alias, real_dir) in mapped_dirs { + debug!("Attempting to open {:?} at {}", dest_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 kind = if cur_dir_metadata.is_dir() { + Kind::Dir { + parent: None, + path: real_dir.clone(), + entries: Default::default(), + } + } else { + return Err(format!( + "WASI only supports pre-opened directories right now; found \"{:?}\"", + &real_dir, + )); + }; + // TODO: handle nested pats in `file` + let inode_val = + InodeVal::from_file_metadata(&cur_dir_metadata, alias.clone(), true, kind); + + let inode = wasi_fs.inodes.insert(inode_val); + wasi_fs.inodes[inode].stat.st_ino = wasi_fs.inode_counter.get(); + wasi_fs + .create_fd(default_rights, default_rights, 0, inode) + .expect("Could not open fd"); + } debug!("wasi::fs::end"); Ok(wasi_fs) } @@ -419,29 +448,10 @@ impl WasiFs { } pub fn get_base_path_for_directory(&self, directory: Inode) -> Option { - let mut path_segments = vec![]; - let mut cur_inode = directory; - loop { - path_segments.push(self.inodes[cur_inode].name.clone()); - - if let Kind::Dir { parent, .. } = &self.inodes[cur_inode].kind { - if let Some(p_inode) = parent { - cur_inode = *p_inode; - } else { - break; - } - } else { - return None; - } + if let Kind::Dir { path, .. } = &self.inodes[directory].kind { + return Some(path.to_string_lossy().to_string()); } - - path_segments.reverse(); - Some( - path_segments - .iter() - .skip(1) - .fold(path_segments.first()?.clone(), |a, b| a + "/" + b), - ) + None } } diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 429ec1da80e..87610eceacc 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -795,6 +795,7 @@ pub fn fd_readdir( for entry in entries.iter().skip(cookie as usize) { cur_cookie += 1; let entry_path = entry.path(); + let entry_path = wasi_try!(entry_path.file_stem().ok_or(__WASI_EIO)); let entry_path_str = entry_path.to_string_lossy(); let namlen = entry_path_str.len(); debug!("Returning dirent for {}", entry_path_str); @@ -1486,10 +1487,11 @@ pub fn path_open( .fs .create_fd(fs_rights_base, fs_rights_inheriting, fs_flags, child)) } else { - let file_metadata = wasi_try!(file_path.metadata().map_err(|_| __WASI_ENOENT)); + debug!("Attempting to load file from host system"); + let file_metadata = file_path.metadata(); // if entry does not exist in parent directory, try to lazily // load it; possibly creating or truncating it if flags set - let kind = if file_metadata.is_dir() { + let kind = if file_metadata.is_ok() && file_metadata.unwrap().is_dir() { // special dir logic Kind::Dir { parent: Some(cur_dir_inode), diff --git a/lib/wasi/tests/wasitests/_common.rs b/lib/wasi/tests/wasitests/_common.rs index 1fbd783f42d..a3f45947e0c 100644 --- a/lib/wasi/tests/wasitests/_common.rs +++ b/lib/wasi/tests/wasitests/_common.rs @@ -32,7 +32,7 @@ macro_rules! assert_wasi_output { let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler()) .expect("WASM can't be compiled"); - let import_object = generate_import_object(vec![], vec![], vec![".".to_string()]); + let import_object = generate_import_object(vec![], vec![], vec![".".to_string()], vec![]); let instance = module .instantiate(&import_object) diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 8231c60b410..00249ebf92c 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -97,7 +97,11 @@ struct Run { #[structopt(long = "dir", multiple = true, group = "wasi")] pre_opened_directories: Vec, - // Custom code loader + /// Map a host directory to a different location for the wasm module + #[structopt(long = "mapdir", multiple = true)] + mapped_dirs: Vec, + + /// Custom code loader #[structopt( long = "loader", raw(possible_values = "LoaderName::variants()", case_insensitive = "true") @@ -227,6 +231,31 @@ fn execute_wasm(options: &Run) -> Result<(), String> { #[cfg(not(target_os = "windows"))] let disable_cache = options.disable_cache; + let mapped_dirs = { + let mut md = vec![]; + for entry in options.mapped_dirs.iter() { + if let &[alias, real_dir] = &entry.split(':').collect::>()[..] { + let pb = PathBuf::from(&real_dir); + if let Ok(pb_metadata) = pb.metadata() { + if !pb_metadata.is_dir() { + return Err(format!( + "\"{}\" exists, but it is not a directory", + &real_dir + )); + } + } else { + return Err(format!("Directory \"{}\" does not exist", &real_dir)); + } + md.push((alias.to_string(), pb)); + continue; + } + return Err(format!( + "Directory mappings must consist of two paths separate by a colon. Found {}", + &entry + )); + } + md + }; let wasm_path = &options.path; let mut wasm_binary: Vec = read_file_contents(wasm_path).map_err(|err| { @@ -416,6 +445,9 @@ fn execute_wasm(options: &Run) -> Result<(), String> { .instantiate(&import_object) .map_err(|e| format!("Can't instantiate module: {:?}", e))?; + if !mapped_dirs.is_empty() { + eprintln!("WARN: mapdir is not implemented for emscripten targets"); + } wasmer_emscripten::run_emscripten_instance( &module, &mut instance, @@ -445,6 +477,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { .map(|(k, v)| format!("{}={}", k, v).into_bytes()) .collect(), options.pre_opened_directories.clone(), + mapped_dirs, ); let instance = module