diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index dac093f5c6f..c08428dbebb 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -17,7 +17,11 @@ pub use self::utils::is_wasi_module; use wasmer_runtime_core::{func, import::ImportObject, imports}; -pub fn generate_import_object(args: Vec>, envs: Vec>) -> ImportObject { +pub fn generate_import_object( + args: Vec>, + envs: Vec>, + preopened_files: Vec, +) -> ImportObject { let state_gen = move || { fn state_destructor(data: *mut c_void) { unsafe { @@ -26,7 +30,7 @@ pub fn generate_import_object(args: Vec>, envs: Vec>) -> ImportO } let state = Box::new(WasiState { - fs: WasiFs::new().unwrap(), + fs: WasiFs::new(&preopened_files).unwrap(), args: &args[..], envs: &envs[..], }); diff --git a/lib/wasi/src/ptr.rs b/lib/wasi/src/ptr.rs index da892d1bf69..85a582df0c2 100644 --- a/lib/wasi/src/ptr.rs +++ b/lib/wasi/src/ptr.rs @@ -36,10 +36,12 @@ impl WasmPtr { return Err(__WASI_EFAULT); } unsafe { - let cell_ptr = memory - .view::() - .get_unchecked((self.offset() as usize) / mem::size_of::()) - as *const _; + // clears bits below aligment amount (assumes power of 2) to align pointer + let aligner = |ptr: usize, align: usize| ptr & !(align - 1); + let cell_ptr = aligner( + memory.view::().as_ptr().add(self.offset as usize) as usize, + mem::align_of::(), + ) as *const Cell; Ok(&*cell_ptr) } } diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state.rs index 8e186011dea..d703e2032d0 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -7,14 +7,91 @@ use generational_arena::{Arena, Index as Inode}; use hashbrown::hash_map::{Entry, HashMap}; use std::{ cell::Cell, - io::{self, Write}, + fs, + io::{self, Read, Seek, Write}, time::SystemTime, }; use wasmer_runtime_core::debug; -use zbox::{init_env as zbox_init_env, File, FileType, OpenOptions, Repo, RepoOpener}; +use zbox::{init_env as zbox_init_env, FileType, OpenOptions, Repo, RepoOpener}; pub const MAX_SYMLINKS: usize = 100; +#[derive(Debug)] +pub enum WasiFile { + ZboxFile(zbox::File), + HostFile(fs::File), +} + +impl Write for WasiFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + WasiFile::ZboxFile(zbf) => zbf.write(buf), + WasiFile::HostFile(hf) => hf.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + WasiFile::ZboxFile(zbf) => zbf.flush(), + WasiFile::HostFile(hf) => hf.flush(), + } + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + match self { + WasiFile::ZboxFile(zbf) => zbf.write_all(buf), + WasiFile::HostFile(hf) => hf.write_all(buf), + } + } + + fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { + match self { + WasiFile::ZboxFile(zbf) => zbf.write_fmt(fmt), + WasiFile::HostFile(hf) => hf.write_fmt(fmt), + } + } +} + +impl Read for WasiFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + WasiFile::ZboxFile(zbf) => zbf.read(buf), + WasiFile::HostFile(hf) => hf.read(buf), + } + } + + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + match self { + WasiFile::ZboxFile(zbf) => zbf.read_to_end(buf), + WasiFile::HostFile(hf) => hf.read_to_end(buf), + } + } + + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + match self { + WasiFile::ZboxFile(zbf) => zbf.read_to_string(buf), + WasiFile::HostFile(hf) => hf.read_to_string(buf), + } + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + match self { + WasiFile::ZboxFile(zbf) => zbf.read_exact(buf), + WasiFile::HostFile(hf) => hf.read_exact(buf), + } + } +} + +impl Seek for WasiFile { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + match self { + WasiFile::ZboxFile(zbf) => zbf.seek(pos), + WasiFile::HostFile(hf) => hf.seek(pos), + } + } +} + +#[derive(Debug)] pub struct InodeVal { pub stat: __wasi_filestat_t, pub is_preopened: bool, @@ -22,13 +99,57 @@ pub struct InodeVal { pub kind: Kind, } +impl InodeVal { + // TODO: clean this up + pub fn from_file_metadata( + metadata: &std::fs::Metadata, + name: String, + is_preopened: bool, + kind: Kind, + ) -> Self { + InodeVal { + stat: __wasi_filestat_t { + st_filetype: if metadata.is_dir() { + __WASI_FILETYPE_DIRECTORY + } else { + __WASI_FILETYPE_REGULAR_FILE + }, + st_size: metadata.len(), + st_atim: metadata + .accessed() + .ok() + .and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|duration| duration.as_nanos() as u64) + .unwrap_or(0), + st_ctim: metadata + .created() + .ok() + .and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|duration| duration.as_nanos() as u64) + .unwrap_or(0), + st_mtim: metadata + .modified() + .ok() + .and_then(|sys_time| sys_time.duration_since(SystemTime::UNIX_EPOCH).ok()) + .map(|duration| duration.as_nanos() as u64) + .unwrap_or(0), + ..__wasi_filestat_t::default() + }, + is_preopened, + name, + kind, + } + } +} + #[allow(dead_code)] +#[derive(Debug)] pub enum Kind { File { - handle: File, + handle: WasiFile, }, Dir { - handle: File, + handle: WasiFile, /// The entries of a directory are lazily filled. entries: HashMap, }, @@ -40,7 +161,7 @@ pub enum Kind { }, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Fd { pub rights: __wasi_rights_t, pub rights_inheriting: __wasi_rights_t, @@ -50,7 +171,7 @@ pub struct Fd { } pub struct WasiFs { - // pub repo: Repo, + //pub repo: Repo, pub name_map: HashMap, pub inodes: Arena, pub fd_map: HashMap, @@ -59,26 +180,56 @@ pub struct WasiFs { } impl WasiFs { - pub fn new() -> Result { + pub fn new(preopened_files: &[String]) -> Result { debug!("wasi::fs::init"); zbox_init_env(); debug!("wasi::fs::repo"); - // let repo = RepoOpener::new() - // .create(true) - // .open("mem://wasmer-test-fs", "") - // .map_err(|e| e.to_string())?; + /*let repo = RepoOpener::new() + .create(true) + .open("mem://wasmer-test-fs", "") + .map_err(|e| e.to_string())?;*/ debug!("wasi::fs::inodes"); let inodes = Arena::new(); - let res = Ok(Self { - // repo: repo, + 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), - }); + }; + for file in preopened_files { + debug!("Attempting to preopen {}", &file); + // TODO: think about this + let default_rights = 0x1FFFFFFF; // all rights + let cur_file: fs::File = fs::OpenOptions::new() + .read(true) + .open(file) + .expect("Could not find file"); + let cur_file_metadata = cur_file.metadata().unwrap(); + let kind = if cur_file_metadata.is_dir() { + Kind::Dir { + handle: WasiFile::HostFile(cur_file), + entries: Default::default(), + } + } else { + return Err(format!( + "WASI only supports pre-opened directories right now; found \"{}\"", + file + )); + }; + // TODO: handle nested pats in `file` + let inode_val = + InodeVal::from_file_metadata(&cur_file_metadata, file.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"); - res + Ok(wasi_fs) } #[allow(dead_code)] @@ -195,6 +346,8 @@ impl WasiFs { pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> { let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + debug!("fdstat: {:?}", fd); + Ok(__wasi_fdstat_t { fs_filetype: match self.inodes[fd.inode].kind { Kind::File { .. } => __WASI_FILETYPE_REGULAR_FILE, @@ -204,20 +357,22 @@ impl WasiFs { }, fs_flags: fd.flags, fs_rights_base: fd.rights, - fs_rights_inheriting: fd.rights, // TODO(lachlan): Is this right? + fs_rights_inheriting: fd.rights_inheriting, // TODO(lachlan): Is this right? }) } pub fn prestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_prestat_t, __wasi_errno_t> { let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + debug!("in prestat_fd {:?}", fd); let inode_val = &self.inodes[fd.inode]; if inode_val.is_preopened { Ok(__wasi_prestat_t { pr_type: __WASI_PREOPENTYPE_DIR, u: PrestatEnum::Dir { - pr_name_len: inode_val.name.len() as u32, + // REVIEW: + pr_name_len: inode_val.name.len() as u32 + 1, } .untagged(), }) diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 15f0f26798a..455d210d424 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -8,11 +8,11 @@ pub mod windows; use self::types::*; use crate::{ ptr::{Array, WasmPtr}, - state::{Fd, Kind, WasiState, MAX_SYMLINKS}, + state::{Fd, InodeVal, Kind, WasiFile, WasiState, MAX_SYMLINKS}, }; use rand::{thread_rng, Rng}; use std::cell::Cell; -use std::io::{self, Read, Write}; +use std::io::{self, Read, Seek, Write}; use wasmer_runtime_core::{debug, memory::Memory, vm::Ctx}; #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -324,18 +324,21 @@ pub fn fd_datasync(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { pub fn fd_fdstat_get( ctx: &mut Ctx, fd: __wasi_fd_t, - buf: WasmPtr<__wasi_fdstat_t>, + buf_ptr: WasmPtr<__wasi_fdstat_t>, ) -> __wasi_errno_t { - debug!("wasi::fd_fdstat_get: fd={}", fd); + debug!( + "wasi::fd_fdstat_get: fd={}, buf_ptr={}", + fd, + buf_ptr.offset() + ); let mut state = get_wasi_state(ctx); let memory = ctx.memory(0); - let stat = wasi_try!(state.fs.fdstat(fd)); + let buf = wasi_try!(buf_ptr.deref(memory)); - let buf = wasi_try!(buf.deref(memory)); buf.set(stat); - __WASI_EFAULT + __WASI_ESUCCESS } /// ### `fd_fdstat_set_flags()` @@ -532,27 +535,36 @@ pub fn fd_prestat_dir_name( fd, path_len ); let memory = ctx.memory(0); + let path_chars = wasi_try!(path.deref(memory, 0, path_len)); + + let state = get_wasi_state(ctx); + let real_fd = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let inode_val = &state.fs.inodes[real_fd.inode]; + + // check inode-val.is_preopened? + + if let Kind::Dir { .. } = inode_val.kind { + // TODO: verify this: null termination, etc + if inode_val.name.len() <= path_len as usize { + let mut i = 0; + for c in inode_val.name.bytes() { + path_chars[i].set(c); + i += 1 + } + path_chars[i].set(0); + + debug!( + "=> result: \"{}\"", + ::std::str::from_utf8(unsafe { &*(&path_chars[..] as *const [_] as *const [u8]) }) + .unwrap() + ); - if let Ok(path_chars) = path.deref(memory, 0, path_len) { - debug!( - "=> path: {}", - path_chars - .iter() - .map(|c| c.get() as char) - .collect::() - ); - if true - /* check if dir */ - { - // get name - // write name - // if overflow __WASI_EOVERFLOW __WASI_ESUCCESS } else { - __WASI_ENOTDIR + __WASI_EOVERFLOW } } else { - __WASI_EFAULT + __WASI_ENOTDIR } } @@ -612,7 +624,7 @@ pub fn fd_pwrite( let bytes_written = match &mut inode.kind { Kind::File { handle } => { - // TODO: adjust by offset + handle.seek(::std::io::SeekFrom::Start(offset as u64)); wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) } Kind::Dir { .. } => { @@ -699,7 +711,10 @@ pub fn fd_read( let inode = &mut state.fs.inodes[fd_entry.inode]; let bytes_read = match &mut inode.kind { - Kind::File { handle } => wasi_try!(read_bytes(handle, memory, iovs_arr_cell)), + Kind::File { handle } => { + handle.seek(::std::io::SeekFrom::Start(offset as u64)); + wasi_try!(read_bytes(handle, memory, iovs_arr_cell)) + } Kind::Dir { .. } => { // TODO: verify return __WASI_EISDIR; @@ -798,7 +813,7 @@ pub fn fd_seek( whence: __wasi_whence_t, newoffset: WasmPtr<__wasi_filesize_t>, ) -> __wasi_errno_t { - debug!("wasi::fd_seek: fd={}", fd); + debug!("wasi::fd_seek: fd={}, offset={}", fd, offset); let memory = ctx.memory(0); let state = get_wasi_state(ctx); let new_offset_cell = wasi_try!(newoffset.deref(memory)); @@ -830,7 +845,7 @@ pub fn fd_seek( /// Errors: /// TODO: figure out which errors this should return /// - `__WASI_EPERM` -/// - `__WAIS_ENOTCAPABLE` +/// - `__WASI_ENOTCAPABLE` pub fn fd_sync(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_sync"); // TODO: check __WASI_RIGHT_FD_SYNC @@ -920,7 +935,11 @@ pub fn fd_write( let inode = &mut state.fs.inodes[fd_entry.inode]; let bytes_written = match &mut inode.kind { - Kind::File { handle } => wasi_try!(write_bytes(handle, memory, iovs_arr_cell)), + Kind::File { handle } => { + handle.seek(::std::io::SeekFrom::Start(offset as u64)); + + wasi_try!(write_bytes(handle, memory, iovs_arr_cell)) + } Kind::Dir { .. } => { // TODO: verify return __WASI_EISDIR; @@ -1030,7 +1049,9 @@ pub fn path_filestat_get( return __WASI_ELOOP; } } - _ => return __WASI_ENOTDIR, + _ => { + return __WASI_ENOTDIR; + } } } } @@ -1142,13 +1163,11 @@ pub fn path_open( ) -> __wasi_errno_t { debug!("wasi::path_open"); let memory = ctx.memory(0); - if path_len > 1024 /* TODO: find actual upper bound on name size (also this is a path, not a name :think-fish:) */ - { + if path_len > 1024 * 1024 { return __WASI_ENAMETOOLONG; } - // check for __WASI_RIGHT_PATH_OPEN somewhere, probably via dirfd let fd_cell = wasi_try!(fd.deref(memory)); let path_cells = wasi_try!(path.deref(memory, 0, path_len)); let state = get_wasi_state(ctx); @@ -1161,85 +1180,182 @@ pub fn path_open( let working_dir = wasi_try!(state.fs.fd_map.get(&dirfd).ok_or(__WASI_EBADF)); - // ASSUMPTION: open rights cascade down + // ASSUMPTION: open rights apply recursively if !has_rights(working_dir.rights, __WASI_RIGHT_PATH_OPEN) { return __WASI_EACCES; } - let path_vec = + let path_string = wasi_try!( - ::std::str::from_utf8(unsafe { &*(path_cells as *const [_] as *const [u8]) }) + std::str::from_utf8(unsafe { &*(path_cells as *const [_] as *const [u8]) }) .map_err(|_| __WASI_EINVAL) - ) - .split('/') - .map(|str| str.to_string()) - .collect::>(); + ); + let path = std::path::PathBuf::from(path_string); + let path_vec = wasi_try!(path + .components() + .map(|comp| { + comp.as_os_str() + .to_str() + .map(|inner_str| inner_str.to_string()) + .ok_or(__WASI_EINVAL) + }) + .collect::, __wasi_errno_t>>()); + debug!("Path vec: {:#?}", path_vec); if path_vec.is_empty() { return __WASI_EINVAL; } - let working_dir_inode = &mut state.fs.inodes[working_dir.inode]; - - let mut cur_dir = working_dir; + let mut cur_dir_inode = working_dir.inode; + let mut cumulative_path = std::path::PathBuf::from("."); - // TODO: refactor single path segment logic out and do traversing before - // as necessary - let out_fd = if path_vec.len() == 1 { - // just the file or dir - if let Kind::Dir { entries, .. } = &mut working_dir_inode.kind { - if let Some(child) = entries.get(&path_vec[0]).cloned() { - let child_inode_val = &state.fs.inodes[child]; - // early return based on flags - if o_flags & __WASI_O_EXCL != 0 { - return __WASI_EEXIST; - } - if o_flags & __WASI_O_DIRECTORY != 0 { - match &child_inode_val.kind { - Kind::Dir { .. } => (), - Kind::Symlink { .. } => unimplemented!(), - _ => return __WASI_ENOTDIR, - } - } - // do logic on child - wasi_try!(state - .fs - .create_fd(fs_rights_base, fs_rights_inheriting, fs_flags, child)) - } else { - // entry does not exist in parent directory - // check to see if we should create it - if o_flags & __WASI_O_CREAT != 0 { - // insert in to directory and set values - //entries.insert(path_segment[0], ) - unimplemented!() - } else { - // no entry and can't create it - return __WASI_ENOENT; - } - } - } else { - // working_dir is not a directory - return __WASI_ENOTDIR; - } - } else { - // traverse the pieces of the path - // TODO: lots of testing on this + // traverse path + if path_vec.len() > 1 { for path_segment in &path_vec[..(path_vec.len() - 1)] { - match &working_dir_inode.kind { + match &state.fs.inodes[cur_dir_inode].kind { Kind::Dir { entries, .. } => { if let Some(child) = entries.get(path_segment) { - unimplemented!(); + let inode_val = *child; + cur_dir_inode = inode_val; } else { - // Is __WASI_O_FLAG_CREAT recursive? - // ASSUMPTION: it's not - return __WASI_EINVAL; + // attempt to lazily load or create + if path_segment == ".." { + unimplemented!( + "\"..\" in paths in `path_open` has not been implemented yet" + ); + } + // lazily load + cumulative_path.push(path_segment); + let mut open_options = std::fs::OpenOptions::new(); + let open_options = open_options.read(true); + // ASSUMPTION: __WASI_O_CREAT applies recursively + let open_options = if o_flags & __WASI_O_CREAT != 0 { + open_options.create(true) + } else { + open_options + }; + // TODO: handle __WASI_O_TRUNC on directories + + let cur_dir = wasi_try!(open_options + .open(&cumulative_path) + .map_err(|_| __WASI_EINVAL)); + + // TODO: refactor and reuse + let cur_file_metadata = cur_dir.metadata().unwrap(); + let kind = if cur_file_metadata.is_dir() { + Kind::Dir { + handle: WasiFile::HostFile(cur_dir), + entries: Default::default(), + } + } else { + return __WASI_ENOTDIR; + }; + let inode_val = InodeVal::from_file_metadata( + &cur_file_metadata, + path_segment.clone(), + false, + kind, + ); + + let new_inode = state.fs.inodes.insert(inode_val); + let inode_idx = state.fs.inode_counter.get(); + state.fs.inode_counter.replace(inode_idx + 1); + // reborrow to insert entry + if let Kind::Dir { entries, .. } = &mut state.fs.inodes[cur_dir_inode].kind + { + assert!(entries.insert(path_segment.clone(), new_inode).is_none()); + state.fs.inodes[new_inode].stat.st_ino = state.fs.inode_counter.get(); + cur_dir_inode = new_inode; + } } } - Kind::Symlink { .. } => unimplemented!(), + Kind::Symlink { .. } => unimplemented!("Symlinks not yet supported in `path_open`"), _ => return __WASI_ENOTDIR, } } - unimplemented!() + } + + let file_name = path_vec.last().unwrap(); + + debug!( + "Looking for file {} in directory {:#?}", + file_name, cumulative_path + ); + cumulative_path.push(file_name); + let file_path = cumulative_path; + + let out_fd = if let Kind::Dir { entries, .. } = &mut state.fs.inodes[cur_dir_inode].kind { + if let Some(child) = entries.get(file_name).cloned() { + let child_inode_val = &state.fs.inodes[child]; + // early return based on flags + if o_flags & __WASI_O_EXCL != 0 { + return __WASI_EEXIST; + } + if o_flags & __WASI_O_DIRECTORY != 0 { + match &child_inode_val.kind { + Kind::Dir { .. } => (), + Kind::Symlink { .. } => { + unimplemented!("Symlinks not yet supported in path_open") + } + _ => return __WASI_ENOTDIR, + } + } + // do logic on child + wasi_try!(state + .fs + .create_fd(fs_rights_base, fs_rights_inheriting, fs_flags, child)) + } else { + // if entry does not exist in parent directory, try to lazily + // load it; possibly creating or truncating it if flags set + let real_opened_file = { + let mut open_options = std::fs::OpenOptions::new(); + let open_options = open_options.read(true).write(true); + let open_options = if o_flags & __WASI_O_CREAT != 0 { + debug!( + "File {} may be created when opened if it does not exist", + &path_string + ); + open_options.create(true) + } else { + open_options + }; + let open_options = if o_flags & __WASI_O_TRUNC != 0 { + debug!("File {} will be truncated when opened", &path_string); + open_options.truncate(true) + } else { + open_options + }; + let real_open_file = + wasi_try!(open_options.open(&file_path).map_err(|_| __WASI_EIO)); + debug!("Opening host file {}", &path_string); + + real_open_file + }; + // record lazily loaded or newly created fd + let new_inode = state.fs.inodes.insert(InodeVal { + stat: __wasi_filestat_t::default(), + is_preopened: false, + name: file_name.clone(), + kind: Kind::File { + handle: WasiFile::HostFile(real_opened_file), + }, + }); + // reborrow to insert entry + if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind { + entries.insert(file_name.clone(), new_inode); + } + let new_fd = wasi_try!(state.fs.create_fd( + fs_rights_base, + fs_rights_inheriting, + fs_flags, + new_inode, + )); + + new_fd + } + } else { + // working_dir did not match on Kind::Dir + return __WASI_ENOTDIR; }; fd_cell.set(out_fd); diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index b6f7bada2d2..5afd3148fdf 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -264,7 +264,7 @@ pub type __wasi_filedelta_t = i64; pub type __wasi_filesize_t = u64; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[repr(C)] pub struct __wasi_filestat_t { pub st_dev: __wasi_device_t, diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 414099f4067..b9606a225a7 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -80,9 +80,13 @@ struct Run { backend: Backend, /// Emscripten symbol map - #[structopt(long = "em-symbol-map", parse(from_os_str))] + #[structopt(long = "em-symbol-map", parse(from_os_str), group = "emscripten")] em_symbol_map: Option, + /// WASI pre-opened directory + #[structopt(long = "dir", multiple = true, group = "wasi")] + pre_opened_directories: Vec, + #[structopt(long = "command-name", hidden = true)] command_name: Option, @@ -316,6 +320,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { env::vars() .map(|(k, v)| format!("{}={}", k, v).into_bytes()) .collect(), + options.pre_opened_directories.clone(), ), None, )