From 12972c7fd3ecdf8f1e4206c3f579ebb5a6068f85 Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Thu, 7 Nov 2019 11:23:52 +0100 Subject: [PATCH] Implement fd_readdir (#116) * Reimpleent fd_readdir on Linux using quasi-nix. * Implement fd_readdir on Windows. * wip * Adapt to upstream changes. * Cleanup dir.rs * Address review * Fix macos build * host -> wasi * Partially address review, more to come later. * Address more review comments * Fix compilation on Windows --- build.rs | 1 + src/error.rs | 7 + src/hostcalls_impl/fs.rs | 43 ++++++ src/sys/unix/dir.rs | 216 +++++++++++++++++++++++++++ src/sys/unix/linux/hostcalls_impl.rs | 125 ++++++++-------- src/sys/unix/linux/mod.rs | 22 --- src/sys/unix/mod.rs | 2 + src/sys/windows/hostcalls_impl/fs.rs | 132 ++++++++++++++-- 8 files changed, 449 insertions(+), 99 deletions(-) create mode 100644 src/sys/unix/dir.rs diff --git a/build.rs b/build.rs index fc7db99b83..ec379f7364 100644 --- a/build.rs +++ b/build.rs @@ -197,6 +197,7 @@ mod wasm_tests { "dangling_symlink" => true, "symlink_loop" => true, "truncation_rights" => true, + "path_rename_trailing_slashes" => true, "fd_readdir" => true, "poll_oneoff" => true, _ => false, diff --git a/src/error.rs b/src/error.rs index 120e195c8d..7152844131 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ use failure::Fail; use std::convert::Infallible; use std::fmt; use std::num::TryFromIntError; +use std::str; #[derive(Clone, Copy, Debug, Fail, Eq, PartialEq)] #[repr(u16)] @@ -143,6 +144,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: str::Utf8Error) -> Self { + Self::Wasi(WasiError::EILSEQ) + } +} + #[cfg(windows)] impl From for Error { fn from(err: winx::winerror::WinError) -> Self { diff --git a/src/hostcalls_impl/fs.rs b/src/hostcalls_impl/fs.rs index 6f6d8027cb..ae2485be21 100644 --- a/src/hostcalls_impl/fs.rs +++ b/src/hostcalls_impl/fs.rs @@ -10,8 +10,10 @@ use crate::sys::{host_impl, hostcalls_impl}; use crate::{helpers, host, wasi, wasi32, Error, Result}; use filetime::{set_file_handle_times, FileTime}; use log::trace; +use std::convert::TryInto; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::mem; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub(crate) unsafe fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasi::__wasi_fd_t) -> Result<()> { @@ -1046,3 +1048,44 @@ impl FileType { *self as wasi::__wasi_filetype_t } } + +#[derive(Debug, Clone)] +pub(crate) struct Dirent { + pub name: String, + pub ftype: FileType, + pub ino: u64, + pub cookie: wasi::__wasi_dircookie_t, +} + +impl Dirent { + #![allow(unused)] // temporarily, until BSD catches up with this change + /// Serialize the directory entry to the format define by `__wasi_fd_readdir`, + /// so that the serialized entries can be concatenated by the implementation. + pub fn to_wasi_raw(&self) -> Result> { + use std::slice; + + let name = self.name.as_bytes(); + let namlen = name.len(); + let dirent_size = mem::size_of::(); + let offset = dirent_size.checked_add(namlen).ok_or(Error::EOVERFLOW)?; + + let mut raw = Vec::::with_capacity(offset); + raw.resize(offset, 0); + + let sys_dirent = raw.as_mut_ptr() as *mut wasi::__wasi_dirent_t; + unsafe { + *sys_dirent = wasi::__wasi_dirent_t { + d_namlen: namlen.try_into()?, + d_ino: self.ino, + d_next: self.cookie, + d_type: self.ftype.to_wasi(), + }; + } + + let sys_name = unsafe { sys_dirent.offset(1) as *mut u8 }; + let sys_name = unsafe { slice::from_raw_parts_mut(sys_name, namlen) }; + sys_name.copy_from_slice(&name); + + Ok(raw) + } +} diff --git a/src/sys/unix/dir.rs b/src/sys/unix/dir.rs new file mode 100644 index 0000000000..20b75f968d --- /dev/null +++ b/src/sys/unix/dir.rs @@ -0,0 +1,216 @@ +// Based on src/dir.rs from nix +#![allow(unused)] // temporarily, until BSD catches up with this change +use crate::hostcalls_impl::FileType; +use libc; +use nix::{errno::Errno, Error, Result}; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::{ffi, ptr}; + +#[cfg(target_os = "linux")] +use libc::{dirent64 as dirent, readdir64_r as readdir_r}; + +#[cfg(not(target_os = "linux"))] +use libc::{dirent, readdir_r}; + +/// An open directory. +/// +/// This is a lower-level interface than `std::fs::ReadDir`. Notable differences: +/// * can be opened from a file descriptor (as returned by `openat`, perhaps before knowing +/// if the path represents a file or directory). +/// * implements `AsRawFd`, so it can be passed to `fstat`, `openat`, etc. +/// The file descriptor continues to be owned by the `Dir`, so callers must not keep a `RawFd` +/// after the `Dir` is dropped. +/// * can be iterated through multiple times without closing and reopening the file +/// descriptor. Each iteration rewinds when finished. +/// * returns entries for `.` (current directory) and `..` (parent directory). +/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc +/// does). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) struct Dir(ptr::NonNull); + +impl Dir { + /// Converts from a descriptor-based object, closing the descriptor on success or failure. + #[inline] + pub(crate) fn from(fd: F) -> Result { + unsafe { Self::from_fd(fd.into_raw_fd()) } + } + + /// Converts from a file descriptor, closing it on success or failure. + unsafe fn from_fd(fd: RawFd) -> Result { + let d = libc::fdopendir(fd); + if d.is_null() { + let e = Error::last(); + libc::close(fd); + return Err(e); + }; + // Always guaranteed to be non-null by the previous check + Ok(Self(ptr::NonNull::new(d).unwrap())) + } + + /// Set the position of the directory stream, see `seekdir(3)`. + #[cfg(not(target_os = "android"))] + pub(crate) fn seek(&mut self, loc: SeekLoc) { + unsafe { libc::seekdir(self.0.as_ptr(), loc.0) } + } + + /// Reset directory stream, see `rewinddir(3)`. + pub(crate) fn rewind(&mut self) { + unsafe { libc::rewinddir(self.0.as_ptr()) } + } + + /// Get the current position in the directory stream. + /// + /// If this location is given to `Dir::seek`, the entries up to the previously returned + /// will be omitted and the iteration will start from the currently pending directory entry. + #[cfg(not(target_os = "android"))] + #[allow(dead_code)] + pub(crate) fn tell(&self) -> SeekLoc { + let loc = unsafe { libc::telldir(self.0.as_ptr()) }; + SeekLoc(loc) + } +} + +// `Dir` is not `Sync`. With the current implementation, it could be, but according to +// https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html, +// future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to +// call `readdir` simultaneously from multiple threads. +// +// `Dir` is safe to pass from one thread to another, as it's not reference-counted. +unsafe impl Send for Dir {} + +impl AsRawFd for Dir { + fn as_raw_fd(&self) -> RawFd { + unsafe { libc::dirfd(self.0.as_ptr()) } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + unsafe { libc::closedir(self.0.as_ptr()) }; + } +} + +pub(crate) struct IntoIter(Dir); +impl Iterator for IntoIter { + type Item = Result; + fn next(&mut self) -> Option { + unsafe { + // Note: POSIX specifies that portable applications should dynamically allocate a + // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 + // for the NUL byte. It doesn't look like the std library does this; it just uses + // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). + // Probably fine here too then. + // + // See `impl Iterator for ReadDir` [1] for more details. + // [1] https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs + let mut ent = std::mem::MaybeUninit::::uninit(); + let mut result = ptr::null_mut(); + if let Err(e) = Errno::result(readdir_r( + (self.0).0.as_ptr(), + ent.as_mut_ptr(), + &mut result, + )) { + return Some(Err(e)); + } + if result.is_null() { + None + } else { + assert_eq!(result, ent.as_mut_ptr(), "readdir_r specification violated"); + Some(Ok(Entry(ent.assume_init()))) + } + } + } +} + +impl IntoIterator for Dir { + type IntoIter = IntoIter; + type Item = Result; + + fn into_iter(self) -> IntoIter { + IntoIter(self) + } +} + +/// A directory entry, similar to `std::fs::DirEntry`. +/// +/// Note that unlike the std version, this may represent the `.` or `..` entries. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub(crate) struct Entry(dirent); + +pub(crate) type Type = FileType; + +impl Entry { + /// Returns the inode number (`d_ino`) of the underlying `dirent`. + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "haiku", + target_os = "ios", + target_os = "l4re", + target_os = "linux", + target_os = "macos", + target_os = "solaris" + ))] + pub(crate) fn ino(&self) -> u64 { + self.0.d_ino.into() + } + + /// Returns the inode number (`d_fileno`) of the underlying `dirent`. + #[cfg(not(any( + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "haiku", + target_os = "ios", + target_os = "l4re", + target_os = "linux", + target_os = "macos", + target_os = "solaris" + )))] + pub(crate) fn ino(&self) -> u64 { + u64::from(self.0.d_fileno) + } + + /// Returns the bare file name of this directory entry without any other leading path component. + pub(crate) fn file_name(&self) -> &ffi::CStr { + unsafe { ::std::ffi::CStr::from_ptr(self.0.d_name.as_ptr()) } + } + + /// Returns the type of this directory entry, if known. + /// + /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known; + /// notably, some Linux filesystems don't implement this. The caller should use `stat` or + /// `fstat` if this returns `None`. + pub(crate) fn file_type(&self) -> FileType { + match self.0.d_type { + libc::DT_CHR => Type::CharacterDevice, + libc::DT_DIR => Type::Directory, + libc::DT_BLK => Type::BlockDevice, + libc::DT_REG => Type::RegularFile, + libc::DT_LNK => Type::Symlink, + /* libc::DT_UNKNOWN | libc::DT_SOCK | libc::DT_FIFO */ _ => Type::Unknown, + } + } + + #[cfg(target_os = "linux")] + pub(crate) fn seek_loc(&self) -> SeekLoc { + unsafe { SeekLoc::from_raw(self.0.d_off) } + } +} + +#[cfg(not(target_os = "android"))] +#[derive(Clone, Copy, Debug)] +pub(crate) struct SeekLoc(libc::c_long); + +#[cfg(not(target_os = "android"))] +impl SeekLoc { + pub(crate) unsafe fn from_raw(loc: i64) -> Self { + Self(loc.into()) + } + + pub(crate) fn to_raw(&self) -> i64 { + self.0.into() + } +} diff --git a/src/sys/unix/linux/hostcalls_impl.rs b/src/sys/unix/linux/hostcalls_impl.rs index d0fc412544..fd267738d4 100644 --- a/src/sys/unix/linux/hostcalls_impl.rs +++ b/src/sys/unix/linux/hostcalls_impl.rs @@ -1,12 +1,12 @@ +use super::super::dir::{Dir, Entry, SeekLoc}; use super::osfile::OsFile; -use crate::hostcalls_impl::PathGet; +use crate::hostcalls_impl::{Dirent, PathGet}; use crate::sys::host_impl; use crate::sys::unix::str_to_cstring; use crate::{wasi, Error, Result}; -use nix::libc::{self, c_long, c_void}; +use log::trace; use std::convert::TryInto; use std::fs::File; -use std::mem::MaybeUninit; use std::os::unix::prelude::AsRawFd; pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { @@ -67,75 +67,74 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Resul } } -pub(crate) fn fd_readdir( - os_file: &mut OsFile, - host_buf: &mut [u8], +pub(crate) fn fd_readdir_impl( + fd: &File, cookie: wasi::__wasi_dircookie_t, -) -> Result { - use libc::{dirent, fdopendir, readdir_r, rewinddir, seekdir}; - - let host_buf_ptr = host_buf.as_mut_ptr(); - let host_buf_len = host_buf.len(); - let dir = unsafe { fdopendir(os_file.as_raw_fd()) }; - if dir.is_null() { - return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); - } - - if cookie != wasi::__WASI_DIRCOOKIE_START { - unsafe { seekdir(dir, cookie as c_long) }; +) -> Result>> { + // We need to duplicate the fd, because `opendir(3)`: + // After a successful call to fdopendir(), fd is used internally by the implementation, + // and should not otherwise be used by the application. + // `opendir(3p)` also says that it's undefined behavior to + // modify the state of the fd in a different way than by accessing DIR*. + // + // Still, rewinddir will be needed because the two file descriptors + // share progress. But we can safely execute closedir now. + let fd = fd.try_clone()?; + let mut dir = Dir::from(fd)?; + + // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, + // new items may not be returned to the caller. + // + // According to `opendir(3p)`: + // If a file is removed from or added to the directory after the most recent call + // to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry + // for that file is unspecified. + if cookie == wasi::__WASI_DIRCOOKIE_START { + trace!(" | fd_readdir: doing rewinddir"); + dir.rewind(); } else { - // If cookie set to __WASI_DIRCOOKIE_START, rewind the dir ptr - // to the start of the stream. - unsafe { rewinddir(dir) }; + trace!(" | fd_readdir: doing seekdir to {}", cookie); + let loc = unsafe { SeekLoc::from_raw(cookie as i64) }; + dir.seek(loc); } - let mut entry_buf = MaybeUninit::::uninit(); - let mut left = host_buf_len; - let mut host_buf_offset: usize = 0; - while left > 0 { - let mut host_entry: *mut dirent = std::ptr::null_mut(); - - // TODO - // `readdir_r` syscall is being deprecated so we should look into - // replacing it with `readdir` call instead. - // Also, `readdir_r` returns a positive int on failure, and doesn't - // set the errno. - let res = unsafe { readdir_r(dir, entry_buf.as_mut_ptr(), &mut host_entry) }; - if res == -1 { - return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); - } - if host_entry.is_null() { - break; - } - unsafe { entry_buf.assume_init() }; - let entry: wasi::__wasi_dirent_t = host_impl::dirent_from_host(&unsafe { *host_entry })?; - - log::debug!("fd_readdir entry = {:?}", entry); + Ok(dir.into_iter().map(|entry| { + let entry: Entry = entry?; + Ok(Dirent { + name: entry // TODO can we reuse path_from_host for CStr? + .file_name() + .to_str()? + .to_owned(), + ino: entry.ino(), + ftype: entry.file_type().into(), + cookie: entry.seek_loc().to_raw().try_into()?, + }) + })) +} - let name_len = entry.d_namlen.try_into()?; - let required_space = std::mem::size_of_val(&entry) + name_len; - if required_space > left { +// This should actually be common code with Windows, +// but there's BSD stuff remaining +pub(crate) fn fd_readdir( + os_file: &mut OsFile, + mut host_buf: &mut [u8], + cookie: wasi::__wasi_dircookie_t, +) -> Result { + let iter = fd_readdir_impl(os_file, cookie)?; + let mut used = 0; + for dirent in iter { + let dirent_raw = dirent?.to_wasi_raw()?; + let offset = dirent_raw.len(); + if host_buf.len() < offset { break; + } else { + host_buf[0..offset].copy_from_slice(&dirent_raw); + used += offset; + host_buf = &mut host_buf[offset..]; } - unsafe { - let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void - as *mut wasi::__wasi_dirent_t; - *ptr = entry; - } - host_buf_offset += std::mem::size_of_val(&entry); - let name_ptr = unsafe { *host_entry }.d_name.as_ptr(); - unsafe { - std::ptr::copy_nonoverlapping( - name_ptr as *const _, - host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut _, - name_len, - ) - }; - host_buf_offset += name_len; - left -= required_space; } - Ok(host_buf_len - left) + trace!(" | *buf_used={:?}", used); + Ok(used) } pub(crate) fn fd_advise( diff --git a/src/sys/unix/linux/mod.rs b/src/sys/unix/linux/mod.rs index bef8b85ade..3b42bf2085 100644 --- a/src/sys/unix/linux/mod.rs +++ b/src/sys/unix/linux/mod.rs @@ -26,29 +26,7 @@ pub(crate) mod fdentry_impl { } pub(crate) mod host_impl { - use super::super::host_impl::dirent_filetype_from_host; - use crate::{wasi, Error, Result}; - use std::convert::TryFrom; - pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC; - - pub(crate) fn dirent_from_host( - host_entry: &nix::libc::dirent, - ) -> Result { - let mut entry = unsafe { std::mem::zeroed::() }; - let d_namlen = unsafe { std::ffi::CStr::from_ptr(host_entry.d_name.as_ptr()) } - .to_bytes() - .len(); - if d_namlen > u32::max_value() as usize { - return Err(Error::EIO); - } - let d_type = dirent_filetype_from_host(host_entry)?; - entry.d_ino = host_entry.d_ino; - entry.d_next = u64::try_from(host_entry.d_off).map_err(|_| Error::EOVERFLOW)?; - entry.d_namlen = u32::try_from(d_namlen).map_err(|_| Error::EOVERFLOW)?; - entry.d_type = d_type; - Ok(entry) - } } pub(crate) mod fs_helpers { diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs index 2bed69f61c..91892df647 100644 --- a/src/sys/unix/mod.rs +++ b/src/sys/unix/mod.rs @@ -2,6 +2,8 @@ pub(crate) mod fdentry_impl; pub(crate) mod host_impl; pub(crate) mod hostcalls_impl; +mod dir; + #[cfg(any( target_os = "macos", target_os = "netbsd", diff --git a/src/sys/windows/hostcalls_impl/fs.rs b/src/sys/windows/hostcalls_impl/fs.rs index 0fe84e2cd7..cba0edc623 100644 --- a/src/sys/windows/hostcalls_impl/fs.rs +++ b/src/sys/windows/hostcalls_impl/fs.rs @@ -4,17 +4,19 @@ use super::fs_helpers::*; use crate::ctx::WasiCtx; use crate::fdentry::FdEntry; use crate::helpers::systemtime_to_timestamp; -use crate::hostcalls_impl::{fd_filestat_set_times_impl, FileType, PathGet}; +use crate::hostcalls_impl::{fd_filestat_set_times_impl, Dirent, FileType, PathGet}; use crate::sys::fdentry_impl::{determine_type_rights, OsFile}; -use crate::sys::host_impl; +use crate::sys::host_impl::{self, path_from_host}; use crate::sys::hostcalls_impl::fs_helpers::PathGetExt; use crate::{wasi, Error, Result}; +use log::{debug, trace}; use std::convert::TryInto; use std::fs::{File, Metadata, OpenOptions}; use std::io::{self, Seek, SeekFrom}; use std::os::windows::fs::{FileExt, OpenOptionsExt}; use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; use std::path::{Path, PathBuf}; +use winx::file::{AccessMode, Flags}; fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result { // get current cursor position @@ -166,12 +168,117 @@ pub(crate) fn path_open( .map_err(Into::into) } +fn dirent_from_path>( + path: P, + name: &str, + cookie: wasi::__wasi_dircookie_t, +) -> Result { + let path = path.as_ref(); + trace!("dirent_from_path: opening {}", path.to_string_lossy()); + + // To open a directory on Windows, FILE_FLAG_BACKUP_SEMANTICS flag must be used + let file = OpenOptions::new() + .custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits()) + .read(true) + .open(path)?; + let ty = file.metadata()?.file_type(); + Ok(Dirent { + ftype: filetype_from_std(&ty), + name: name.to_owned(), + cookie, + ino: file_serial_no(&file)?, + }) +} + +// On Windows there is apparently no support for seeking the directory stream in the OS. +// cf. https://github.com/WebAssembly/WASI/issues/61 +// +// The implementation here may perform in O(n^2) if the host buffer is O(1) +// and the number of directory entries is O(n). +// TODO: Add a heuristic optimization to achieve O(n) time in the most common case +// where fd_readdir is resumed where it previously finished +// +// Correctness of this approach relies upon one assumption: that the order of entries +// returned by `FindNextFileW` is stable, i.e. doesn't change if the directory +// contents stay the same. This invariant is crucial to be able to implement +// any kind of seeking whatsoever without having to read the whole directory at once +// and then return the data from cache. (which leaks memory) +// +// The MSDN documentation explicitly says that the order in which the search returns the files +// is not guaranteed, and is dependent on the file system. +// cf. https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew +// +// This stackoverflow post suggests that `FindNextFileW` is indeed stable and that +// the order of directory entries depends **only** on the filesystem used, but the +// MSDN documentation is not clear about this. +// cf. https://stackoverflow.com/questions/47380739/is-findfirstfile-and-findnextfile-order-random-even-for-dvd +// +// Implementation details: +// Cookies for the directory entries start from 1. (0 is reserved by wasi::__WASI_DIRCOOKIE_START) +// . gets cookie = 1 +// .. gets cookie = 2 +// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies +pub(crate) fn fd_readdir_impl( + fd: &File, + cookie: wasi::__wasi_dircookie_t, +) -> Result>> { + use winx::file::get_file_path; + + let cookie = cookie.try_into()?; + let path = get_file_path(fd)?; + // std::fs::ReadDir doesn't return . and .., so we need to emulate it + let path = Path::new(&path); + // The directory /.. is the same as / on Unix (at least on ext4), so emulate this behavior too + let parent = path.parent().unwrap_or(path); + let dot = dirent_from_path(path, ".", 1)?; + let dotdot = dirent_from_path(parent, "..", 2)?; + + trace!(" | fd_readdir impl: executing std::fs::ReadDir"); + let iter = path.read_dir()?.zip(3..).map(|(dir, no)| { + let dir: std::fs::DirEntry = dir?; + + Ok(Dirent { + name: path_from_host(dir.file_name())?, + ftype: filetype_from_std(&dir.file_type()?), + ino: File::open(dir.path()).and_then(|f| file_serial_no(&f))?, + cookie: no, + }) + }); + + // into_iter for arrays is broken and returns references instead of values, + // so we need to use vec![...] and do heap allocation + // See https://github.com/rust-lang/rust/issues/25725 + let iter = vec![dot, dotdot].into_iter().map(Ok).chain(iter); + + // Emulate seekdir(). This may give O(n^2) complexity if used with a + // small host_buf, but this is difficult to implement efficiently. + // + // See https://github.com/WebAssembly/WASI/issues/61 for more details. + Ok(iter.skip(cookie)) +} + +// This should actually be common code with Linux pub(crate) fn fd_readdir( - fd: &mut OsFile, - host_buf: &mut [u8], + os_file: &mut OsFile, + mut host_buf: &mut [u8], cookie: wasi::__wasi_dircookie_t, ) -> Result { - unimplemented!("fd_readdir") + let iter = fd_readdir_impl(os_file, cookie)?; + let mut used = 0; + for dirent in iter { + let dirent_raw = dirent?.to_wasi_raw()?; + let offset = dirent_raw.len(); + if host_buf.len() < offset { + break; + } else { + host_buf[0..offset].copy_from_slice(&dirent_raw); + used += offset; + host_buf = &mut host_buf[offset..]; + } + } + + trace!(" | *buf_used={:?}", used); + Ok(used) } pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result { @@ -288,7 +395,7 @@ pub(crate) fn device_id(file: &File, _metadata: &Metadata) -> io::Result { Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into()) } -pub(crate) fn file_serial_no(file: &File, _metadata: &Metadata) -> io::Result { +pub(crate) fn file_serial_no(file: &File) -> io::Result { let info = winx::file::get_fileinfo(file)?; let high = info.nFileIndexHigh; let low = info.nFileIndexLow; @@ -304,19 +411,18 @@ pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result Result { - let ftype = metadata.file_type(); - let ret = if ftype.is_file() { +pub(crate) fn filetype_from_std(ftype: &std::fs::FileType) -> FileType { + if ftype.is_file() { FileType::RegularFile } else if ftype.is_dir() { FileType::Directory @@ -324,9 +430,7 @@ fn filetype(_file: &File, metadata: &Metadata) -> Result { FileType::Symlink } else { FileType::Unknown - }; - - Ok(ret) + } } pub(crate) fn path_filestat_get(