diff --git a/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs b/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs index 631c8b171b..4481dc461f 100644 --- a/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/old/snapshot_0/hostcalls_impl/fs.rs @@ -576,41 +576,6 @@ pub(crate) unsafe fn path_open( 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( wasi_ctx: &WasiCtx, 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) } +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 #[derive(Clone, Copy, Debug)] #[repr(u8)] @@ -1059,7 +1071,6 @@ pub(crate) struct 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`, /// so that the serialized entries can be concatenated by the implementation. pub fn to_wasi_raw(&self) -> Result> { diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs index d3647fb81b..edcd0c80db 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/hostcalls_impl.rs @@ -1,12 +1,14 @@ +use super::super::dir::{Dir, Entry, SeekLoc}; 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::unix::str_to_cstring; 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::fs::File; use std::os::unix::prelude::AsRawFd; +use std::sync::MutexGuard; pub(crate) fn path_unlink_file(resolved: PathGet) -> Result<()> { 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 { - 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"))] pub(crate) fn fd_advise( file: &File, @@ -296,3 +203,88 @@ pub(crate) fn fd_advise( Ok(()) } + +pub(crate) fn fd_readdir<'a>( + os_handle: &'a mut OsHandle, + cookie: wasi::__wasi_dircookie_t, +) -> Result> + '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 { + 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))) + } + } + } +} diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs index 27d5442886..bdc611b279 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/mod.rs @@ -22,24 +22,11 @@ pub(crate) mod fdentry_impl { } pub(crate) mod host_impl { - use super::super::host_impl::dirent_filetype_from_host; use crate::old::snapshot_0::{wasi, Result}; use std::convert::TryFrom; 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 { - let mut entry = unsafe { std::mem::zeroed::() }; - 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::try_from(dev).map_err(Into::into) } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs index 3900ef8141..eccc7ab33e 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/bsd/oshandle.rs @@ -1,33 +1,29 @@ +use super::super::dir::Dir; use std::fs; -use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; use std::os::unix::prelude::{AsRawFd, RawFd}; use std::sync::Mutex; -#[derive(Debug)] -pub(crate) struct DirStream { - pub(crate) file: ManuallyDrop, - pub(crate) dir_ptr: *mut libc::DIR, -} - -impl Drop for DirStream { - fn drop(&mut self) { - unsafe { libc::closedir(self.dir_ptr) }; - } -} - #[derive(Debug)] pub(crate) struct OsHandle { pub(crate) file: fs::File, - pub(crate) dir_stream: Option>, + // 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>, } impl From for OsHandle { fn from(file: fs::File) -> Self { - Self { - file, - dir_stream: None, - } + Self { file, dir: None } } } diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs index 26f2ddc414..81bb2e6877 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/dir.rs @@ -1,16 +1,15 @@ // 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 libc; -use nix::{errno::Errno, Error, Result}; +use nix::{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}; +use libc::dirent64 as dirent; -#[cfg(not(target_os = "linux"))] -use libc::{dirent, readdir_r}; +#[cfg(not(target_os = "linux",))] +use libc::dirent; /// 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 /// does). #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) struct Dir(ptr::NonNull); +pub(crate) struct Dir(pub(crate) ptr::NonNull); impl Dir { /// 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; - 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) struct Entry(pub(crate) dirent); pub(crate) type Type = FileType; diff --git a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs index d582105ede..538eed841c 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/unix/linux/hostcalls_impl.rs @@ -1,5 +1,4 @@ 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::sys::host_impl; 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, cookie: wasi::__wasi_dircookie_t, ) -> Result>> { @@ -98,7 +97,7 @@ pub(crate) fn fd_readdir_impl( dir.seek(loc); } - Ok(dir.into_iter().map(|entry| { + Ok(DirIter(dir).map(|entry| { let entry: Entry = entry?; Ok(Dirent { 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, -// but there's BSD stuff remaining -pub(crate) fn fd_readdir( - os_handle: &mut OsHandle, - mut host_buf: &mut [u8], - cookie: wasi::__wasi_dircookie_t, -) -> Result { - 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..]; +struct DirIter(Dir); + +impl Iterator for DirIter { + type Item = nix::Result; + + fn next(&mut self) -> Option { + use libc::{dirent64, readdir64_r}; + use nix::errno::Errno; + + 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 = 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( diff --git a/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs b/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs index 0a33e9056e..a3c8acee9d 100644 --- a/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs +++ b/crates/wasi-common/src/old/snapshot_0/sys/windows/hostcalls_impl/fs.rs @@ -7,7 +7,7 @@ use crate::old::snapshot_0::helpers::systemtime_to_timestamp; use crate::old::snapshot_0::hostcalls_impl::{ 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::hostcalls_impl::fs_helpers::PathGetExt; 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::prelude::{AsRawHandle, FromRawHandle}; 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 { // 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 { - use winx::file::AccessMode; - unsafe { winx::file::query_access_information(fd.as_raw_handle()) } - .map(host_impl::fdflags_from_win) - .map_err(Into::into) + let mut fdflags = 0; + + let handle = unsafe { fd.as_raw_handle() }; + + 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<()> { @@ -102,36 +121,23 @@ pub(crate) fn path_open( ) -> Result { 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 + // 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(); - match host_impl::win_from_oflags(oflags) { + match creation_disposition_from_oflags(oflags) { CreationDisposition::CREATE_ALWAYS => { - opts.create(true).append(true); + opts.create(true).write(true); } CreationDisposition::CREATE_NEW => { opts.create_new(true).write(true); } 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()?; match path.symlink_metadata().map(|metadata| metadata.file_type()) { @@ -164,12 +170,71 @@ pub(crate) fn path_open( }, } - opts.access_mode(access_mode.bits()) - .custom_flags(flags.bits()) + opts.access_mode(file_access_mode_from_fdflags(fdflags, read, write).bits()) + .custom_flags(file_flags_from_fdflags(fdflags).bits()) .open(&path) .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>( path: P, name: &str, @@ -220,7 +285,7 @@ fn dirent_from_path>( // . 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( +pub(crate) fn fd_readdir( fd: &File, cookie: wasi::__wasi_dircookie_t, ) -> Result>> { @@ -259,30 +324,6 @@ pub(crate) fn fd_readdir_impl( 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 { - 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 { use winx::file::get_file_path;