Fast-forward snapshot_0 to match wasi-common upstream
While we are waiting for the Rust toolchain to use the new ABI, I thought it might be useful to sync `snapshot_0` with the latest code in `wasi-common` "upstream". This mainly includes the latest refactoring effort to unify the machinery for `fd_readdir` between Linux, Windows and BSD.
This commit is contained in:
@@ -576,41 +576,6 @@ pub(crate) unsafe fn path_open(
|
|||||||
enc_fd_byref(memory, fd_out_ptr, guest_fd)
|
enc_fd_byref(memory, fd_out_ptr, guest_fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn fd_readdir(
|
|
||||||
wasi_ctx: &mut WasiCtx,
|
|
||||||
memory: &mut [u8],
|
|
||||||
fd: wasi::__wasi_fd_t,
|
|
||||||
buf: wasi32::uintptr_t,
|
|
||||||
buf_len: wasi32::size_t,
|
|
||||||
cookie: wasi::__wasi_dircookie_t,
|
|
||||||
buf_used: wasi32::uintptr_t,
|
|
||||||
) -> Result<()> {
|
|
||||||
trace!(
|
|
||||||
"fd_readdir(fd={:?}, buf={:#x?}, buf_len={}, cookie={:#x?}, buf_used={:#x?})",
|
|
||||||
fd,
|
|
||||||
buf,
|
|
||||||
buf_len,
|
|
||||||
cookie,
|
|
||||||
buf_used,
|
|
||||||
);
|
|
||||||
|
|
||||||
enc_usize_byref(memory, buf_used, 0)?;
|
|
||||||
|
|
||||||
let file = wasi_ctx
|
|
||||||
.get_fd_entry_mut(fd)?
|
|
||||||
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READDIR, 0)?
|
|
||||||
.as_file_mut()?;
|
|
||||||
let host_buf = dec_slice_of_mut_u8(memory, buf, buf_len)?;
|
|
||||||
|
|
||||||
trace!(" | (buf,buf_len)={:?}", host_buf);
|
|
||||||
|
|
||||||
let host_bufused = hostcalls_impl::fd_readdir(file, host_buf, cookie)?;
|
|
||||||
|
|
||||||
trace!(" | *buf_used={:?}", host_bufused);
|
|
||||||
|
|
||||||
enc_usize_byref(memory, buf_used, host_bufused)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn path_readlink(
|
pub(crate) unsafe fn path_readlink(
|
||||||
wasi_ctx: &WasiCtx,
|
wasi_ctx: &WasiCtx,
|
||||||
memory: &mut [u8],
|
memory: &mut [u8],
|
||||||
@@ -1030,6 +995,53 @@ pub(crate) unsafe fn fd_prestat_dir_name(
|
|||||||
enc_slice_of_u8(memory, path.as_bytes(), path_ptr)
|
enc_slice_of_u8(memory, path.as_bytes(), path_ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn fd_readdir(
|
||||||
|
wasi_ctx: &mut WasiCtx,
|
||||||
|
memory: &mut [u8],
|
||||||
|
fd: wasi::__wasi_fd_t,
|
||||||
|
buf: wasi32::uintptr_t,
|
||||||
|
buf_len: wasi32::size_t,
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
buf_used: wasi32::uintptr_t,
|
||||||
|
) -> Result<()> {
|
||||||
|
trace!(
|
||||||
|
"fd_readdir(fd={:?}, buf={:#x?}, buf_len={}, cookie={:#x?}, buf_used={:#x?})",
|
||||||
|
fd,
|
||||||
|
buf,
|
||||||
|
buf_len,
|
||||||
|
cookie,
|
||||||
|
buf_used,
|
||||||
|
);
|
||||||
|
|
||||||
|
enc_usize_byref(memory, buf_used, 0)?;
|
||||||
|
|
||||||
|
let file = wasi_ctx
|
||||||
|
.get_fd_entry_mut(fd)?
|
||||||
|
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READDIR, 0)?
|
||||||
|
.as_file_mut()?;
|
||||||
|
let mut host_buf = dec_slice_of_mut_u8(memory, buf, buf_len)?;
|
||||||
|
|
||||||
|
trace!(" | (buf,buf_len)={:?}", host_buf);
|
||||||
|
|
||||||
|
let iter = hostcalls_impl::fd_readdir(file, cookie)?;
|
||||||
|
let mut host_bufused = 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);
|
||||||
|
host_bufused += offset;
|
||||||
|
host_buf = &mut host_buf[offset..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(" | *buf_used={:?}", host_bufused);
|
||||||
|
|
||||||
|
enc_usize_byref(memory, buf_used, host_bufused)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // trouble with sockets
|
#[allow(dead_code)] // trouble with sockets
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -1059,7 +1071,6 @@ pub(crate) struct Dirent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dirent {
|
impl Dirent {
|
||||||
#![allow(unused)] // temporarily, until BSD catches up with this change
|
|
||||||
/// Serialize the directory entry to the format define by `__wasi_fd_readdir`,
|
/// Serialize the directory entry to the format define by `__wasi_fd_readdir`,
|
||||||
/// so that the serialized entries can be concatenated by the implementation.
|
/// so that the serialized entries can be concatenated by the implementation.
|
||||||
pub fn to_wasi_raw(&self) -> Result<Vec<u8>> {
|
pub fn to_wasi_raw(&self) -> Result<Vec<u8>> {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
use super::super::dir::{Dir, Entry, SeekLoc};
|
||||||
use super::oshandle::OsHandle;
|
use super::oshandle::OsHandle;
|
||||||
use crate::old::snapshot_0::hostcalls_impl::PathGet;
|
use crate::old::snapshot_0::hostcalls_impl::{Dirent, PathGet};
|
||||||
use crate::old::snapshot_0::sys::host_impl;
|
use crate::old::snapshot_0::sys::host_impl;
|
||||||
use crate::old::snapshot_0::sys::unix::str_to_cstring;
|
use crate::old::snapshot_0::sys::unix::str_to_cstring;
|
||||||
use crate::old::snapshot_0::{wasi, Error, Result};
|
use crate::old::snapshot_0::{wasi, Error, Result};
|
||||||
use nix::libc::{self, c_long, c_void};
|
use nix::libc;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::prelude::AsRawFd;
|
use std::os::unix::prelude::AsRawFd;
|
||||||
|
use std::sync::MutexGuard;
|
||||||
|
|
||||||
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
|
pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> {
|
||||||
use nix::errno;
|
use nix::errno;
|
||||||
@@ -139,101 +141,6 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Resul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fd_readdir(
|
|
||||||
os_handle: &mut OsHandle,
|
|
||||||
host_buf: &mut [u8],
|
|
||||||
cookie: wasi::__wasi_dircookie_t,
|
|
||||||
) -> Result<usize> {
|
|
||||||
use crate::old::snapshot_0::sys::unix::bsd::oshandle::DirStream;
|
|
||||||
use libc::{fdopendir, readdir, rewinddir, seekdir, telldir};
|
|
||||||
use nix::errno::Errno;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
let dir_stream = match os_handle.dir_stream {
|
|
||||||
Some(ref mut dir_stream) => dir_stream,
|
|
||||||
None => {
|
|
||||||
let file = os_handle.file.try_clone()?;
|
|
||||||
let dir_ptr = unsafe { fdopendir(file.as_raw_fd()) };
|
|
||||||
os_handle.dir_stream.get_or_insert(Mutex::new(DirStream {
|
|
||||||
file: ManuallyDrop::new(file),
|
|
||||||
dir_ptr,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let dir_stream = dir_stream.lock().unwrap();
|
|
||||||
|
|
||||||
let host_buf_ptr = host_buf.as_mut_ptr();
|
|
||||||
let host_buf_len = host_buf.len();
|
|
||||||
|
|
||||||
if cookie != wasi::__WASI_DIRCOOKIE_START {
|
|
||||||
unsafe { seekdir(dir_stream.dir_ptr, cookie as c_long) };
|
|
||||||
} else {
|
|
||||||
unsafe { rewinddir(dir_stream.dir_ptr) };
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut left = host_buf_len;
|
|
||||||
let mut host_buf_offset: usize = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let errno = Errno::last();
|
|
||||||
let host_entry_ptr = unsafe { readdir(dir_stream.dir_ptr) };
|
|
||||||
if host_entry_ptr.is_null() {
|
|
||||||
if errno != Errno::last() {
|
|
||||||
// TODO Is this correct?
|
|
||||||
// According to POSIX man (for Linux though!), there was an error
|
|
||||||
// if the errno value has changed at some point during the sequence
|
|
||||||
// of readdir calls
|
|
||||||
return Err(host_impl::errno_from_nix(Errno::last()));
|
|
||||||
} else {
|
|
||||||
// Not an error
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let host_entry = unsafe { *host_entry_ptr };
|
|
||||||
let mut wasi_entry: wasi::__wasi_dirent_t = host_impl::dirent_from_host(&host_entry)?;
|
|
||||||
// Set d_next manually:
|
|
||||||
// * on macOS d_seekoff is not set for some reason
|
|
||||||
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
|
|
||||||
// not equivalent to the value read from telldir call
|
|
||||||
wasi_entry.d_next = unsafe { telldir(dir_stream.dir_ptr) } as wasi::__wasi_dircookie_t;
|
|
||||||
|
|
||||||
log::debug!("fd_readdir host_entry = {:?}", host_entry);
|
|
||||||
log::debug!("fd_readdir wasi_entry = {:?}", wasi_entry);
|
|
||||||
|
|
||||||
let name_len = host_entry.d_namlen.try_into()?;
|
|
||||||
let required_space = std::mem::size_of_val(&wasi_entry) + name_len;
|
|
||||||
|
|
||||||
if required_space > left {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = unsafe { CStr::from_ptr(host_entry.d_name.as_ptr()) }.to_str()?;
|
|
||||||
log::debug!("fd_readdir entry name = {}", name);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let ptr = host_buf_ptr.offset(host_buf_offset.try_into()?) as *mut c_void
|
|
||||||
as *mut wasi::__wasi_dirent_t;
|
|
||||||
*ptr = wasi_entry;
|
|
||||||
}
|
|
||||||
host_buf_offset += std::mem::size_of_val(&wasi_entry);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
std::ptr::copy_nonoverlapping(
|
|
||||||
name.as_ptr(),
|
|
||||||
host_buf_ptr.offset(host_buf_offset.try_into()?),
|
|
||||||
name_len,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
host_buf_offset += name_len;
|
|
||||||
left -= required_space;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(host_buf_len - left)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
pub(crate) fn fd_advise(
|
pub(crate) fn fd_advise(
|
||||||
file: &File,
|
file: &File,
|
||||||
@@ -296,3 +203,88 @@ pub(crate) fn fd_advise(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fd_readdir<'a>(
|
||||||
|
os_handle: &'a mut OsHandle,
|
||||||
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
|
) -> Result<impl Iterator<Item = Result<Dirent>> + 'a> {
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
let dir = match os_handle.dir {
|
||||||
|
Some(ref mut dir) => dir,
|
||||||
|
None => {
|
||||||
|
// We need to duplicate the fd, because `opendir(3)`:
|
||||||
|
// Upon successful return from fdopendir(), the file descriptor is under
|
||||||
|
// control of the system, and if any attempt is made to close the file
|
||||||
|
// descriptor, or to modify the state of the associated description other
|
||||||
|
// than by means of closedir(), readdir(), readdir_r(), or rewinddir(),
|
||||||
|
// the behaviour is undefined.
|
||||||
|
let fd = (*os_handle).try_clone()?;
|
||||||
|
let dir = Dir::from(fd)?;
|
||||||
|
os_handle.dir.get_or_insert(Mutex::new(dir))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut dir = dir.lock().unwrap();
|
||||||
|
|
||||||
|
// Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START,
|
||||||
|
// new items may not be returned to the caller.
|
||||||
|
if cookie == wasi::__WASI_DIRCOOKIE_START {
|
||||||
|
log::trace!(" | fd_readdir: doing rewinddir");
|
||||||
|
dir.rewind();
|
||||||
|
} else {
|
||||||
|
log::trace!(" | fd_readdir: doing seekdir to {}", cookie);
|
||||||
|
let loc = unsafe { SeekLoc::from_raw(cookie as i64) };
|
||||||
|
dir.seek(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DirIter(dir).map(|entry| {
|
||||||
|
let (entry, loc): (Entry, SeekLoc) = 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(),
|
||||||
|
// Set cookie manually:
|
||||||
|
// * on macOS d_seekoff is not set for some reason
|
||||||
|
// * on FreeBSD d_seekoff doesn't exist; there is d_off but it is
|
||||||
|
// not equivalent to the value read from telldir call
|
||||||
|
cookie: loc.to_raw().try_into()?,
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DirIter<'a>(MutexGuard<'a, Dir>);
|
||||||
|
|
||||||
|
impl<'a> Iterator for DirIter<'a> {
|
||||||
|
type Item = nix::Result<(Entry, SeekLoc)>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
use libc::readdir;
|
||||||
|
use nix::{errno::Errno, Error};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let errno = Errno::last();
|
||||||
|
let ent = readdir((self.0).0.as_ptr());
|
||||||
|
if ent.is_null() {
|
||||||
|
if errno != Errno::last() {
|
||||||
|
// TODO This should be verified on different BSD-flavours.
|
||||||
|
//
|
||||||
|
// According to 4.3BSD/POSIX.1-2001 man pages, there was an error
|
||||||
|
// if the errno value has changed at some point during the sequence
|
||||||
|
// of readdir calls.
|
||||||
|
Some(Err(Error::last()))
|
||||||
|
} else {
|
||||||
|
// Not an error. We've simply reached the end of the stream.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let entry = Entry(*ent);
|
||||||
|
let loc = self.0.tell();
|
||||||
|
Some(Ok((entry, loc)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,24 +22,11 @@ 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::old::snapshot_0::{wasi, Result};
|
use crate::old::snapshot_0::{wasi, Result};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC;
|
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC;
|
||||||
|
|
||||||
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_type = dirent_filetype_from_host(host_entry)?;
|
|
||||||
entry.d_ino = host_entry.d_ino;
|
|
||||||
entry.d_next = host_entry.d_seekoff;
|
|
||||||
entry.d_namlen = u32::from(host_entry.d_namlen);
|
|
||||||
entry.d_type = d_type;
|
|
||||||
Ok(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn stdev_from_nix(dev: nix::libc::dev_t) -> Result<wasi::__wasi_device_t> {
|
pub(crate) fn stdev_from_nix(dev: nix::libc::dev_t) -> Result<wasi::__wasi_device_t> {
|
||||||
wasi::__wasi_device_t::try_from(dev).map_err(Into::into)
|
wasi::__wasi_device_t::try_from(dev).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
|
use super::super::dir::Dir;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct DirStream {
|
|
||||||
pub(crate) file: ManuallyDrop<fs::File>,
|
|
||||||
pub(crate) dir_ptr: *mut libc::DIR,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for DirStream {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { libc::closedir(self.dir_ptr) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct OsHandle {
|
pub(crate) struct OsHandle {
|
||||||
pub(crate) file: fs::File,
|
pub(crate) file: fs::File,
|
||||||
pub(crate) dir_stream: Option<Mutex<DirStream>>,
|
// In case that this `OsHandle` actually refers to a directory,
|
||||||
|
// when the client makes a `fd_readdir` syscall on this descriptor,
|
||||||
|
// we will need to cache the `libc::DIR` pointer manually in order
|
||||||
|
// to be able to seek on it later. While on Linux, this is handled
|
||||||
|
// by the OS, BSD Unixes require the client to do this caching.
|
||||||
|
//
|
||||||
|
// This comes directly from the BSD man pages on `readdir`:
|
||||||
|
// > Values returned by telldir() are good only for the lifetime
|
||||||
|
// > of the DIR pointer, dirp, from which they are derived.
|
||||||
|
// > If the directory is closed and then reopened, prior values
|
||||||
|
// > returned by telldir() will no longer be valid.
|
||||||
|
pub(crate) dir: Option<Mutex<Dir>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<fs::File> for OsHandle {
|
impl From<fs::File> for OsHandle {
|
||||||
fn from(file: fs::File) -> Self {
|
fn from(file: fs::File) -> Self {
|
||||||
Self {
|
Self { file, dir: None }
|
||||||
file,
|
|
||||||
dir_stream: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
// Based on src/dir.rs from nix
|
// Based on src/dir.rs from nix
|
||||||
#![allow(unused)] // temporarily, until BSD catches up with this change
|
|
||||||
use crate::old::snapshot_0::hostcalls_impl::FileType;
|
use crate::old::snapshot_0::hostcalls_impl::FileType;
|
||||||
use libc;
|
use libc;
|
||||||
use nix::{errno::Errno, Error, Result};
|
use nix::{Error, Result};
|
||||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||||
use std::{ffi, ptr};
|
use std::{ffi, ptr};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use libc::{dirent64 as dirent, readdir64_r as readdir_r};
|
use libc::dirent64 as dirent;
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux",))]
|
||||||
use libc::{dirent, readdir_r};
|
use libc::dirent;
|
||||||
|
|
||||||
/// An open directory.
|
/// An open directory.
|
||||||
///
|
///
|
||||||
@@ -26,7 +25,7 @@ use libc::{dirent, readdir_r};
|
|||||||
/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
|
/// * returns entries' names as a `CStr` (no allocation or conversion beyond whatever libc
|
||||||
/// does).
|
/// does).
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub(crate) struct Dir(ptr::NonNull<libc::DIR>);
|
pub(crate) struct Dir(pub(crate) ptr::NonNull<libc::DIR>);
|
||||||
|
|
||||||
impl Dir {
|
impl Dir {
|
||||||
/// Converts from a descriptor-based object, closing the descriptor on success or failure.
|
/// Converts from a descriptor-based object, closing the descriptor on success or failure.
|
||||||
@@ -90,53 +89,12 @@ impl Drop for Dir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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`.
|
/// A directory entry, similar to `std::fs::DirEntry`.
|
||||||
///
|
///
|
||||||
/// Note that unlike the std version, this may represent the `.` or `..` entries.
|
/// Note that unlike the std version, this may represent the `.` or `..` entries.
|
||||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct Entry(dirent);
|
pub(crate) struct Entry(pub(crate) dirent);
|
||||||
|
|
||||||
pub(crate) type Type = FileType;
|
pub(crate) type Type = FileType;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use super::super::dir::{Dir, Entry, SeekLoc};
|
use super::super::dir::{Dir, Entry, SeekLoc};
|
||||||
use super::oshandle::OsHandle;
|
|
||||||
use crate::old::snapshot_0::hostcalls_impl::{Dirent, PathGet};
|
use crate::old::snapshot_0::hostcalls_impl::{Dirent, PathGet};
|
||||||
use crate::old::snapshot_0::sys::host_impl;
|
use crate::old::snapshot_0::sys::host_impl;
|
||||||
use crate::old::snapshot_0::sys::unix::str_to_cstring;
|
use crate::old::snapshot_0::sys::unix::str_to_cstring;
|
||||||
@@ -67,7 +66,7 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> Resul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fd_readdir_impl(
|
pub(crate) fn fd_readdir(
|
||||||
fd: &File,
|
fd: &File,
|
||||||
cookie: wasi::__wasi_dircookie_t,
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
) -> Result<impl Iterator<Item = Result<Dirent>>> {
|
) -> Result<impl Iterator<Item = Result<Dirent>>> {
|
||||||
@@ -98,7 +97,7 @@ pub(crate) fn fd_readdir_impl(
|
|||||||
dir.seek(loc);
|
dir.seek(loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(dir.into_iter().map(|entry| {
|
Ok(DirIter(dir).map(|entry| {
|
||||||
let entry: Entry = entry?;
|
let entry: Entry = entry?;
|
||||||
Ok(Dirent {
|
Ok(Dirent {
|
||||||
name: entry // TODO can we reuse path_from_host for CStr?
|
name: entry // TODO can we reuse path_from_host for CStr?
|
||||||
@@ -112,29 +111,41 @@ pub(crate) fn fd_readdir_impl(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should actually be common code with Windows,
|
struct DirIter(Dir);
|
||||||
// but there's BSD stuff remaining
|
|
||||||
pub(crate) fn fd_readdir(
|
impl Iterator for DirIter {
|
||||||
os_handle: &mut OsHandle,
|
type Item = nix::Result<Entry>;
|
||||||
mut host_buf: &mut [u8],
|
|
||||||
cookie: wasi::__wasi_dircookie_t,
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
) -> Result<usize> {
|
use libc::{dirent64, readdir64_r};
|
||||||
let iter = fd_readdir_impl(os_handle, cookie)?;
|
use nix::errno::Errno;
|
||||||
let mut used = 0;
|
|
||||||
for dirent in iter {
|
unsafe {
|
||||||
let dirent_raw = dirent?.to_wasi_raw()?;
|
// Note: POSIX specifies that portable applications should dynamically allocate a
|
||||||
let offset = dirent_raw.len();
|
// buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
|
||||||
if host_buf.len() < offset {
|
// for the NUL byte. It doesn't look like the std library does this; it just uses
|
||||||
break;
|
// fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
|
||||||
} else {
|
// Probably fine here too then.
|
||||||
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
//
|
||||||
used += offset;
|
// See `impl Iterator for ReadDir` [1] for more details.
|
||||||
host_buf = &mut host_buf[offset..];
|
// [1] https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/fs.rs
|
||||||
|
let mut ent = std::mem::MaybeUninit::<dirent64>::uninit();
|
||||||
|
let mut result = std::ptr::null_mut();
|
||||||
|
if let Err(e) = Errno::result(readdir64_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())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(" | *buf_used={:?}", used);
|
|
||||||
Ok(used)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fd_advise(
|
pub(crate) fn fd_advise(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::old::snapshot_0::helpers::systemtime_to_timestamp;
|
|||||||
use crate::old::snapshot_0::hostcalls_impl::{
|
use crate::old::snapshot_0::hostcalls_impl::{
|
||||||
fd_filestat_set_times_impl, Dirent, FileType, PathGet,
|
fd_filestat_set_times_impl, Dirent, FileType, PathGet,
|
||||||
};
|
};
|
||||||
use crate::old::snapshot_0::sys::fdentry_impl::{determine_type_rights, OsHandle};
|
use crate::old::snapshot_0::sys::fdentry_impl::determine_type_rights;
|
||||||
use crate::old::snapshot_0::sys::host_impl::{self, path_from_host};
|
use crate::old::snapshot_0::sys::host_impl::{self, path_from_host};
|
||||||
use crate::old::snapshot_0::sys::hostcalls_impl::fs_helpers::PathGetExt;
|
use crate::old::snapshot_0::sys::hostcalls_impl::fs_helpers::PathGetExt;
|
||||||
use crate::old::snapshot_0::{wasi, Error, Result};
|
use crate::old::snapshot_0::{wasi, Error, Result};
|
||||||
@@ -18,7 +18,7 @@ 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};
|
use winx::file::{AccessMode, CreationDisposition, FileModeInformation, 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
|
||||||
@@ -55,10 +55,29 @@ pub(crate) fn fd_pwrite(file: &File, buf: &[u8], offset: wasi::__wasi_filesize_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
|
pub(crate) fn fd_fdstat_get(fd: &File) -> Result<wasi::__wasi_fdflags_t> {
|
||||||
use winx::file::AccessMode;
|
let mut fdflags = 0;
|
||||||
unsafe { winx::file::query_access_information(fd.as_raw_handle()) }
|
|
||||||
.map(host_impl::fdflags_from_win)
|
let handle = unsafe { fd.as_raw_handle() };
|
||||||
.map_err(Into::into)
|
|
||||||
|
let access_mode = winx::file::query_access_information(handle)?;
|
||||||
|
let mode = winx::file::query_mode_information(handle)?;
|
||||||
|
|
||||||
|
// Append without write implies append-only (__WASI_FDFLAGS_APPEND)
|
||||||
|
if access_mode.contains(AccessMode::FILE_APPEND_DATA)
|
||||||
|
&& !access_mode.contains(AccessMode::FILE_WRITE_DATA)
|
||||||
|
{
|
||||||
|
fdflags |= wasi::__WASI_FDFLAGS_APPEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode.contains(FileModeInformation::FILE_WRITE_THROUGH) {
|
||||||
|
// Only report __WASI_FDFLAGS_SYNC
|
||||||
|
// This is technically the only one of the O_?SYNC flags Windows supports.
|
||||||
|
fdflags |= wasi::__WASI_FDFLAGS_SYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files do not support the `__WASI_FDFLAGS_NONBLOCK` flag
|
||||||
|
|
||||||
|
Ok(fdflags)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
|
pub(crate) fn fd_fdstat_set_flags(fd: &File, fdflags: wasi::__wasi_fdflags_t) -> Result<()> {
|
||||||
@@ -102,36 +121,23 @@ pub(crate) fn path_open(
|
|||||||
) -> Result<File> {
|
) -> Result<File> {
|
||||||
use winx::file::{AccessMode, CreationDisposition, Flags};
|
use winx::file::{AccessMode, CreationDisposition, Flags};
|
||||||
|
|
||||||
let mut access_mode = AccessMode::READ_CONTROL;
|
|
||||||
if read {
|
|
||||||
access_mode.insert(AccessMode::FILE_GENERIC_READ);
|
|
||||||
}
|
|
||||||
if write {
|
|
||||||
access_mode.insert(AccessMode::FILE_GENERIC_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;
|
|
||||||
|
|
||||||
// convert open flags
|
// convert open flags
|
||||||
|
// note: the calls to `write(true)` are to bypass an internal OpenOption check
|
||||||
|
// the write flag will ultimately be ignored when `access_mode` is called below.
|
||||||
let mut opts = OpenOptions::new();
|
let mut opts = OpenOptions::new();
|
||||||
match host_impl::win_from_oflags(oflags) {
|
match creation_disposition_from_oflags(oflags) {
|
||||||
CreationDisposition::CREATE_ALWAYS => {
|
CreationDisposition::CREATE_ALWAYS => {
|
||||||
opts.create(true).append(true);
|
opts.create(true).write(true);
|
||||||
}
|
}
|
||||||
CreationDisposition::CREATE_NEW => {
|
CreationDisposition::CREATE_NEW => {
|
||||||
opts.create_new(true).write(true);
|
opts.create_new(true).write(true);
|
||||||
}
|
}
|
||||||
CreationDisposition::TRUNCATE_EXISTING => {
|
CreationDisposition::TRUNCATE_EXISTING => {
|
||||||
opts.truncate(true);
|
opts.truncate(true).write(true);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert file descriptor flags
|
|
||||||
let (add_access_mode, add_flags) = host_impl::win_from_fdflags(fdflags);
|
|
||||||
access_mode.insert(add_access_mode);
|
|
||||||
flags.insert(add_flags);
|
|
||||||
|
|
||||||
let path = resolved.concatenate()?;
|
let path = resolved.concatenate()?;
|
||||||
|
|
||||||
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
|
match path.symlink_metadata().map(|metadata| metadata.file_type()) {
|
||||||
@@ -164,12 +170,71 @@ pub(crate) fn path_open(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.access_mode(access_mode.bits())
|
opts.access_mode(file_access_mode_from_fdflags(fdflags, read, write).bits())
|
||||||
.custom_flags(flags.bits())
|
.custom_flags(file_flags_from_fdflags(fdflags).bits())
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn creation_disposition_from_oflags(oflags: wasi::__wasi_oflags_t) -> CreationDisposition {
|
||||||
|
if oflags & wasi::__WASI_OFLAGS_CREAT != 0 {
|
||||||
|
if oflags & wasi::__WASI_OFLAGS_EXCL != 0 {
|
||||||
|
CreationDisposition::CREATE_NEW
|
||||||
|
} else {
|
||||||
|
CreationDisposition::CREATE_ALWAYS
|
||||||
|
}
|
||||||
|
} else if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 {
|
||||||
|
CreationDisposition::TRUNCATE_EXISTING
|
||||||
|
} else {
|
||||||
|
CreationDisposition::OPEN_EXISTING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_access_mode_from_fdflags(
|
||||||
|
fdflags: wasi::__wasi_fdflags_t,
|
||||||
|
read: bool,
|
||||||
|
write: bool,
|
||||||
|
) -> AccessMode {
|
||||||
|
let mut access_mode = AccessMode::READ_CONTROL;
|
||||||
|
|
||||||
|
if read {
|
||||||
|
access_mode.insert(AccessMode::GENERIC_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
if write {
|
||||||
|
access_mode.insert(AccessMode::GENERIC_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA.
|
||||||
|
// This makes the handle "append only".
|
||||||
|
// Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior).
|
||||||
|
if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 {
|
||||||
|
access_mode.insert(AccessMode::FILE_APPEND_DATA);
|
||||||
|
access_mode.remove(AccessMode::FILE_WRITE_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
access_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_flags_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> Flags {
|
||||||
|
// Enable backup semantics so directories can be opened as files
|
||||||
|
let mut flags = Flags::FILE_FLAG_BACKUP_SEMANTICS;
|
||||||
|
|
||||||
|
// Note: __WASI_FDFLAGS_NONBLOCK is purposely being ignored for files
|
||||||
|
// While Windows does inherently support a non-blocking mode on files, the WASI API will
|
||||||
|
// treat I/O operations on files as synchronous. WASI might have an async-io API in the future.
|
||||||
|
|
||||||
|
// Technically, Windows only supports __WASI_FDFLAGS_SYNC, but treat all the flags as the same.
|
||||||
|
if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0
|
||||||
|
|| fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0
|
||||||
|
|| fdflags & wasi::__WASI_FDFLAGS_SYNC != 0
|
||||||
|
{
|
||||||
|
flags.insert(Flags::FILE_FLAG_WRITE_THROUGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
|
||||||
fn dirent_from_path<P: AsRef<Path>>(
|
fn dirent_from_path<P: AsRef<Path>>(
|
||||||
path: P,
|
path: P,
|
||||||
name: &str,
|
name: &str,
|
||||||
@@ -220,7 +285,7 @@ fn dirent_from_path<P: AsRef<Path>>(
|
|||||||
// . gets cookie = 1
|
// . gets cookie = 1
|
||||||
// .. gets cookie = 2
|
// .. gets cookie = 2
|
||||||
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
|
// other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies
|
||||||
pub(crate) fn fd_readdir_impl(
|
pub(crate) fn fd_readdir(
|
||||||
fd: &File,
|
fd: &File,
|
||||||
cookie: wasi::__wasi_dircookie_t,
|
cookie: wasi::__wasi_dircookie_t,
|
||||||
) -> Result<impl Iterator<Item = Result<Dirent>>> {
|
) -> Result<impl Iterator<Item = Result<Dirent>>> {
|
||||||
@@ -259,30 +324,6 @@ pub(crate) fn fd_readdir_impl(
|
|||||||
Ok(iter.skip(cookie))
|
Ok(iter.skip(cookie))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should actually be common code with Linux
|
|
||||||
pub(crate) fn fd_readdir(
|
|
||||||
os_handle: &mut OsHandle,
|
|
||||||
mut host_buf: &mut [u8],
|
|
||||||
cookie: wasi::__wasi_dircookie_t,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let iter = fd_readdir_impl(os_handle, 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> {
|
||||||
use winx::file::get_file_path;
|
use winx::file::get_file_path;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user