diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index dac093f5c..c08428dbe 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/state.rs b/lib/wasi/src/state.rs index 8e186011d..43b2c9399 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -7,14 +7,89 @@ 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; +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), + } + } +} + pub struct InodeVal { pub stat: __wasi_filestat_t, pub is_preopened: bool, @@ -25,10 +100,10 @@ pub struct InodeVal { #[allow(dead_code)] 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 +115,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 +125,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 +134,58 @@ 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; + let cur_file: fs::File = fs::File::open(file).expect("Could not find file"); + let kind = if cur_file.metadata().unwrap().is_dir() { + // it seems bad to open every file recursively; can do it lazily though + Kind::Dir { + handle: WasiFile::HostFile(cur_file), + entries: Default::default(), + } + } else { + /*Kind::File { + handle: WasiFile::HostFile(cur_file), + }*/ + return Err(format!( + "WASI only supports pre-opened directories right now; found \"{}\"", + file + )); + }; + let inode_val = InodeVal { + stat: __wasi_filestat_t::default(), + is_preopened: true, + // this is incorrect + name: file.clone(), + kind, + }; + let inode = wasi_fs.inodes.insert(inode_val); + 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,7 +302,9 @@ 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)?; - Ok(__wasi_fdstat_t { + debug!("fdstat: {:?}", fd); + + dbg!(Ok(__wasi_fdstat_t { fs_filetype: match self.inodes[fd.inode].kind { Kind::File { .. } => __WASI_FILETYPE_REGULAR_FILE, Kind::Dir { .. } => __WASI_FILETYPE_DIRECTORY, @@ -205,12 +314,13 @@ impl WasiFs { fs_flags: fd.flags, fs_rights_base: fd.rights, fs_rights_inheriting: fd.rights, // 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 { @@ -272,6 +382,19 @@ impl WasiFs { ); Ok(idx) } + + /*pub fn create_file_at_fd( + &mut self, + parent: __wasi_fd_t, + path: String, + fs_rights_base: __wasi_rights_t, + fs_rights_inheriting: __wasi_rights_t, + fs_flags: fs_flags, + ) -> Result<__wasi_fd_t, __wasi_errno_t> { + + let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + Ok() + }*/ } pub struct WasiState<'a> { diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 15f0f2679..45b626346 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"))] @@ -335,7 +335,7 @@ pub fn fd_fdstat_get( let buf = wasi_try!(buf.deref(memory)); buf.set(stat); - __WASI_EFAULT + __WASI_ESUCCESS } /// ### `fd_fdstat_set_flags()` @@ -532,27 +532,26 @@ 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)); - 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 + 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 { + for (i, c) in inode_val.name.bytes().enumerate() { + path_chars[i].set(c); + } __WASI_ESUCCESS } else { - __WASI_ENOTDIR + __WASI_EOVERFLOW } } else { - __WASI_EFAULT + __WASI_ENOTDIR } } @@ -612,7 +611,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 { .. } => { @@ -670,14 +669,18 @@ pub fn fd_read( for iov in iovs_arr_cell { let iov_inner = iov.get(); - let bytes = iov_inner.buf.deref(memory, 0, iov_inner.buf_len)?; + let bytes = iov_inner.buf.deref(memory, 0, dbg!(iov_inner.buf_len))?; let mut raw_bytes: &mut [u8] = unsafe { &mut *(bytes as *const [_] as *mut [_] as *mut [u8]) }; - bytes_read += reader.read(raw_bytes).map_err(|_| __WASI_EIO)? as u32; + bytes_read += dbg!(reader.read(raw_bytes).map_err(|e| { + dbg!(e); + __WASI_EIO + })? as u32); } Ok(bytes_read) } + debug!("think-fish"); let bytes_read = match fd { 0 => { let stdin = io::stdin(); @@ -699,7 +702,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; @@ -716,7 +722,7 @@ pub fn fd_read( } }; - nread_cell.set(bytes_read); + nread_cell.set(dbg!(bytes_read)); __WASI_ESUCCESS } @@ -798,7 +804,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)); @@ -809,9 +815,11 @@ pub fn fd_seek( return __WASI_EACCES; } + debug!("Hmml"); + // TODO: handle case if fd is a dir? match whence { - __WASI_WHENCE_CUR => fd_entry.offset = (fd_entry.offset as i64 + offset) as u64, + __WASI_WHENCE_CUR => fd_entry.offset = (dbg!(fd_entry.offset) as i64 + offset) as u64, __WASI_WHENCE_END => unimplemented!(), __WASI_WHENCE_SET => fd_entry.offset = offset as u64, _ => return __WASI_EINVAL, @@ -920,7 +928,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; @@ -937,7 +949,7 @@ pub fn fd_write( } }; - nwritten_cell.set(bytes_written); + nwritten_cell.set(dbg!(bytes_written)); __WASI_ESUCCESS } @@ -1209,12 +1221,77 @@ pub fn path_open( // entry does not exist in parent directory // check to see if we should create it if o_flags & __WASI_O_CREAT != 0 { + // REVIEW: the code in this branch was written while very tired // insert in to directory and set values //entries.insert(path_segment[0], ) - unimplemented!() + + let real_opened_file = wasi_try!(::std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&path_vec[0]) + .map_err(|_| __WASI_EIO)); + debug!("Creating host file {}", &path_vec[0]); + let new_inode = state.fs.inodes.insert(InodeVal { + stat: __wasi_filestat_t::default(), + is_preopened: false, + name: path_vec[0].clone(), + kind: Kind::File { + handle: WasiFile::HostFile(real_opened_file), + }, + }); + // reborrow + if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind + { + entries.insert(path_vec[0].clone(), new_inode); + } + let new_fd = wasi_try!(state.fs.create_fd( + fs_rights_base, + fs_rights_inheriting, + fs_flags, + new_inode, + )); + + new_fd + + // TODO: technically this could just not be lazily loaded... + /*wasi_try!(state.fs.create_file_at_fd( + dirfd, + path_vec[0], + fs_rights_base, + fs_rights_inheriting, + fs_flags + ));*/ } else { - // no entry and can't create it - return __WASI_ENOENT; + // attempt to load it + let real_opened_file = wasi_try!(::std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(&path_vec[0]) + .map_err(|_| __WASI_ENOENT)); + + debug!("Opening host file {}", &path_vec[0]); + let new_inode = state.fs.inodes.insert(InodeVal { + stat: __wasi_filestat_t::default(), + is_preopened: false, + name: path_vec[0].clone(), + kind: Kind::File { + handle: WasiFile::HostFile(real_opened_file), + }, + }); + // reborrow + if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind + { + entries.insert(path_vec[0].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 { diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index b6f7bada2..5afd3148f 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 345503558..b0676a074 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -66,9 +66,13 @@ struct Run { #[structopt(name = "--", raw(multiple = "true"))] args: Vec, - /// Emscripten symbol map - #[structopt(long = "em-symbol-map", parse(from_os_str))] + /// Emscripten symbol ma>p + #[structopt(long = "em-symbol-map", parse(from_os_str), group = "emscripten")] em_symbol_map: Option, + + /// WASI pre-opened file + #[structopt(long = "pre-open", group = "wasi")] + pre_opened_files: Vec, } #[derive(Debug, StructOpt)] @@ -248,6 +252,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { env::vars() .map(|(k, v)| format!("{}={}", k, v).into_bytes()) .collect(), + options.pre_opened_files.clone(), ), None, )