Browse Source

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
pull/502/head
Marcin Mielniczuk 5 years ago
committed by Jakub Konka
parent
commit
12972c7fd3
  1. 1
      build.rs
  2. 7
      src/error.rs
  3. 43
      src/hostcalls_impl/fs.rs
  4. 216
      src/sys/unix/dir.rs
  5. 125
      src/sys/unix/linux/hostcalls_impl.rs
  6. 22
      src/sys/unix/linux/mod.rs
  7. 2
      src/sys/unix/mod.rs
  8. 132
      src/sys/windows/hostcalls_impl/fs.rs

1
build.rs

@ -197,6 +197,7 @@ mod wasm_tests {
"dangling_symlink" => true, "dangling_symlink" => true,
"symlink_loop" => true, "symlink_loop" => true,
"truncation_rights" => true, "truncation_rights" => true,
"path_rename_trailing_slashes" => true,
"fd_readdir" => true, "fd_readdir" => true,
"poll_oneoff" => true, "poll_oneoff" => true,
_ => false, _ => false,

7
src/error.rs

@ -5,6 +5,7 @@ use failure::Fail;
use std::convert::Infallible; use std::convert::Infallible;
use std::fmt; use std::fmt;
use std::num::TryFromIntError; use std::num::TryFromIntError;
use std::str;
#[derive(Clone, Copy, Debug, Fail, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Fail, Eq, PartialEq)]
#[repr(u16)] #[repr(u16)]
@ -143,6 +144,12 @@ impl From<Infallible> for Error {
} }
} }
impl From<str::Utf8Error> for Error {
fn from(_: str::Utf8Error) -> Self {
Self::Wasi(WasiError::EILSEQ)
}
}
#[cfg(windows)] #[cfg(windows)]
impl From<winx::winerror::WinError> for Error { impl From<winx::winerror::WinError> for Error {
fn from(err: winx::winerror::WinError) -> Self { fn from(err: winx::winerror::WinError) -> Self {

43
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 crate::{helpers, host, wasi, wasi32, Error, Result};
use filetime::{set_file_handle_times, FileTime}; use filetime::{set_file_handle_times, FileTime};
use log::trace; use log::trace;
use std::convert::TryInto;
use std::fs::File; use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub(crate) unsafe fn fd_close(wasi_ctx: &mut WasiCtx, fd: wasi::__wasi_fd_t) -> Result<()> { 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 *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<Vec<u8>> {
use std::slice;
let name = self.name.as_bytes();
let namlen = name.len();
let dirent_size = mem::size_of::<wasi::__wasi_dirent_t>();
let offset = dirent_size.checked_add(namlen).ok_or(Error::EOVERFLOW)?;
let mut raw = Vec::<u8>::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)
}
}

216
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<libc::DIR>);
impl Dir {
/// Converts from a descriptor-based object, closing the descriptor on success or failure.
#[inline]
pub(crate) fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
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<Self> {
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<Entry>;
fn next(&mut self) -> Option<Self::Item> {
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::<dirent>::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<Entry>;
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()
}
}

125
src/sys/unix/linux/hostcalls_impl.rs

@ -1,12 +1,12 @@
use super::super::dir::{Dir, Entry, SeekLoc};
use super::osfile::OsFile; use super::osfile::OsFile;
use crate::hostcalls_impl::PathGet; use crate::hostcalls_impl::{Dirent, PathGet};
use crate::sys::host_impl; use crate::sys::host_impl;
use crate::sys::unix::str_to_cstring; use crate::sys::unix::str_to_cstring;
use crate::{wasi, Error, Result}; use crate::{wasi, Error, Result};
use nix::libc::{self, c_long, c_void}; use log::trace;
use std::convert::TryInto; use std::convert::TryInto;
use std::fs::File; use std::fs::File;
use std::mem::MaybeUninit;
use std::os::unix::prelude::AsRawFd; use std::os::unix::prelude::AsRawFd;
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { 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( pub(crate) fn fd_readdir_impl(
os_file: &mut OsFile, fd: &File,
host_buf: &mut [u8],
cookie: wasi::__wasi_dircookie_t, cookie: wasi::__wasi_dircookie_t,
) -> Result<usize> { ) -> Result<impl Iterator<Item = Result<Dirent>>> {
use libc::{dirent, fdopendir, readdir_r, rewinddir, seekdir}; // We need to duplicate the fd, because `opendir(3)`:
// After a successful call to fdopendir(), fd is used internally by the implementation,
let host_buf_ptr = host_buf.as_mut_ptr(); // and should not otherwise be used by the application.
let host_buf_len = host_buf.len(); // `opendir(3p)` also says that it's undefined behavior to
let dir = unsafe { fdopendir(os_file.as_raw_fd()) }; // modify the state of the fd in a different way than by accessing DIR*.
if dir.is_null() { //
return Err(host_impl::errno_from_nix(nix::errno::Errno::last())); // Still, rewinddir will be needed because the two file descriptors
} // share progress. But we can safely execute closedir now.
let fd = fd.try_clone()?;
if cookie != wasi::__WASI_DIRCOOKIE_START { let mut dir = Dir::from(fd)?;
unsafe { seekdir(dir, cookie as c_long) };
// 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 { } else {
// If cookie set to __WASI_DIRCOOKIE_START, rewind the dir ptr trace!(" | fd_readdir: doing seekdir to {}", cookie);
// to the start of the stream. let loc = unsafe { SeekLoc::from_raw(cookie as i64) };
unsafe { rewinddir(dir) }; dir.seek(loc);
} }
let mut entry_buf = MaybeUninit::<dirent>::uninit(); Ok(dir.into_iter().map(|entry| {
let mut left = host_buf_len; let entry: Entry = entry?;
let mut host_buf_offset: usize = 0; Ok(Dirent {
while left > 0 { name: entry // TODO can we reuse path_from_host for CStr?
let mut host_entry: *mut dirent = std::ptr::null_mut(); .file_name()
.to_str()?
// TODO .to_owned(),
// `readdir_r` syscall is being deprecated so we should look into ino: entry.ino(),
// replacing it with `readdir` call instead. ftype: entry.file_type().into(),
// Also, `readdir_r` returns a positive int on failure, and doesn't cookie: entry.seek_loc().to_raw().try_into()?,
// 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);
let name_len = entry.d_namlen.try_into()?; // This should actually be common code with Windows,
let required_space = std::mem::size_of_val(&entry) + name_len; // but there's BSD stuff remaining
if required_space > left { pub(crate) fn fd_readdir(
os_file: &mut OsFile,
mut host_buf: &mut [u8],
cookie: wasi::__wasi_dircookie_t,
) -> Result<usize> {
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; 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( pub(crate) fn fd_advise(

22
src/sys/unix/linux/mod.rs

@ -26,29 +26,7 @@ pub(crate) mod fdentry_impl {
} }
pub(crate) mod host_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) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC;
pub(crate) fn dirent_from_host(
host_entry: &nix::libc::dirent,
) -> Result<wasi::__wasi_dirent_t> {
let mut entry = unsafe { std::mem::zeroed::<wasi::__wasi_dirent_t>() };
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 { pub(crate) mod fs_helpers {

2
src/sys/unix/mod.rs

@ -2,6 +2,8 @@ pub(crate) mod fdentry_impl;
pub(crate) mod host_impl; pub(crate) mod host_impl;
pub(crate) mod hostcalls_impl; pub(crate) mod hostcalls_impl;
mod dir;
#[cfg(any( #[cfg(any(
target_os = "macos", target_os = "macos",
target_os = "netbsd", target_os = "netbsd",

132
src/sys/windows/hostcalls_impl/fs.rs

@ -4,17 +4,19 @@ use super::fs_helpers::*;
use crate::ctx::WasiCtx; use crate::ctx::WasiCtx;
use crate::fdentry::FdEntry; use crate::fdentry::FdEntry;
use crate::helpers::systemtime_to_timestamp; 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::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::sys::hostcalls_impl::fs_helpers::PathGetExt;
use crate::{wasi, Error, Result}; use crate::{wasi, Error, Result};
use log::{debug, trace};
use std::convert::TryInto; use std::convert::TryInto;
use std::fs::{File, Metadata, OpenOptions}; use std::fs::{File, Metadata, OpenOptions};
use std::io::{self, Seek, SeekFrom}; use std::io::{self, Seek, SeekFrom};
use std::os::windows::fs::{FileExt, OpenOptionsExt}; use std::os::windows::fs::{FileExt, OpenOptionsExt};
use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; use std::os::windows::prelude::{AsRawHandle, FromRawHandle};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use winx::file::{AccessMode, Flags};
fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> { fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
// get current cursor position // get current cursor position
@ -166,12 +168,117 @@ pub(crate) fn path_open(
.map_err(Into::into) .map_err(Into::into)
} }
fn dirent_from_path<P: AsRef<Path>>(
path: P,
name: &str,
cookie: wasi::__wasi_dircookie_t,
) -> Result<Dirent> {
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<impl Iterator<Item = Result<Dirent>>> {
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( pub(crate) fn fd_readdir(
fd: &mut OsFile, os_file: &mut OsFile,
host_buf: &mut [u8], mut host_buf: &mut [u8],
cookie: wasi::__wasi_dircookie_t, cookie: wasi::__wasi_dircookie_t,
) -> Result<usize> { ) -> Result<usize> {
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<usize> { pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> Result<usize> {
@ -288,7 +395,7 @@ pub(crate) fn device_id(file: &File, _metadata: &Metadata) -> io::Result<u64> {
Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into()) Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into())
} }
pub(crate) fn file_serial_no(file: &File, _metadata: &Metadata) -> io::Result<u64> { pub(crate) fn file_serial_no(file: &File) -> io::Result<u64> {
let info = winx::file::get_fileinfo(file)?; let info = winx::file::get_fileinfo(file)?;
let high = info.nFileIndexHigh; let high = info.nFileIndexHigh;
let low = info.nFileIndexLow; let low = info.nFileIndexLow;
@ -304,19 +411,18 @@ pub(crate) fn fd_filestat_get_impl(file: &std::fs::File) -> Result<wasi::__wasi_
let metadata = file.metadata()?; let metadata = file.metadata()?;
Ok(wasi::__wasi_filestat_t { Ok(wasi::__wasi_filestat_t {
st_dev: device_id(file, &metadata)?, st_dev: device_id(file, &metadata)?,
st_ino: file_serial_no(file, &metadata)?, st_ino: file_serial_no(file)?,
st_nlink: num_hardlinks(file, &metadata)?.try_into()?, // u64 doesn't fit into u32 st_nlink: num_hardlinks(file, &metadata)?.try_into()?, // u64 doesn't fit into u32
st_size: metadata.len(), st_size: metadata.len(),
st_atim: systemtime_to_timestamp(metadata.accessed()?)?, st_atim: systemtime_to_timestamp(metadata.accessed()?)?,
st_ctim: change_time(file, &metadata)?.try_into()?, // i64 doesn't fit into u64 st_ctim: change_time(file, &metadata)?.try_into()?, // i64 doesn't fit into u64
st_mtim: systemtime_to_timestamp(metadata.modified()?)?, st_mtim: systemtime_to_timestamp(metadata.modified()?)?,
st_filetype: filetype(file, &metadata)?.to_wasi(), st_filetype: filetype_from_std(&metadata.file_type()).to_wasi(),
}) })
} }
fn filetype(_file: &File, metadata: &Metadata) -> Result<FileType> { pub(crate) fn filetype_from_std(ftype: &std::fs::FileType) -> FileType {
let ftype = metadata.file_type(); if ftype.is_file() {
let ret = if ftype.is_file() {
FileType::RegularFile FileType::RegularFile
} else if ftype.is_dir() { } else if ftype.is_dir() {
FileType::Directory FileType::Directory
@ -324,9 +430,7 @@ fn filetype(_file: &File, metadata: &Metadata) -> Result<FileType> {
FileType::Symlink FileType::Symlink
} else { } else {
FileType::Unknown FileType::Unknown
}; }
Ok(ret)
} }
pub(crate) fn path_filestat_get( pub(crate) fn path_filestat_get(

Loading…
Cancel
Save