diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3ff1a2b85..fb13b2d3bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Blocks of changes will separated by version increments. ## **[Unreleased]** +- [#442](https://github.com/wasmerio/wasmer/pull/442) Misc. WASI FS fixes and implement readdir - [#440](https://github.com/wasmerio/wasmer/pull/440) Fix type mismatch between `wasmer_instance_call` and `wasmer_export_func_*_arity` functions in the runtime C API. - [#269](https://github.com/wasmerio/wasmer/pull/269) Add better runtime docs - [#432](https://github.com/wasmerio/wasmer/pull/432) Fix returned value of `wasmer_last_error_message` in the runtime C API diff --git a/lib/wasi/src/state.rs b/lib/wasi/src/state.rs index f1608e76846..ff137451756 100644 --- a/lib/wasi/src/state.rs +++ b/lib/wasi/src/state.rs @@ -139,8 +139,10 @@ pub enum Kind { handle: WasiFile, }, Dir { - // TODO: wrap it like WasiFile + /// Parent directory + parent: Option, /// The path on the host system where the directory is located + // TODO: wrap it like WasiFile path: PathBuf, /// The entries of a directory are lazily filled. entries: HashMap, @@ -196,6 +198,7 @@ impl WasiFs { let cur_dir_metadata = cur_dir.metadata().expect("Could not find directory"); let kind = if cur_dir_metadata.is_dir() { Kind::Dir { + parent: None, path: cur_dir.clone(), entries: Default::default(), } @@ -414,6 +417,32 @@ impl WasiFs { ); Ok(idx) } + + 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; + } + } + + path_segments.reverse(); + Some( + path_segments + .iter() + .skip(1) + .fold(path_segments.first()?.clone(), |a, b| a + "/" + b), + ) + } } #[derive(Debug)] diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index f82b9f9e876..ace1f1c8eac 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -767,14 +767,74 @@ pub fn fd_readdir( ) -> __wasi_errno_t { debug!("wasi::fd_readdir"); let memory = ctx.memory(0); + let state = get_wasi_state(ctx); + // TODO: figure out how this is supposed to work; + // is it supposed to pack the buffer full every time until it can't? or do one at a time? - if let (Ok(buf_arr_cell), Ok(bufused_cell)) = - (buf.deref(memory, 0, buf_len), bufused.deref(memory)) - { - unimplemented!("wasi::fd_readdir") + let buf_arr_cell = wasi_try!(buf.deref(memory, 0, buf_len)); + let bufused_cell = wasi_try!(bufused.deref(memory)); + let working_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let mut cur_cookie = cookie; + let mut buf_idx = 0; + + if let Kind::Dir { path, .. } = &state.fs.inodes[working_dir.inode].kind { + // we need to support multiple calls, + // simple and obviously correct implementation for now: + // maintain consistent order via lexacographic sorting + let mut entries = wasi_try!(wasi_try!(std::fs::read_dir(path).map_err(|_| __WASI_EIO)) + .collect::, _>>() + .map_err(|_| __WASI_EIO)); + entries.sort_by(|a, b| a.file_name().cmp(&b.file_name())); + + for entry in entries.iter().skip(cookie as usize) { + cur_cookie += 1; + let entry_path = entry.path(); + let entry_path_str = entry_path.to_string_lossy(); + let namlen = entry_path_str.len(); + let dirent = __wasi_dirent_t { + d_next: cur_cookie, + d_ino: 0, // TODO: inode + d_namlen: namlen as u32, + d_type: { + let file_type = wasi_try!(entry.file_type().map_err(|_| __WASI_EIO)); + // TODO: handle other file types + if file_type.is_dir() { + __WASI_FILETYPE_DIRECTORY + } else if file_type.is_file() { + __WASI_FILETYPE_REGULAR_FILE + } else if file_type.is_symlink() { + __WASI_FILETYPE_SYMBOLIC_LINK + } else { + __WASI_FILETYPE_UNKNOWN + } + }, + }; + let dirent_bytes = dirent_to_le_bytes(&dirent); + let upper_limit = std::cmp::min( + buf_len as usize - buf_idx, + std::mem::size_of::<__wasi_dirent_t>(), + ); + for i in 0..upper_limit { + buf_arr_cell[i + buf_idx].set(dirent_bytes[i]); + } + buf_idx += upper_limit; + if upper_limit != std::mem::size_of::<__wasi_dirent_t>() { + break; + } + let upper_limit = std::cmp::min(buf_len as usize - buf_idx, namlen); + for (i, b) in entry_path_str.bytes().take(upper_limit).enumerate() { + buf_arr_cell[i + buf_idx].set(b); + } + buf_idx += upper_limit; + if upper_limit != namlen { + break; + } + } } else { - __WASI_EFAULT + return __WASI_ENOTDIR; } + bufused_cell.set(buf_idx as u32); + __WASI_ESUCCESS } /// ### `fd_renumber()` @@ -795,6 +855,7 @@ pub fn fd_renumber(ctx: &mut Ctx, from: __wasi_fd_t, to: __wasi_fd_t) -> __wasi_ }; state.fs.fd_map.insert(to, new_fd_entry); + state.fs.fd_map.remove(&from); __WASI_ESUCCESS } @@ -1023,7 +1084,7 @@ pub fn path_create_directory( wasi_try!(std::fs::create_dir(&path).map_err(|_| __WASI_EIO)); let kind = Kind::Dir { - //parent: Some(working_dir.inode), + parent: Some(working_dir.inode), path: path.clone(), entries: Default::default(), }; @@ -1116,6 +1177,7 @@ pub fn path_filestat_get( return __WASI_ENOTDIR; } let kind = Kind::Dir { + parent: Some(inode), path: std::path::PathBuf::from(&segment), entries: Default::default(), }; @@ -1175,6 +1237,7 @@ pub fn path_filestat_get( is_preopened: false, // is this correct? name: last_segment.clone(), kind: Kind::Dir { + parent: Some(inode), path: std::path::PathBuf::from(&last_segment), entries: Default::default(), }, @@ -1359,7 +1422,10 @@ pub fn path_open( } let mut cur_dir_inode = working_dir.inode; - let mut cumulative_path = std::path::PathBuf::from("."); + let mut cumulative_path = std::path::PathBuf::from(wasi_try!(state + .fs + .get_base_path_for_directory(working_dir.inode) + .ok_or(__WASI_EIO))); // traverse path if path_vec.len() > 1 { @@ -1389,11 +1455,13 @@ pub fn path_open( }; // TODO: handle __WASI_O_TRUNC on directories + dbg!(&cumulative_path); // TODO: refactor and reuse let cur_file_metadata = wasi_try!(cumulative_path.metadata().map_err(|_| __WASI_EINVAL)); let kind = if cur_file_metadata.is_dir() { Kind::Dir { + parent: Some(cur_dir_inode), path: cumulative_path.clone(), entries: Default::default(), } @@ -1455,41 +1523,57 @@ 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)); // 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 kind = if file_metadata.is_dir() { + // special dir logic + Kind::Dir { + parent: Some(cur_dir_inode), + path: file_path.clone(), + entries: Default::default(), + } + } else { + // file is not a dir + 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", + &file_path + ); + open_options.create(true) + } else { + open_options + }; + let open_options = if o_flags & __WASI_O_TRUNC != 0 { + debug!("File {:?} will be truncated when opened", &file_path); + open_options.truncate(true) + } else { + open_options + }; + debug!("Opening host file {:?}", &file_path); + let real_open_file = wasi_try!(open_options.open(&file_path).map_err(|e| { + dbg!(e); + __WASI_EIO + })); + + real_open_file }; - let real_open_file = - wasi_try!(open_options.open(&file_path).map_err(|_| __WASI_EIO)); - debug!("Opening host file {}", &path_string); - - real_open_file + Kind::File { + handle: WasiFile::HostFile(real_opened_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), - }, + kind, }); + // reborrow to insert entry if let Kind::Dir { entries, .. } = &mut state.fs.inodes[working_dir.inode].kind { entries.insert(file_name.clone(), new_inode); diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index 636e1858e17..d484db08b55 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -43,6 +43,31 @@ pub struct __wasi_dirent_t { pub d_type: __wasi_filetype_t, } +unsafe impl ValueType for __wasi_dirent_t {} + +pub fn dirent_to_le_bytes(ent: &__wasi_dirent_t) -> Vec { + use std::mem::transmute; + let mut out = Vec::with_capacity(std::mem::size_of::<__wasi_dirent_t>()); + let bytes: [u8; 8] = unsafe { transmute(ent.d_next.to_le()) }; + for &b in &bytes { + out.push(b); + } + let bytes: [u8; 8] = unsafe { transmute(ent.d_ino.to_le()) }; + for &b in &bytes { + out.push(b); + } + let bytes: [u8; 4] = unsafe { transmute(ent.d_namlen.to_le()) }; + for &b in &bytes { + out.push(b); + } + out.push(ent.d_type); + out.push(0); + out.push(0); + out.push(0); + assert_eq!(out.len(), std::mem::size_of::<__wasi_dirent_t>()); + out +} + pub type __wasi_errno_t = u16; pub const __WASI_ESUCCESS: u16 = 0; pub const __WASI_E2BIG: u16 = 1;