From 32595faba59360c400a517df2003e0a4eebabfdb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 20 Mar 2020 21:54:44 +0100 Subject: [PATCH] It's wiggle time! (#1202) * Use wiggle in place of wig in wasi-common This is a rather massive commit that introduces `wiggle` into the picture. We still use `wig`'s macro in `old` snapshot and to generate `wasmtime-wasi` glue, but everything else is now autogenerated by `wiggle`. In summary, thanks to `wiggle`, we no longer need to worry about serialising and deserialising to and from the guest memory, and all guest (WASI) types are now proper idiomatic Rust types. While we're here, in preparation for the ephemeral snapshot, I went ahead and reorganised the internal structure of the crate. Instead of modules like `hostcalls_impl` or `hostcalls_impl::fs`, the structure now resembles that in ephemeral with modules like `path`, `fd`, etc. Now, I'm not requiring we leave it like this, but I reckon it looks cleaner this way after all. * Fix wig to use new first-class access to caller's mem * Ignore warning in proc_exit for the moment * Group unsafes together in args and environ calls * Simplify pwrite; more unsafe blocks * Simplify fd_read * Bundle up unsafes in fd_readdir * Simplify fd_write * Add comment to path_readlink re zero-len buffers * Simplify unsafes in random_get * Hide GuestPtr to &str in path::get * Rewrite pread and pwrite using SeekFrom and read/write_vectored I've left the implementation of VirtualFs pretty much untouched as I don't feel that comfortable in changing the API too much. Having said that, I reckon `pread` and `pwrite` could be refactored out, and `preadv` and `pwritev` could be entirely rewritten using `seek` and `read_vectored` and `write_vectored`. * Add comment about VirtFs unsafety * Fix all mentions of FdEntry to Entry * Fix warnings on Win * Add aux struct EntryTable responsible for Fds and Entries This commit adds aux struct `EntryTable` which is private to `WasiCtx` and is basically responsible for `Fd` alloc/dealloc as well as storing matching `Entry`s. This struct is entirely private to `WasiCtx` and as such as should remain transparent to `WasiCtx` users. * Remove redundant check for empty buffer in path_readlink * Preserve and rewind file cursor in pread/pwrite * Use GuestPtr<[u8]>::copy_from_slice wherever copying bytes directly * Use GuestPtr<[u8]>::copy_from_slice in fd_readdir * Clean up unsafes around WasiCtx accessors * Fix bugs in args_get and environ_get * Fix conflicts after rebase --- Cargo.lock | 3 + crates/wasi-common/Cargo.toml | 2 + crates/wasi-common/src/clock.rs | 18 + crates/wasi-common/src/ctx.rs | 146 +- crates/wasi-common/src/entry.rs | 85 +- crates/wasi-common/src/fd.rs | 51 + crates/wasi-common/src/fs/dir.rs | 18 +- crates/wasi-common/src/fs/file.rs | 42 +- crates/wasi-common/src/fs/readdir.rs | 6 +- crates/wasi-common/src/helpers.rs | 11 - crates/wasi-common/src/host.rs | 96 -- crates/wasi-common/src/hostcalls_impl/fs.rs | 1281 ---------------- crates/wasi-common/src/hostcalls_impl/misc.rs | 367 ----- crates/wasi-common/src/hostcalls_impl/mod.rs | 9 - crates/wasi-common/src/hostcalls_impl/sock.rs | 37 - crates/wasi-common/src/lib.rs | 16 +- crates/wasi-common/src/memory.rs | 484 ------ .../wasi-common/src/old/snapshot_0/entry.rs | 32 +- crates/wasi-common/src/old/snapshot_0/wasi.rs | 9 + .../{hostcalls_impl/fs_helpers.rs => path.rs} | 114 +- crates/wasi-common/src/poll.rs | 19 + crates/wasi-common/src/snapshots/mod.rs | 1 + .../src/snapshots/wasi_snapshot_preview1.rs | 1333 +++++++++++++++++ crates/wasi-common/src/sys/unix/bsd/fd.rs | 26 + .../wasi-common/src/sys/unix/bsd/host_impl.rs | 12 - crates/wasi-common/src/sys/unix/bsd/mod.rs | 6 +- .../unix/bsd/{hostcalls_impl.rs => path.rs} | 47 +- crates/wasi-common/src/sys/unix/clock.rs | 35 + .../src/sys/unix/emscripten/mod.rs | 10 +- .../src/sys/unix/{entry_impl.rs => entry.rs} | 68 +- crates/wasi-common/src/sys/unix/fd.rs | 78 + crates/wasi-common/src/sys/unix/host_impl.rs | 227 --- .../src/sys/unix/hostcalls_impl/fs.rs | 329 ---- .../src/sys/unix/hostcalls_impl/fs_helpers.rs | 66 - .../src/sys/unix/hostcalls_impl/misc.rs | 213 --- .../src/sys/unix/hostcalls_impl/mod.rs | 8 - crates/wasi-common/src/sys/unix/linux/fd.rs | 19 + .../src/sys/unix/linux/host_impl.rs | 11 - .../src/sys/unix/linux/hostcalls_impl.rs | 68 - crates/wasi-common/src/sys/unix/linux/mod.rs | 6 +- crates/wasi-common/src/sys/unix/linux/path.rs | 46 + crates/wasi-common/src/sys/unix/mod.rs | 260 +++- crates/wasi-common/src/sys/unix/path.rs | 298 ++++ crates/wasi-common/src/sys/unix/poll.rs | 159 ++ crates/wasi-common/src/sys/windows/clock.rs | 104 ++ .../sys/windows/{entry_impl.rs => entry.rs} | 44 +- crates/wasi-common/src/sys/windows/fd.rs | 188 +++ .../wasi-common/src/sys/windows/host_impl.rs | 113 -- .../src/sys/windows/hostcalls_impl/fs.rs | 599 -------- .../sys/windows/hostcalls_impl/fs_helpers.rs | 145 -- .../src/sys/windows/hostcalls_impl/mod.rs | 8 - crates/wasi-common/src/sys/windows/mod.rs | 148 +- crates/wasi-common/src/sys/windows/path.rs | 506 +++++++ .../{hostcalls_impl/misc.rs => poll.rs} | 185 +-- crates/wasi-common/src/virtfs.rs | 435 +++--- crates/wasi-common/src/wasi.rs | 388 +++-- crates/wasi-common/src/wasi32.rs | 15 - crates/wasi-common/wig/src/lib.rs | 5 + crates/wasi-common/wig/src/wasi.rs | 254 +++- crates/wasi/Cargo.toml | 1 + crates/wasi/src/lib.rs | 4 +- .../crates/generate/src/types/handle.rs | 6 + 62 files changed, 4293 insertions(+), 5027 deletions(-) create mode 100644 crates/wasi-common/src/clock.rs create mode 100644 crates/wasi-common/src/fd.rs delete mode 100644 crates/wasi-common/src/helpers.rs delete mode 100644 crates/wasi-common/src/host.rs delete mode 100644 crates/wasi-common/src/hostcalls_impl/fs.rs delete mode 100644 crates/wasi-common/src/hostcalls_impl/misc.rs delete mode 100644 crates/wasi-common/src/hostcalls_impl/mod.rs delete mode 100644 crates/wasi-common/src/hostcalls_impl/sock.rs delete mode 100644 crates/wasi-common/src/memory.rs rename crates/wasi-common/src/{hostcalls_impl/fs_helpers.rs => path.rs} (75%) create mode 100644 crates/wasi-common/src/poll.rs create mode 100644 crates/wasi-common/src/snapshots/mod.rs create mode 100644 crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs create mode 100644 crates/wasi-common/src/sys/unix/bsd/fd.rs delete mode 100644 crates/wasi-common/src/sys/unix/bsd/host_impl.rs rename crates/wasi-common/src/sys/unix/bsd/{hostcalls_impl.rs => path.rs} (71%) create mode 100644 crates/wasi-common/src/sys/unix/clock.rs rename crates/wasi-common/src/sys/unix/{entry_impl.rs => entry.rs} (67%) create mode 100644 crates/wasi-common/src/sys/unix/fd.rs delete mode 100644 crates/wasi-common/src/sys/unix/host_impl.rs delete mode 100644 crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs delete mode 100644 crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs delete mode 100644 crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs delete mode 100644 crates/wasi-common/src/sys/unix/hostcalls_impl/mod.rs create mode 100644 crates/wasi-common/src/sys/unix/linux/fd.rs delete mode 100644 crates/wasi-common/src/sys/unix/linux/host_impl.rs delete mode 100644 crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs create mode 100644 crates/wasi-common/src/sys/unix/linux/path.rs create mode 100644 crates/wasi-common/src/sys/unix/path.rs create mode 100644 crates/wasi-common/src/sys/unix/poll.rs create mode 100644 crates/wasi-common/src/sys/windows/clock.rs rename crates/wasi-common/src/sys/windows/{entry_impl.rs => entry.rs} (78%) create mode 100644 crates/wasi-common/src/sys/windows/fd.rs delete mode 100644 crates/wasi-common/src/sys/windows/host_impl.rs delete mode 100644 crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs delete mode 100644 crates/wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs delete mode 100644 crates/wasi-common/src/sys/windows/hostcalls_impl/mod.rs create mode 100644 crates/wasi-common/src/sys/windows/path.rs rename crates/wasi-common/src/sys/windows/{hostcalls_impl/misc.rs => poll.rs} (57%) delete mode 100644 crates/wasi-common/src/wasi32.rs diff --git a/Cargo.lock b/Cargo.lock index 9bddeda50c..a8c624f653 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2354,6 +2354,8 @@ dependencies = [ "num", "thiserror", "wig", + "wiggle", + "wiggle-runtime", "winapi", "winx", "yanix", @@ -2627,6 +2629,7 @@ dependencies = [ "wasmtime", "wasmtime-runtime", "wig", + "wiggle-runtime", ] [[package]] diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index 073bb7d729..a978e37ca5 100644 --- a/crates/wasi-common/Cargo.toml +++ b/crates/wasi-common/Cargo.toml @@ -21,6 +21,8 @@ filetime = "0.2.7" lazy_static = "1.4.0" num = { version = "0.2.0", default-features = false } wig = { path = "wig", version = "0.12.0" } +wiggle = { path = "../wiggle" } +wiggle-runtime = { path = "../wiggle/crates/runtime" } [target.'cfg(unix)'.dependencies] yanix = { path = "yanix", version = "0.12.0" } diff --git a/crates/wasi-common/src/clock.rs b/crates/wasi-common/src/clock.rs new file mode 100644 index 0000000000..fdf9dd2792 --- /dev/null +++ b/crates/wasi-common/src/clock.rs @@ -0,0 +1,18 @@ +use crate::sys; +use crate::wasi::types::{Subclockflags, SubscriptionClock}; +use crate::wasi::{Errno, Result}; +use std::time::SystemTime; + +pub(crate) use sys::clock::*; + +pub(crate) fn to_relative_ns_delay(clock: SubscriptionClock) -> Result { + if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME { + return Ok(u128::from(clock.timeout)); + } + let now: u128 = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(|_| Errno::Notcapable)? + .as_nanos(); + let deadline = u128::from(clock.timeout); + Ok(deadline.saturating_sub(now)) +} diff --git a/crates/wasi-common/src/ctx.rs b/crates/wasi-common/src/ctx.rs index 18b9467cb1..f3b38ed2e2 100644 --- a/crates/wasi-common/src/ctx.rs +++ b/crates/wasi-common/src/ctx.rs @@ -1,9 +1,11 @@ use crate::entry::{Descriptor, Entry}; use crate::fdpool::FdPool; -use crate::sys::entry_impl::OsHandle; +use crate::sys::entry::OsHandle; use crate::virtfs::{VirtualDir, VirtualDirEntry}; -use crate::wasi::{self, WasiError, WasiResult}; +use crate::wasi::types; +use crate::wasi::{Errno, Result}; use std::borrow::Borrow; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::HashMap; use std::ffi::{self, CString, OsString}; use std::fs::File; @@ -48,10 +50,10 @@ impl std::fmt::Debug for PendingEntry { match self { Self::Thunk(f) => write!( fmt, - "PendingFdEntry::Thunk({:p})", + "PendingEntry::Thunk({:p})", f as *const fn() -> io::Result ), - Self::File(f) => write!(fmt, "PendingFdEntry::File({:?})", f), + Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f), } } } @@ -322,35 +324,26 @@ impl WasiCtxBuilder { }) .collect::>>()?; - let mut fd_pool = FdPool::new(); - let mut entries: HashMap = HashMap::new(); + let mut entries = EntryTable::new(); // Populate the non-preopen entries. for pending in vec![ self.stdin.take().unwrap(), self.stdout.take().unwrap(), self.stderr.take().unwrap(), ] { - let fd = fd_pool - .allocate() - .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; - log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending); - match pending { - PendingEntry::Thunk(f) => { - entries.insert(fd, f()?); - } - PendingEntry::File(f) => { - entries.insert(fd, Entry::from(Descriptor::OsHandle(OsHandle::from(f)))?); - } - } + log::debug!("WasiCtx inserting entry {:?}", pending); + let fd = match pending { + PendingEntry::Thunk(f) => entries + .insert(f()?) + .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?, + PendingEntry::File(f) => entries + .insert(Entry::from(Descriptor::OsHandle(OsHandle::from(f)))?) + .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?, + }; + log::debug!("WasiCtx inserted at {:?}", fd); } // Then add the preopen entries. for (guest_path, dir) in self.preopens.take().unwrap() { - // We do the increment at the beginning of the loop body, so that we don't overflow - // unnecessarily if we have exactly the maximum number of file descriptors. - let preopen_fd = fd_pool - .allocate() - .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; - match &dir { Descriptor::OsHandle(handle) => { if !handle.metadata()?.is_dir() { @@ -358,7 +351,7 @@ impl WasiCtxBuilder { } } Descriptor::VirtualFile(virt) => { - if virt.get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY { + if virt.get_file_type() != types::Filetype::Directory { return Err(WasiCtxBuilderError::NotADirectory(guest_path)); } } @@ -367,26 +360,70 @@ impl WasiCtxBuilder { } } - let mut fe = Entry::from(dir)?; - fe.preopen_path = Some(guest_path); - log::debug!("WasiCtx inserting ({:?}, {:?})", preopen_fd, fe); - entries.insert(preopen_fd, fe); + let mut entry = Entry::from(dir)?; + entry.preopen_path = Some(guest_path); + log::debug!("WasiCtx inserting {:?}", entry); + let fd = entries + .insert(entry) + .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; + log::debug!("WasiCtx inserted at {:?}", fd); log::debug!("WasiCtx entries = {:?}", entries); } Ok(WasiCtx { args, env, - entries, - fd_pool, + entries: RefCell::new(entries), }) } } #[derive(Debug)] -pub struct WasiCtx { +struct EntryTable { fd_pool: FdPool, - entries: HashMap, + entries: HashMap, +} + +impl EntryTable { + fn new() -> Self { + Self { + fd_pool: FdPool::new(), + entries: HashMap::new(), + } + } + + fn contains(&self, fd: &types::Fd) -> bool { + self.entries.contains_key(fd) + } + + fn insert(&mut self, entry: Entry) -> Option { + let fd = self.fd_pool.allocate()?; + self.entries.insert(fd, entry); + Some(fd) + } + + fn insert_at(&mut self, fd: &types::Fd, entry: Entry) { + self.entries.insert(*fd, entry); + } + + fn get(&self, fd: &types::Fd) -> Option<&Entry> { + self.entries.get(fd) + } + + fn get_mut(&mut self, fd: &types::Fd) -> Option<&mut Entry> { + self.entries.get_mut(fd) + } + + fn remove(&mut self, fd: types::Fd) -> Option { + let entry = self.entries.remove(&fd)?; + self.fd_pool.deallocate(fd); + Some(entry) + } +} + +#[derive(Debug)] +pub struct WasiCtx { + entries: RefCell, pub(crate) args: Vec, pub(crate) env: Vec, } @@ -408,42 +445,47 @@ impl WasiCtx { } /// Check if `WasiCtx` contains the specified raw WASI `fd`. - pub(crate) unsafe fn contains_entry(&self, fd: wasi::__wasi_fd_t) -> bool { - self.entries.contains_key(&fd) + pub(crate) fn contains_entry(&self, fd: types::Fd) -> bool { + self.entries.borrow().contains(&fd) } /// Get an immutable `Entry` corresponding to the specified raw WASI `fd`. - pub(crate) unsafe fn get_entry(&self, fd: wasi::__wasi_fd_t) -> WasiResult<&Entry> { - self.entries.get(&fd).ok_or(WasiError::EBADF) + pub(crate) fn get_entry(&self, fd: types::Fd) -> Result> { + if !self.contains_entry(fd) { + return Err(Errno::Badf); + } + Ok(Ref::map(self.entries.borrow(), |entries| { + entries.get(&fd).unwrap() + })) } /// Get a mutable `Entry` corresponding to the specified raw WASI `fd`. - pub(crate) unsafe fn get_entry_mut(&mut self, fd: wasi::__wasi_fd_t) -> WasiResult<&mut Entry> { - self.entries.get_mut(&fd).ok_or(WasiError::EBADF) + // TODO This runs the risk of a potential difficult-to-predict panic down-the-line. + pub(crate) fn get_entry_mut(&self, fd: types::Fd) -> Result> { + if !self.contains_entry(fd) { + return Err(Errno::Badf); + } + Ok(RefMut::map(self.entries.borrow_mut(), |entries| { + entries.get_mut(&fd).unwrap() + })) } /// Insert the specified `Entry` into the `WasiCtx` object. /// - /// The `FdEntry` will automatically get another free raw WASI `fd` assigned. Note that + /// The `Entry` will automatically get another free raw WASI `fd` assigned. Note that /// the two subsequent free raw WASI `fd`s do not have to be stored contiguously. - pub(crate) fn insert_entry(&mut self, fe: Entry) -> WasiResult { - let fd = self.fd_pool.allocate().ok_or(WasiError::EMFILE)?; - self.entries.insert(fd, fe); - Ok(fd) + pub(crate) fn insert_entry(&self, entry: Entry) -> Result { + self.entries.borrow_mut().insert(entry).ok_or(Errno::Mfile) } /// Insert the specified `Entry` with the specified raw WASI `fd` key into the `WasiCtx` /// object. - pub(crate) fn insert_entry_at(&mut self, fd: wasi::__wasi_fd_t, fe: Entry) -> Option { - self.entries.insert(fd, fe) + pub(crate) fn insert_entry_at(&self, fd: types::Fd, entry: Entry) { + self.entries.borrow_mut().insert_at(&fd, entry) } /// Remove `Entry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object. - pub(crate) fn remove_entry(&mut self, fd: wasi::__wasi_fd_t) -> WasiResult { - // Remove the `fd` from valid entries. - let entry = self.entries.remove(&fd).ok_or(WasiError::EBADF)?; - // Next, deallocate the `fd`. - self.fd_pool.deallocate(fd); - Ok(entry) + pub(crate) fn remove_entry(&self, fd: types::Fd) -> Result { + self.entries.borrow_mut().remove(fd).ok_or(Errno::Badf) } } diff --git a/crates/wasi-common/src/entry.rs b/crates/wasi-common/src/entry.rs index 69ffddef11..f4a7264f3e 100644 --- a/crates/wasi-common/src/entry.rs +++ b/crates/wasi-common/src/entry.rs @@ -1,7 +1,8 @@ use crate::sys::dev_null; -use crate::sys::entry_impl::{descriptor_as_oshandle, determine_type_and_access_rights, OsHandle}; +use crate::sys::entry::{descriptor_as_oshandle, determine_type_and_access_rights, OsHandle}; use crate::virtfs::VirtualFile; -use crate::wasi::{self, WasiError, WasiResult}; +use crate::wasi::types::{Filetype, Rights}; +use crate::wasi::{Errno, Result}; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; @@ -54,22 +55,20 @@ impl Descriptor { /// Return a reference to the `OsHandle` or `VirtualFile` treating it as an /// actual file/dir, and allowing operations which require an actual file and /// not just a stream or socket file descriptor. - pub(crate) fn as_file<'descriptor>(&'descriptor self) -> WasiResult<&'descriptor Self> { + pub(crate) fn as_file<'descriptor>(&'descriptor self) -> Result<&'descriptor Self> { match self { Self::OsHandle(_) => Ok(self), Self::VirtualFile(_) => Ok(self), - _ => Err(WasiError::EBADF), + _ => Err(Errno::Badf), } } /// Like `as_file`, but return a mutable reference. - pub(crate) fn as_file_mut<'descriptor>( - &'descriptor mut self, - ) -> WasiResult<&'descriptor mut Self> { + pub(crate) fn as_file_mut<'descriptor>(&'descriptor mut self) -> Result<&'descriptor mut Self> { match self { Self::OsHandle(_) => Ok(self), Self::VirtualFile(_) => Ok(self), - _ => Err(WasiError::EBADF), + _ => Err(Errno::Badf), } } @@ -84,15 +83,15 @@ impl Descriptor { /// accessed correctly. /// /// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or -/// stdin handle), and accessing it can only be done via the provided `FdEntry::as_descriptor` and +/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` and /// `Entry::as_descriptor_mut` methods which require a set of base and inheriting rights to be /// specified, verifying whether the stored `Descriptor` object is valid for the rights specified. #[derive(Debug)] pub(crate) struct Entry { - pub(crate) file_type: wasi::__wasi_filetype_t, + pub(crate) file_type: Filetype, descriptor: Descriptor, - pub(crate) rights_base: wasi::__wasi_rights_t, - pub(crate) rights_inheriting: wasi::__wasi_rights_t, + pub(crate) rights_base: Rights, + pub(crate) rights_inheriting: Rights, pub(crate) preopen_path: Option, // TODO: directories } @@ -122,7 +121,7 @@ impl Entry { }) } Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => { - panic!("implementation error, stdin/stdout/stderr FdEntry must not be constructed from FdEntry::from"); + panic!("implementation error, stdin/stdout/stderr Entry must not be constructed from Entry::from"); } } } @@ -167,56 +166,56 @@ impl Entry { Self::from(OsHandle::from(dev_null()?).into()) } - /// Convert this `FdEntry` into a host `Descriptor` object provided the specified - /// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object. + /// Convert this `Entry` into a host `Descriptor` object provided the specified + /// `rights_base` and `rights_inheriting` rights are set on this `Entry` object. /// - /// The `FdEntry` can only be converted into a valid `Descriptor` object if + /// The `Entry` can only be converted into a valid `Descriptor` object if /// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting` - /// is a subset of rights attached to this `FdEntry`. The check is performed using - /// `FdEntry::validate_rights` method. If the check fails, `WasiError::ENOTCAPABLE` is returned. + /// is a subset of rights attached to this `Entry`. The check is performed using + /// `Entry::validate_rights` method. If the check fails, `Errno::Notcapable` is returned. pub(crate) fn as_descriptor( &self, - rights_base: wasi::__wasi_rights_t, - rights_inheriting: wasi::__wasi_rights_t, - ) -> WasiResult<&Descriptor> { + rights_base: Rights, + rights_inheriting: Rights, + ) -> Result<&Descriptor> { self.validate_rights(rights_base, rights_inheriting)?; Ok(&self.descriptor) } - /// Convert this `FdEntry` into a mutable host `Descriptor` object provided the specified - /// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object. + /// Convert this `Entry` into a mutable host `Descriptor` object provided the specified + /// `rights_base` and `rights_inheriting` rights are set on this `Entry` object. /// - /// The `FdEntry` can only be converted into a valid `Descriptor` object if + /// The `Entry` can only be converted into a valid `Descriptor` object if /// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting` - /// is a subset of rights attached to this `FdEntry`. The check is performed using - /// `FdEntry::validate_rights` method. If the check fails, `WasiError::ENOTCAPABLE` is returned. + /// is a subset of rights attached to this `Entry`. The check is performed using + /// `Entry::validate_rights` method. If the check fails, `Errno::Notcapable` is returned. pub(crate) fn as_descriptor_mut( &mut self, - rights_base: wasi::__wasi_rights_t, - rights_inheriting: wasi::__wasi_rights_t, - ) -> WasiResult<&mut Descriptor> { + rights_base: Rights, + rights_inheriting: Rights, + ) -> Result<&mut Descriptor> { self.validate_rights(rights_base, rights_inheriting)?; Ok(&mut self.descriptor) } - /// Check if this `FdEntry` object satisfies the specified base rights `rights_base`, and - /// inheriting rights `rights_inheriting`; i.e., if rights attached to this `FdEntry` object + /// Check if this `Entry` object satisfies the specified base rights `rights_base`, and + /// inheriting rights `rights_inheriting`; i.e., if rights attached to this `Entry` object /// are a superset. /// - /// Upon unsuccessful check, `WasiError::ENOTCAPABLE` is returned. - fn validate_rights( + /// Upon unsuccessful check, `Errno::Notcapable` is returned. + pub(crate) fn validate_rights( &self, - rights_base: wasi::__wasi_rights_t, - rights_inheriting: wasi::__wasi_rights_t, - ) -> WasiResult<()> { + rights_base: Rights, + rights_inheriting: Rights, + ) -> Result<()> { let missing_base = !self.rights_base & rights_base; let missing_inheriting = !self.rights_inheriting & rights_inheriting; - if missing_base != 0 || missing_inheriting != 0 { + if missing_base != Rights::empty() || missing_inheriting != Rights::empty() { log::trace!( " | validate_rights failed: required: \ - rights_base = {:#x}, rights_inheriting = {:#x}; \ - actual: rights_base = {:#x}, rights_inheriting = {:#x}; \ - missing_base = {:#x}, missing_inheriting = {:#x}", + rights_base = {}, rights_inheriting = {}; \ + actual: rights_base = {}, rights_inheriting = {}; \ + missing_base = {}, missing_inheriting = {}", rights_base, rights_inheriting, self.rights_base, @@ -224,7 +223,7 @@ impl Entry { missing_base, missing_inheriting ); - Err(WasiError::ENOTCAPABLE) + Err(Errno::Notcapable) } else { Ok(()) } @@ -234,8 +233,8 @@ impl Entry { /// Note that since WASI itself lacks an `isatty` syscall and relies /// on a conservative approximation, we use the same approximation here. pub(crate) fn isatty(&self) -> bool { - self.file_type == wasi::__WASI_FILETYPE_CHARACTER_DEVICE - && (self.rights_base & (wasi::__WASI_RIGHTS_FD_SEEK | wasi::__WASI_RIGHTS_FD_TELL)) == 0 + self.file_type == Filetype::CharacterDevice + && (self.rights_base & (Rights::FD_SEEK | Rights::FD_TELL)) == Rights::empty() } } diff --git a/crates/wasi-common/src/fd.rs b/crates/wasi-common/src/fd.rs new file mode 100644 index 0000000000..05d4706f42 --- /dev/null +++ b/crates/wasi-common/src/fd.rs @@ -0,0 +1,51 @@ +use crate::entry::Descriptor; +use crate::sys; +use crate::wasi::{types, Errno, Result}; +use filetime::{set_file_handle_times, FileTime}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +pub(crate) use sys::fd::*; + +pub(crate) fn filestat_set_times_impl( + file: &Descriptor, + st_atim: types::Timestamp, + st_mtim: types::Timestamp, + fst_flags: types::Fstflags, +) -> Result<()> { + let set_atim = fst_flags.contains(&types::Fstflags::ATIM); + let set_atim_now = fst_flags.contains(&types::Fstflags::ATIM_NOW); + let set_mtim = fst_flags.contains(&types::Fstflags::MTIM); + let set_mtim_now = fst_flags.contains(&types::Fstflags::MTIM_NOW); + + if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) { + return Err(Errno::Inval); + } + let atim = if set_atim { + let time = UNIX_EPOCH + Duration::from_nanos(st_atim); + Some(FileTime::from_system_time(time)) + } else if set_atim_now { + let time = SystemTime::now(); + Some(FileTime::from_system_time(time)) + } else { + None + }; + + let mtim = if set_mtim { + let time = UNIX_EPOCH + Duration::from_nanos(st_mtim); + Some(FileTime::from_system_time(time)) + } else if set_mtim_now { + let time = SystemTime::now(); + Some(FileTime::from_system_time(time)) + } else { + None + }; + match file { + Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into), + Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim), + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + } +} diff --git a/crates/wasi-common/src/fs/dir.rs b/crates/wasi-common/src/fs/dir.rs index 0a543b4a15..5c23296699 100644 --- a/crates/wasi-common/src/fs/dir.rs +++ b/crates/wasi-common/src/fs/dir.rs @@ -1,5 +1,7 @@ use crate::fs::{File, OpenOptions, ReadDir}; -use crate::{host, hostcalls, wasi, WasiCtx}; +use crate::wasi::types; +use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1; +use crate::WasiCtx; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use std::{io, path::Path}; @@ -15,13 +17,13 @@ use std::{io, path::Path}; /// Unlike `std::fs`, this API has no `canonicalize`, because absolute paths /// don't interoperate well with the capability-oriented security model. pub struct Dir<'ctx> { - ctx: &'ctx mut WasiCtx, - fd: wasi::__wasi_fd_t, + ctx: &'ctx WasiCtx, + fd: types::Fd, } impl<'ctx> Dir<'ctx> { /// Constructs a new instance of `Self` from the given raw WASI file descriptor. - pub unsafe fn from_raw_wasi_fd(ctx: &'ctx mut WasiCtx, fd: wasi::__wasi_fd_t) -> Self { + pub unsafe fn from_raw_wasi_fd(ctx: &'ctx WasiCtx, fd: types::Fd) -> Self { Self { ctx, fd } } @@ -37,7 +39,7 @@ impl<'ctx> Dir<'ctx> { /// [`std::fs::File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open pub fn open_file>(&mut self, path: P) -> io::Result { let path = path.as_ref(); - let mut fd = 0; + let mut fd = types::Fd::from(0); // TODO: Refactor the hostcalls functions to split out the encoding/decoding // parts from the underlying functionality, so that we can call into the @@ -90,7 +92,7 @@ impl<'ctx> Dir<'ctx> { /// TODO: Not yet implemented. See the comment in `open_file`. pub fn open_dir>(&mut self, path: P) -> io::Result { let path = path.as_ref(); - let mut fd = 0; + let mut fd = types::Fd::from(0); // TODO: See the comment in `open_file`. unimplemented!("Dir::open_dir"); @@ -122,7 +124,7 @@ impl<'ctx> Dir<'ctx> { /// [`std::fs::File::create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create pub fn create_file>(&mut self, path: P) -> io::Result { let path = path.as_ref(); - let mut fd = 0; + let mut fd = types::Fd::from(0); // TODO: See the comments in `open_file`. // @@ -199,7 +201,7 @@ impl<'ctx> Drop for Dir<'ctx> { // the file descriptor was closed or not, and if we retried (for // something like EINTR), we might close another valid file descriptor // opened after we closed ours. - let _ = unsafe { hostcalls::fd_close(self.ctx, &mut [], self.fd) }; + let _ = self.ctx.fd_close(self.fd); } } diff --git a/crates/wasi-common/src/fs/file.rs b/crates/wasi-common/src/fs/file.rs index d8b0dbf717..9382804052 100644 --- a/crates/wasi-common/src/fs/file.rs +++ b/crates/wasi-common/src/fs/file.rs @@ -1,6 +1,7 @@ use crate::fs::Metadata; -use crate::wasi::{self, WasiResult}; -use crate::{host, hostcalls, hostcalls_impl, WasiCtx}; +use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1; +use crate::wasi::{types, Result}; +use crate::WasiCtx; use std::io; /// A reference to an open file on the filesystem. @@ -16,8 +17,8 @@ use std::io; /// [`Dir::open_file`]: struct.Dir.html#method.open_file /// [`Dir::create_file`]: struct.Dir.html#method.create_file pub struct File<'ctx> { - ctx: &'ctx mut WasiCtx, - fd: wasi::__wasi_fd_t, + ctx: &'ctx WasiCtx, + fd: types::Fd, } impl<'ctx> File<'ctx> { @@ -26,7 +27,7 @@ impl<'ctx> File<'ctx> { /// This corresponds to [`std::fs::File::from_raw_fd`]. /// /// [`std::fs::File::from_raw_fd`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.from_raw_fd - pub unsafe fn from_raw_wasi_fd(ctx: &'ctx mut WasiCtx, fd: wasi::__wasi_fd_t) -> Self { + pub unsafe fn from_raw_wasi_fd(ctx: &'ctx WasiCtx, fd: types::Fd) -> Self { Self { ctx, fd } } @@ -35,10 +36,8 @@ impl<'ctx> File<'ctx> { /// This corresponds to [`std::fs::File::sync_all`]. /// /// [`std::fs::File::sync_all`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all - pub fn sync_all(&self) -> WasiResult<()> { - unsafe { - hostcalls_impl::fd_sync(self.ctx, &mut [], self.fd)?; - } + pub fn sync_all(&self) -> Result<()> { + self.ctx.fd_sync(self.fd)?; Ok(()) } @@ -48,10 +47,8 @@ impl<'ctx> File<'ctx> { /// This corresponds to [`std::fs::File::sync_data`]. /// /// [`std::fs::File::sync_data`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data - pub fn sync_data(&self) -> WasiResult<()> { - unsafe { - hostcalls_impl::fd_datasync(self.ctx, &mut [], self.fd)?; - } + pub fn sync_data(&self) -> Result<()> { + self.ctx.fd_datasync(self.fd)?; Ok(()) } @@ -61,10 +58,8 @@ impl<'ctx> File<'ctx> { /// This corresponds to [`std::fs::File::set_len`]. /// /// [`std::fs::File::set_len`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len - pub fn set_len(&self, size: u64) -> WasiResult<()> { - unsafe { - hostcalls_impl::fd_filestat_set_size(self.ctx, &mut [], self.fd, size)?; - } + pub fn set_len(&self, size: u64) -> Result<()> { + self.ctx.fd_filestat_set_size(self.fd, size)?; Ok(()) } @@ -73,7 +68,7 @@ impl<'ctx> File<'ctx> { /// This corresponds to [`std::fs::File::metadata`]. /// /// [`std::fs::File::metadata`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata - pub fn metadata(&self) -> WasiResult { + pub fn metadata(&self) -> Result { Ok(Metadata {}) } } @@ -85,17 +80,18 @@ impl<'ctx> Drop for File<'ctx> { // the file descriptor was closed or not, and if we retried (for // something like EINTR), we might close another valid file descriptor // opened after we closed ours. - let _ = unsafe { hostcalls::fd_close(self.ctx, &mut [], self.fd) }; + let _ = self.ctx.fd_close(self.fd); } } impl<'ctx> io::Read for File<'ctx> { /// TODO: Not yet implemented. See the comment in `Dir::open_file`. fn read(&mut self, buf: &mut [u8]) -> io::Result { - let iov = [host::__wasi_iovec_t { - buf: buf.as_mut_ptr() as *mut u8, - buf_len: buf.len(), - }]; + // TODO + // let iov = [types::Iovec { + // buf: buf.as_mut_ptr() as *mut u8, + // buf_len: buf.len(), + // }]; let mut nread = 0; // TODO: See the comment in `Dir::open_file`. diff --git a/crates/wasi-common/src/fs/readdir.rs b/crates/wasi-common/src/fs/readdir.rs index 4a5ce7196c..1e9ec229aa 100644 --- a/crates/wasi-common/src/fs/readdir.rs +++ b/crates/wasi-common/src/fs/readdir.rs @@ -1,5 +1,5 @@ use crate::fs::DirEntry; -use crate::{hostcalls, wasi}; +use crate::wasi::types; /// Iterator over the entries in a directory. /// @@ -9,12 +9,12 @@ use crate::{hostcalls, wasi}; /// /// [`std::fs::ReadDir`]: https://doc.rust-lang.org/std/fs/struct.ReadDir.html pub struct ReadDir { - fd: wasi::__wasi_fd_t, + fd: types::Fd, } impl ReadDir { /// Constructs a new instance of `Self` from the given raw WASI file descriptor. - pub unsafe fn from_raw_wasi_fd(fd: wasi::__wasi_fd_t) -> Self { + pub unsafe fn from_raw_wasi_fd(fd: types::Fd) -> Self { Self { fd } } } diff --git a/crates/wasi-common/src/helpers.rs b/crates/wasi-common/src/helpers.rs deleted file mode 100644 index 4fb365882b..0000000000 --- a/crates/wasi-common/src/helpers.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::wasi::WasiResult; -use std::str; - -/// Creates not-owned WASI path from byte slice. -/// -/// NB WASI spec requires bytes to be valid UTF-8. Otherwise, -/// `__WASI_ERRNO_ILSEQ` error is returned. -pub(crate) fn path_from_slice<'a>(s: &'a [u8]) -> WasiResult<&'a str> { - let s = str::from_utf8(s)?; - Ok(s) -} diff --git a/crates/wasi-common/src/host.rs b/crates/wasi-common/src/host.rs deleted file mode 100644 index 56598b17a2..0000000000 --- a/crates/wasi-common/src/host.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! WASI host types. These are types that contain raw pointers and `usize` -//! values, and so are platform-specific. - -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -use crate::wasi::*; -use std::{convert::TryInto, io, mem, slice}; -use wig::witx_host_types; - -witx_host_types!("snapshot" "wasi_snapshot_preview1"); - -pub(crate) unsafe fn ciovec_to_host(ciovec: &__wasi_ciovec_t) -> io::IoSlice { - let slice = slice::from_raw_parts(ciovec.buf as *const u8, ciovec.buf_len); - io::IoSlice::new(slice) -} - -pub(crate) unsafe fn iovec_to_host_mut(iovec: &mut __wasi_iovec_t) -> io::IoSliceMut { - let slice = slice::from_raw_parts_mut(iovec.buf as *mut u8, iovec.buf_len); - io::IoSliceMut::new(slice) -} - -#[allow(dead_code)] // trouble with sockets -#[derive(Clone, Copy, Debug, PartialEq)] -#[repr(u8)] -pub enum FileType { - Unknown = __WASI_FILETYPE_UNKNOWN, - BlockDevice = __WASI_FILETYPE_BLOCK_DEVICE, - CharacterDevice = __WASI_FILETYPE_CHARACTER_DEVICE, - Directory = __WASI_FILETYPE_DIRECTORY, - RegularFile = __WASI_FILETYPE_REGULAR_FILE, - SocketDgram = __WASI_FILETYPE_SOCKET_DGRAM, - SocketStream = __WASI_FILETYPE_SOCKET_STREAM, - Symlink = __WASI_FILETYPE_SYMBOLIC_LINK, -} - -impl FileType { - pub(crate) fn to_wasi(&self) -> __wasi_filetype_t { - *self as __wasi_filetype_t - } - - pub(crate) fn from_wasi(wasi_filetype: u8) -> Option { - use FileType::*; - match wasi_filetype { - __WASI_FILETYPE_UNKNOWN => Some(Unknown), - __WASI_FILETYPE_BLOCK_DEVICE => Some(BlockDevice), - __WASI_FILETYPE_CHARACTER_DEVICE => Some(CharacterDevice), - __WASI_FILETYPE_DIRECTORY => Some(Directory), - __WASI_FILETYPE_REGULAR_FILE => Some(RegularFile), - __WASI_FILETYPE_SOCKET_DGRAM => Some(SocketDgram), - __WASI_FILETYPE_SOCKET_STREAM => Some(SocketStream), - __WASI_FILETYPE_SYMBOLIC_LINK => Some(Symlink), - _ => None, - } - } -} - -#[derive(Debug, Clone)] -pub struct Dirent { - pub name: String, - pub ftype: FileType, - pub ino: u64, - pub cookie: __wasi_dircookie_t, -} - -impl Dirent { - /// 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) -> WasiResult> { - let name = self.name.as_bytes(); - let namlen = name.len(); - let dirent_size = mem::size_of::<__wasi_dirent_t>(); - let offset = dirent_size - .checked_add(namlen) - .ok_or(WasiError::EOVERFLOW)?; - - let mut raw = Vec::::with_capacity(offset); - raw.resize(offset, 0); - - let sys_dirent = raw.as_mut_ptr() as *mut __wasi_dirent_t; - unsafe { - sys_dirent.write_unaligned(__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/crates/wasi-common/src/hostcalls_impl/fs.rs b/crates/wasi-common/src/hostcalls_impl/fs.rs deleted file mode 100644 index 614d21a5a7..0000000000 --- a/crates/wasi-common/src/hostcalls_impl/fs.rs +++ /dev/null @@ -1,1281 +0,0 @@ -#![allow(non_camel_case_types)] -use super::fs_helpers::path_get; -use crate::ctx::WasiCtx; -use crate::entry::{Descriptor, Entry}; -use crate::helpers::*; -use crate::host::Dirent; -use crate::memory::*; -use crate::sandboxed_tty_writer::SandboxedTTYWriter; -use crate::sys::hostcalls_impl::fs_helpers::path_open_rights; -use crate::sys::{host_impl, hostcalls_impl}; -use crate::wasi::{self, WasiError, WasiResult}; -use crate::{helpers, host, wasi32}; -use filetime::{set_file_handle_times, FileTime}; -use log::trace; -use std::convert::TryInto; -use std::io::{self, Read, Seek, SeekFrom, Write}; -use std::ops::DerefMut; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -pub(crate) unsafe fn fd_close( - wasi_ctx: &mut WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, -) -> WasiResult<()> { - trace!("fd_close(fd={:?})", fd); - - if let Ok(fe) = wasi_ctx.get_entry(fd) { - // can't close preopened files - if fe.preopen_path.is_some() { - return Err(WasiError::ENOTSUP); - } - } - - wasi_ctx.remove_entry(fd)?; - Ok(()) -} - -pub(crate) unsafe fn fd_datasync( - wasi_ctx: &WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, -) -> WasiResult<()> { - trace!("fd_datasync(fd={:?})", fd); - - let file = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_DATASYNC, 0)?; - - match file { - Descriptor::OsHandle(fd) => fd.sync_data().map_err(Into::into), - Descriptor::VirtualFile(virt) => virt.datasync(), - other => other.as_os_handle().sync_data().map_err(Into::into), - } -} - -pub(crate) unsafe fn fd_pread( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - iovs_ptr: wasi32::uintptr_t, - iovs_len: wasi32::size_t, - offset: wasi::__wasi_filesize_t, - nread: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_pread(fd={:?}, iovs_ptr={:#x?}, iovs_len={:?}, offset={}, nread={:#x?})", - fd, - iovs_ptr, - iovs_len, - offset, - nread - ); - - let file = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_READ | wasi::__WASI_RIGHTS_FD_SEEK, 0)? - .as_file()?; - - let iovs = dec_iovec_slice(memory, iovs_ptr, iovs_len)?; - - if offset > i64::max_value() as u64 { - return Err(WasiError::EIO); - } - let buf_size = iovs - .iter() - .map(|iov| { - let cast_iovlen: wasi32::size_t = iov - .buf_len - .try_into() - .expect("iovec are bounded by wasi max sizes"); - cast_iovlen - }) - .fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov))) - .ok_or(WasiError::EINVAL)?; - let mut buf = vec![0; buf_size as usize]; - let host_nread = match file { - Descriptor::OsHandle(fd) => hostcalls_impl::fd_pread(&fd, &mut buf, offset)?, - Descriptor::VirtualFile(virt) => virt.pread(&mut buf, offset)?, - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - }; - - let mut buf_offset = 0; - let mut left = host_nread; - for iov in &iovs { - if left == 0 { - break; - } - let vec_len = std::cmp::min(iov.buf_len, left); - std::slice::from_raw_parts_mut(iov.buf as *mut u8, vec_len) - .copy_from_slice(&buf[buf_offset..buf_offset + vec_len]); - buf_offset += vec_len; - left -= vec_len; - } - - trace!(" | *nread={:?}", host_nread); - - enc_usize_byref(memory, nread, host_nread) -} - -pub(crate) unsafe fn fd_pwrite( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - iovs_ptr: wasi32::uintptr_t, - iovs_len: wasi32::size_t, - offset: wasi::__wasi_filesize_t, - nwritten: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_pwrite(fd={:?}, iovs_ptr={:#x?}, iovs_len={:?}, offset={}, nwritten={:#x?})", - fd, - iovs_ptr, - iovs_len, - offset, - nwritten - ); - - let file = wasi_ctx - .get_entry(fd)? - .as_descriptor( - wasi::__WASI_RIGHTS_FD_WRITE | wasi::__WASI_RIGHTS_FD_SEEK, - 0, - )? - .as_file()?; - let iovs = dec_ciovec_slice(memory, iovs_ptr, iovs_len)?; - - if offset > i64::max_value() as u64 { - return Err(WasiError::EIO); - } - let buf_size = iovs - .iter() - .map(|iov| { - let cast_iovlen: wasi32::size_t = iov - .buf_len - .try_into() - .expect("iovec are bounded by wasi max sizes"); - cast_iovlen - }) - .fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov))) - .ok_or(WasiError::EINVAL)?; - let mut buf = Vec::with_capacity(buf_size as usize); - for iov in &iovs { - buf.extend_from_slice(std::slice::from_raw_parts( - iov.buf as *const u8, - iov.buf_len, - )); - } - let host_nwritten = match file { - Descriptor::OsHandle(fd) => hostcalls_impl::fd_pwrite(&fd, &buf, offset)?, - Descriptor::VirtualFile(virt) => virt.pwrite(buf.as_mut(), offset)?, - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - }; - - trace!(" | *nwritten={:?}", host_nwritten); - - enc_usize_byref(memory, nwritten, host_nwritten) -} - -pub(crate) unsafe fn fd_read( - wasi_ctx: &mut WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - iovs_ptr: wasi32::uintptr_t, - iovs_len: wasi32::size_t, - nread: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_read(fd={:?}, iovs_ptr={:#x?}, iovs_len={:?}, nread={:#x?})", - fd, - iovs_ptr, - iovs_len, - nread - ); - - let mut iovs = dec_iovec_slice(memory, iovs_ptr, iovs_len)?; - let mut iovs: Vec = iovs - .iter_mut() - .map(|vec| host::iovec_to_host_mut(vec)) - .collect(); - - let maybe_host_nread = match wasi_ctx - .get_entry_mut(fd)? - .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READ, 0)? - { - Descriptor::OsHandle(file) => file.read_vectored(&mut iovs).map_err(Into::into), - Descriptor::VirtualFile(virt) => virt.read_vectored(&mut iovs), - Descriptor::Stdin => io::stdin().read_vectored(&mut iovs).map_err(Into::into), - _ => return Err(WasiError::EBADF), - }; - - let host_nread = maybe_host_nread?; - - trace!(" | *nread={:?}", host_nread); - - enc_usize_byref(memory, nread, host_nread) -} - -pub(crate) unsafe fn fd_renumber( - wasi_ctx: &mut WasiCtx, - _memory: &mut [u8], - from: wasi::__wasi_fd_t, - to: wasi::__wasi_fd_t, -) -> WasiResult<()> { - trace!("fd_renumber(from={:?}, to={:?})", from, to); - - if !wasi_ctx.contains_entry(from) { - return Err(WasiError::EBADF); - } - - // Don't allow renumbering over a pre-opened resource. - // TODO: Eventually, we do want to permit this, once libpreopen in - // userspace is capable of removing entries from its tables as well. - let from_fe = wasi_ctx.get_entry(from)?; - if from_fe.preopen_path.is_some() { - return Err(WasiError::ENOTSUP); - } - if let Ok(to_fe) = wasi_ctx.get_entry(to) { - if to_fe.preopen_path.is_some() { - return Err(WasiError::ENOTSUP); - } - } - - let fe = wasi_ctx.remove_entry(from)?; - wasi_ctx.insert_entry_at(to, fe); - - Ok(()) -} - -pub(crate) unsafe fn fd_seek( - wasi_ctx: &mut WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - offset: wasi::__wasi_filedelta_t, - whence: wasi::__wasi_whence_t, - newoffset: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_seek(fd={:?}, offset={:?}, whence={}, newoffset={:#x?})", - fd, - offset, - wasi::whence_to_str(whence), - newoffset - ); - - let rights = if offset == 0 && whence == wasi::__WASI_WHENCE_CUR { - wasi::__WASI_RIGHTS_FD_TELL - } else { - wasi::__WASI_RIGHTS_FD_SEEK | wasi::__WASI_RIGHTS_FD_TELL - }; - let file = wasi_ctx - .get_entry_mut(fd)? - .as_descriptor_mut(rights, 0)? - .as_file_mut()?; - - let pos = match whence { - wasi::__WASI_WHENCE_CUR => SeekFrom::Current(offset), - wasi::__WASI_WHENCE_END => SeekFrom::End(offset), - wasi::__WASI_WHENCE_SET => SeekFrom::Start(offset as u64), - _ => return Err(WasiError::EINVAL), - }; - let host_newoffset = match file { - Descriptor::OsHandle(fd) => fd.seek(pos)?, - Descriptor::VirtualFile(virt) => virt.seek(pos)?, - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - }; - - trace!(" | *newoffset={:?}", host_newoffset); - - enc_filesize_byref(memory, newoffset, host_newoffset) -} - -pub(crate) unsafe fn fd_tell( - wasi_ctx: &mut WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - newoffset: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!("fd_tell(fd={:?}, newoffset={:#x?})", fd, newoffset); - - let file = wasi_ctx - .get_entry_mut(fd)? - .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_TELL, 0)? - .as_file_mut()?; - - let host_offset = match file { - Descriptor::OsHandle(fd) => fd.seek(SeekFrom::Current(0))?, - Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?, - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - }; - - trace!(" | *newoffset={:?}", host_offset); - - enc_filesize_byref(memory, newoffset, host_offset) -} - -pub(crate) unsafe fn fd_fdstat_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - fdstat_ptr: wasi32::uintptr_t, // *mut wasi::__wasi_fdstat_t -) -> WasiResult<()> { - trace!("fd_fdstat_get(fd={:?}, fdstat_ptr={:#x?})", fd, fdstat_ptr); - - let mut fdstat = dec_fdstat_byref(memory, fdstat_ptr)?; - let wasi_file = wasi_ctx.get_entry(fd)?.as_descriptor(0, 0)?; - - let fs_flags = match wasi_file { - Descriptor::OsHandle(wasi_fd) => hostcalls_impl::fd_fdstat_get(&wasi_fd)?, - Descriptor::VirtualFile(virt) => virt.fdstat_get(), - other => hostcalls_impl::fd_fdstat_get(&other.as_os_handle())?, - }; - - let fe = wasi_ctx.get_entry(fd)?; - fdstat.fs_filetype = fe.file_type; - fdstat.fs_rights_base = fe.rights_base; - fdstat.fs_rights_inheriting = fe.rights_inheriting; - fdstat.fs_flags = fs_flags; - - trace!(" | *buf={:?}", fdstat); - - enc_fdstat_byref(memory, fdstat_ptr, fdstat) -} - -pub(crate) unsafe fn fd_fdstat_set_flags( - wasi_ctx: &mut WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, - fdflags: wasi::__wasi_fdflags_t, -) -> WasiResult<()> { - trace!("fd_fdstat_set_flags(fd={:?}, fdflags={:#x?})", fd, fdflags); - - let descriptor = wasi_ctx - .get_entry_mut(fd)? - .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_FDSTAT_SET_FLAGS, 0)?; - - match descriptor { - Descriptor::OsHandle(handle) => { - let set_result = - hostcalls_impl::fd_fdstat_set_flags(&handle, fdflags)?.map(Descriptor::OsHandle); - - if let Some(new_descriptor) = set_result { - *descriptor = new_descriptor; - } - } - Descriptor::VirtualFile(handle) => { - handle.fdstat_set_flags(fdflags)?; - } - _ => { - let set_result = - hostcalls_impl::fd_fdstat_set_flags(&descriptor.as_os_handle(), fdflags)? - .map(Descriptor::OsHandle); - - if let Some(new_descriptor) = set_result { - *descriptor = new_descriptor; - } - } - }; - - Ok(()) -} - -pub(crate) unsafe fn fd_fdstat_set_rights( - wasi_ctx: &mut WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, - fs_rights_base: wasi::__wasi_rights_t, - fs_rights_inheriting: wasi::__wasi_rights_t, -) -> WasiResult<()> { - trace!( - "fd_fdstat_set_rights(fd={:?}, fs_rights_base={:#x?}, fs_rights_inheriting={:#x?})", - fd, - fs_rights_base, - fs_rights_inheriting - ); - - let fe = wasi_ctx.get_entry_mut(fd)?; - if fe.rights_base & fs_rights_base != fs_rights_base - || fe.rights_inheriting & fs_rights_inheriting != fs_rights_inheriting - { - return Err(WasiError::ENOTCAPABLE); - } - fe.rights_base = fs_rights_base; - fe.rights_inheriting = fs_rights_inheriting; - - Ok(()) -} - -pub(crate) unsafe fn fd_sync( - wasi_ctx: &WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, -) -> WasiResult<()> { - trace!("fd_sync(fd={:?})", fd); - - let file = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_SYNC, 0)? - .as_file()?; - match file { - Descriptor::OsHandle(fd) => fd.sync_all().map_err(Into::into), - Descriptor::VirtualFile(virt) => virt.sync(), - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - } -} - -pub(crate) unsafe fn fd_write( - wasi_ctx: &mut WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - iovs_ptr: wasi32::uintptr_t, - iovs_len: wasi32::size_t, - nwritten: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_write(fd={:?}, iovs_ptr={:#x?}, iovs_len={:?}, nwritten={:#x?})", - fd, - iovs_ptr, - iovs_len, - nwritten - ); - - let iovs = dec_ciovec_slice(memory, iovs_ptr, iovs_len)?; - let iovs: Vec = iovs.iter().map(|vec| host::ciovec_to_host(vec)).collect(); - - // perform unbuffered writes - let entry = wasi_ctx.get_entry_mut(fd)?; - let isatty = entry.isatty(); - let desc = entry.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_WRITE, 0)?; - let host_nwritten = match desc { - Descriptor::OsHandle(file) => { - if isatty { - SandboxedTTYWriter::new(file.deref_mut()).write_vectored(&iovs)? - } else { - file.write_vectored(&iovs)? - } - } - Descriptor::VirtualFile(virt) => { - if isatty { - unimplemented!("writes to virtual tty"); - } else { - virt.write_vectored(&iovs)? - } - } - Descriptor::Stdin => return Err(WasiError::EBADF), - Descriptor::Stdout => { - // lock for the duration of the scope - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - let nwritten = if isatty { - SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)? - } else { - stdout.write_vectored(&iovs)? - }; - stdout.flush()?; - nwritten - } - // Always sanitize stderr, even if it's not directly connected to a tty, - // because stderr is meant for diagnostics rather than binary output, - // and may be redirected to a file which could end up being displayed - // on a tty later. - Descriptor::Stderr => SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?, - }; - - trace!(" | *nwritten={:?}", host_nwritten); - - enc_usize_byref(memory, nwritten, host_nwritten) -} - -pub(crate) unsafe fn fd_advise( - wasi_ctx: &mut WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__wasi_filesize_t, - advice: wasi::__wasi_advice_t, -) -> WasiResult<()> { - trace!( - "fd_advise(fd={:?}, offset={}, len={}, advice={:?})", - fd, - offset, - len, - advice - ); - - let file = wasi_ctx - .get_entry_mut(fd)? - .as_descriptor_mut(wasi::__WASI_RIGHTS_FD_ADVISE, 0)? - .as_file_mut()?; - - match file { - Descriptor::OsHandle(fd) => hostcalls_impl::fd_advise(&fd, advice, offset, len), - Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len), - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - } -} - -pub(crate) unsafe fn fd_allocate( - wasi_ctx: &WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__wasi_filesize_t, -) -> WasiResult<()> { - trace!("fd_allocate(fd={:?}, offset={}, len={})", fd, offset, len); - - let file = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_ALLOCATE, 0)? - .as_file()?; - - match file { - Descriptor::OsHandle(fd) => { - let metadata = fd.metadata()?; - - let current_size = metadata.len(); - let wanted_size = offset.checked_add(len).ok_or(WasiError::E2BIG)?; - // This check will be unnecessary when rust-lang/rust#63326 is fixed - if wanted_size > i64::max_value() as u64 { - return Err(WasiError::E2BIG); - } - - if wanted_size > current_size { - fd.set_len(wanted_size).map_err(Into::into) - } else { - Ok(()) - } - } - Descriptor::VirtualFile(virt) => virt.allocate(offset, len), - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - } -} - -pub(crate) unsafe fn path_create_directory( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "path_create_directory(dirfd={:?}, path_ptr={:#x?}, path_len={})", - dirfd, - path_ptr, - path_len, - ); - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(helpers::path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", path); - - let rights = wasi::__WASI_RIGHTS_PATH_OPEN | wasi::__WASI_RIGHTS_PATH_CREATE_DIRECTORY; - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get(fe, rights, 0, 0, path, false)?; - - resolved.path_create_directory() -} - -pub(crate) unsafe fn path_link( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - old_dirfd: wasi::__wasi_fd_t, - old_flags: wasi::__wasi_lookupflags_t, - old_path_ptr: wasi32::uintptr_t, - old_path_len: wasi32::size_t, - new_dirfd: wasi::__wasi_fd_t, - new_path_ptr: wasi32::uintptr_t, - new_path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "path_link(old_dirfd={:?}, old_flags={:?}, old_path_ptr={:#x?}, old_path_len={}, new_dirfd={:?}, new_path_ptr={:#x?}, new_path_len={})", - old_dirfd, - old_flags, - old_path_ptr, - old_path_len, - new_dirfd, - new_path_ptr, - new_path_len, - ); - - let old_path = dec_slice_of_u8(memory, old_path_ptr, old_path_len).and_then(path_from_slice)?; - let new_path = dec_slice_of_u8(memory, new_path_ptr, new_path_len).and_then(path_from_slice)?; - - trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); - trace!(" | (new_path_ptr,new_path_len)='{}'", new_path); - - let old_fe = wasi_ctx.get_entry(old_dirfd)?; - let new_fe = wasi_ctx.get_entry(new_dirfd)?; - let resolved_old = path_get( - old_fe, - wasi::__WASI_RIGHTS_PATH_LINK_SOURCE, - 0, - 0, - old_path, - false, - )?; - let resolved_new = path_get( - new_fe, - wasi::__WASI_RIGHTS_PATH_LINK_TARGET, - 0, - 0, - new_path, - false, - )?; - - let follow_symlinks = old_flags & wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW != 0; - - hostcalls_impl::path_link(resolved_old, resolved_new, follow_symlinks) -} - -pub(crate) unsafe fn path_open( - wasi_ctx: &mut WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - dirflags: wasi::__wasi_lookupflags_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, - oflags: wasi::__wasi_oflags_t, - fs_rights_base: wasi::__wasi_rights_t, - fs_rights_inheriting: wasi::__wasi_rights_t, - fs_flags: wasi::__wasi_fdflags_t, - fd_out_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "path_open(dirfd={:?}, dirflags={:?}, path_ptr={:#x?}, path_len={:?}, oflags={:#x?}, fs_rights_base={:#x?}, fs_rights_inheriting={:#x?}, fs_flags={:#x?}, fd_out_ptr={:#x?})", - dirfd, - dirflags, - path_ptr, - path_len, - oflags, - fs_rights_base, - fs_rights_inheriting, - fs_flags, - fd_out_ptr - ); - - // pre-encode fd_out_ptr to -1 in case of error in opening a path - enc_fd_byref(memory, fd_out_ptr, wasi::__wasi_fd_t::max_value())?; - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", path); - - let (needed_base, needed_inheriting) = - path_open_rights(fs_rights_base, fs_rights_inheriting, oflags, fs_flags); - trace!( - " | needed_base = {}, needed_inheriting = {}", - needed_base, - needed_inheriting - ); - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get( - fe, - needed_base, - needed_inheriting, - dirflags, - path, - oflags & wasi::__WASI_OFLAGS_CREAT != 0, - )?; - - // which open mode do we need? - let read = fs_rights_base & (wasi::__WASI_RIGHTS_FD_READ | wasi::__WASI_RIGHTS_FD_READDIR) != 0; - let write = fs_rights_base - & (wasi::__WASI_RIGHTS_FD_DATASYNC - | wasi::__WASI_RIGHTS_FD_WRITE - | wasi::__WASI_RIGHTS_FD_ALLOCATE - | wasi::__WASI_RIGHTS_FD_FILESTAT_SET_SIZE) - != 0; - - trace!( - " | calling path_open impl: read={}, write={}", - read, - write - ); - let fd = resolved.open_with(read, write, oflags, fs_flags)?; - - let mut fe = Entry::from(fd)?; - // We need to manually deny the rights which are not explicitly requested - // because FdEntry::from will assign maximal consistent rights. - fe.rights_base &= fs_rights_base; - fe.rights_inheriting &= fs_rights_inheriting; - let guest_fd = wasi_ctx.insert_entry(fe)?; - - trace!(" | *fd={:?}", guest_fd); - - enc_fd_byref(memory, fd_out_ptr, guest_fd) -} - -pub(crate) unsafe fn path_readlink( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, - buf_ptr: wasi32::uintptr_t, - buf_len: wasi32::size_t, - buf_used: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "path_readlink(dirfd={:?}, path_ptr={:#x?}, path_len={:?}, buf_ptr={:#x?}, buf_len={}, buf_used={:#x?})", - dirfd, - path_ptr, - path_len, - buf_ptr, - buf_len, - buf_used, - ); - - enc_usize_byref(memory, buf_used, 0)?; - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(helpers::path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", &path); - - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get(fe, wasi::__WASI_RIGHTS_PATH_READLINK, 0, 0, &path, false)?; - - let mut buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?; - - let host_bufused = match resolved.dirfd() { - Descriptor::VirtualFile(_virt) => { - unimplemented!("virtual readlink"); - } - _ => hostcalls_impl::path_readlink(resolved, &mut buf)?, - }; - - trace!(" | (buf_ptr,*buf_used)={:?}", buf); - trace!(" | *buf_used={:?}", host_bufused); - - enc_usize_byref(memory, buf_used, host_bufused) -} - -pub(crate) unsafe fn path_rename( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - old_dirfd: wasi::__wasi_fd_t, - old_path_ptr: wasi32::uintptr_t, - old_path_len: wasi32::size_t, - new_dirfd: wasi::__wasi_fd_t, - new_path_ptr: wasi32::uintptr_t, - new_path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "path_rename(old_dirfd={:?}, old_path_ptr={:#x?}, old_path_len={:?}, new_dirfd={:?}, new_path_ptr={:#x?}, new_path_len={:?})", - old_dirfd, - old_path_ptr, - old_path_len, - new_dirfd, - new_path_ptr, - new_path_len, - ); - - let old_path = dec_slice_of_u8(memory, old_path_ptr, old_path_len).and_then(path_from_slice)?; - let new_path = dec_slice_of_u8(memory, new_path_ptr, new_path_len).and_then(path_from_slice)?; - - trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); - trace!(" | (new_path_ptr,new_path_len)='{}'", new_path); - - let old_fe = wasi_ctx.get_entry(old_dirfd)?; - let new_fe = wasi_ctx.get_entry(new_dirfd)?; - let resolved_old = path_get( - old_fe, - wasi::__WASI_RIGHTS_PATH_RENAME_SOURCE, - 0, - 0, - old_path, - true, - )?; - let resolved_new = path_get( - new_fe, - wasi::__WASI_RIGHTS_PATH_RENAME_TARGET, - 0, - 0, - new_path, - true, - )?; - - log::debug!("path_rename resolved_old={:?}", resolved_old); - log::debug!("path_rename resolved_new={:?}", resolved_new); - - if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) = - (resolved_old.dirfd(), resolved_new.dirfd()) - { - hostcalls_impl::path_rename(resolved_old, resolved_new) - } else { - // Virtual files do not support rename, at the moment, and streams don't have paths to - // rename, so any combination of Descriptor that gets here is an error in the making. - panic!("path_rename with one or more non-OS files"); - } -} - -pub(crate) unsafe fn fd_filestat_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - filestat_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_filestat_get(fd={:?}, filestat_ptr={:#x?})", - fd, - filestat_ptr - ); - - let fd = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_GET, 0)? - .as_file()?; - let host_filestat = match fd { - Descriptor::OsHandle(fd) => hostcalls_impl::fd_filestat_get(&fd)?, - Descriptor::VirtualFile(virt) => virt.filestat_get()?, - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - }; - - trace!(" | *filestat_ptr={:?}", host_filestat); - - enc_filestat_byref(memory, filestat_ptr, host_filestat) -} - -pub(crate) unsafe fn fd_filestat_set_times( - wasi_ctx: &WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, - st_atim: wasi::__wasi_timestamp_t, - st_mtim: wasi::__wasi_timestamp_t, - fst_flags: wasi::__wasi_fstflags_t, -) -> WasiResult<()> { - trace!( - "fd_filestat_set_times(fd={:?}, st_atim={}, st_mtim={}, fst_flags={:#x?})", - fd, - st_atim, - st_mtim, - fst_flags - ); - - let fd = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_TIMES, 0)? - .as_file()?; - - fd_filestat_set_times_impl(&fd, st_atim, st_mtim, fst_flags) -} - -pub(crate) fn fd_filestat_set_times_impl( - file: &Descriptor, - st_atim: wasi::__wasi_timestamp_t, - st_mtim: wasi::__wasi_timestamp_t, - fst_flags: wasi::__wasi_fstflags_t, -) -> WasiResult<()> { - let set_atim = fst_flags & wasi::__WASI_FSTFLAGS_ATIM != 0; - let set_atim_now = fst_flags & wasi::__WASI_FSTFLAGS_ATIM_NOW != 0; - let set_mtim = fst_flags & wasi::__WASI_FSTFLAGS_MTIM != 0; - let set_mtim_now = fst_flags & wasi::__WASI_FSTFLAGS_MTIM_NOW != 0; - - if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) { - return Err(WasiError::EINVAL); - } - let atim = if set_atim { - let time = UNIX_EPOCH + Duration::from_nanos(st_atim); - Some(FileTime::from_system_time(time)) - } else if set_atim_now { - let time = SystemTime::now(); - Some(FileTime::from_system_time(time)) - } else { - None - }; - - let mtim = if set_mtim { - let time = UNIX_EPOCH + Duration::from_nanos(st_mtim); - Some(FileTime::from_system_time(time)) - } else if set_mtim_now { - let time = SystemTime::now(); - Some(FileTime::from_system_time(time)) - } else { - None - }; - match file { - Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into), - Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim), - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - } -} - -pub(crate) unsafe fn fd_filestat_set_size( - wasi_ctx: &WasiCtx, - _memory: &mut [u8], - fd: wasi::__wasi_fd_t, - st_size: wasi::__wasi_filesize_t, -) -> WasiResult<()> { - trace!("fd_filestat_set_size(fd={:?}, st_size={})", fd, st_size); - - let file = wasi_ctx - .get_entry(fd)? - .as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_SIZE, 0)? - .as_file()?; - - // This check will be unnecessary when rust-lang/rust#63326 is fixed - if st_size > i64::max_value() as u64 { - return Err(WasiError::E2BIG); - } - match file { - Descriptor::OsHandle(fd) => fd.set_len(st_size).map_err(Into::into), - Descriptor::VirtualFile(virt) => virt.filestat_set_size(st_size), - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - } -} - -pub(crate) unsafe fn path_filestat_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - dirflags: wasi::__wasi_lookupflags_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, - filestat_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "path_filestat_get(dirfd={:?}, dirflags={:?}, path_ptr={:#x?}, path_len={}, filestat_ptr={:#x?})", - dirfd, - dirflags, - path_ptr, - path_len, - filestat_ptr - ); - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", path); - - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get( - fe, - wasi::__WASI_RIGHTS_PATH_FILESTAT_GET, - 0, - dirflags, - path, - false, - )?; - let host_filestat = match resolved.dirfd() { - Descriptor::VirtualFile(virt) => virt - .openat(std::path::Path::new(resolved.path()), false, false, 0, 0)? - .filestat_get()?, - _ => hostcalls_impl::path_filestat_get(resolved, dirflags)?, - }; - - trace!(" | *filestat_ptr={:?}", host_filestat); - - enc_filestat_byref(memory, filestat_ptr, host_filestat) -} - -pub(crate) unsafe fn path_filestat_set_times( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - dirflags: wasi::__wasi_lookupflags_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, - st_atim: wasi::__wasi_timestamp_t, - st_mtim: wasi::__wasi_timestamp_t, - fst_flags: wasi::__wasi_fstflags_t, -) -> WasiResult<()> { - trace!( - "path_filestat_set_times(dirfd={:?}, dirflags={:?}, path_ptr={:#x?}, path_len={}, st_atim={}, st_mtim={}, fst_flags={:#x?})", - dirfd, - dirflags, - path_ptr, - path_len, - st_atim, st_mtim, - fst_flags - ); - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", path); - - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get( - fe, - wasi::__WASI_RIGHTS_PATH_FILESTAT_SET_TIMES, - 0, - dirflags, - path, - false, - )?; - - match resolved.dirfd() { - Descriptor::VirtualFile(_virt) => { - unimplemented!("virtual filestat_set_times"); - } - _ => { - hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags) - } - } -} - -pub(crate) unsafe fn path_symlink( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - old_path_ptr: wasi32::uintptr_t, - old_path_len: wasi32::size_t, - dirfd: wasi::__wasi_fd_t, - new_path_ptr: wasi32::uintptr_t, - new_path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "path_symlink(old_path_ptr={:#x?}, old_path_len={}, dirfd={:?}, new_path_ptr={:#x?}, new_path_len={})", - old_path_ptr, - old_path_len, - dirfd, - new_path_ptr, - new_path_len - ); - - let old_path = dec_slice_of_u8(memory, old_path_ptr, old_path_len).and_then(path_from_slice)?; - let new_path = dec_slice_of_u8(memory, new_path_ptr, new_path_len).and_then(path_from_slice)?; - - trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); - trace!(" | (new_path_ptr,new_path_len)='{}'", new_path); - - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved_new = path_get(fe, wasi::__WASI_RIGHTS_PATH_SYMLINK, 0, 0, new_path, true)?; - - match resolved_new.dirfd() { - Descriptor::VirtualFile(_virt) => { - unimplemented!("virtual path_symlink"); - } - _non_virtual => hostcalls_impl::path_symlink(old_path, resolved_new), - } -} - -pub(crate) unsafe fn path_unlink_file( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "path_unlink_file(dirfd={:?}, path_ptr={:#x?}, path_len={})", - dirfd, - path_ptr, - path_len - ); - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", path); - - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get(fe, wasi::__WASI_RIGHTS_PATH_UNLINK_FILE, 0, 0, path, false)?; - - match resolved.dirfd() { - Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()), - _ => hostcalls_impl::path_unlink_file(resolved), - } -} - -pub(crate) unsafe fn path_remove_directory( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - dirfd: wasi::__wasi_fd_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "path_remove_directory(dirfd={:?}, path_ptr={:#x?}, path_len={})", - dirfd, - path_ptr, - path_len - ); - - let path = dec_slice_of_u8(memory, path_ptr, path_len).and_then(path_from_slice)?; - - trace!(" | (path_ptr,path_len)='{}'", path); - - let fe = wasi_ctx.get_entry(dirfd)?; - let resolved = path_get( - fe, - wasi::__WASI_RIGHTS_PATH_REMOVE_DIRECTORY, - 0, - 0, - path, - true, - )?; - - log::debug!("path_remove_directory resolved={:?}", resolved); - - match resolved.dirfd() { - Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()), - _ => hostcalls_impl::path_remove_directory(resolved), - } -} - -pub(crate) unsafe fn fd_prestat_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - prestat_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "fd_prestat_get(fd={:?}, prestat_ptr={:#x?})", - fd, - prestat_ptr - ); - - // TODO: should we validate any rights here? - let fe = wasi_ctx.get_entry(fd)?; - let po_path = fe.preopen_path.as_ref().ok_or(WasiError::ENOTSUP)?; - if fe.file_type != wasi::__WASI_FILETYPE_DIRECTORY { - return Err(WasiError::ENOTDIR); - } - - let path = host_impl::path_from_host(po_path.as_os_str())?; - - enc_prestat_byref( - memory, - prestat_ptr, - host::__wasi_prestat_t { - tag: wasi::__WASI_PREOPENTYPE_DIR, - u: host::__wasi_prestat_u_t { - dir: host::__wasi_prestat_dir_t { - pr_name_len: path.len(), - }, - }, - }, - ) -} - -pub(crate) unsafe fn fd_prestat_dir_name( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - fd: wasi::__wasi_fd_t, - path_ptr: wasi32::uintptr_t, - path_len: wasi32::size_t, -) -> WasiResult<()> { - trace!( - "fd_prestat_dir_name(fd={:?}, path_ptr={:#x?}, path_len={})", - fd, - path_ptr, - path_len - ); - - // TODO: should we validate any rights here? - let fe = wasi_ctx.get_entry(fd)?; - let po_path = fe.preopen_path.as_ref().ok_or(WasiError::ENOTSUP)?; - if fe.file_type != wasi::__WASI_FILETYPE_DIRECTORY { - return Err(WasiError::ENOTDIR); - } - - let path = host_impl::path_from_host(po_path.as_os_str())?; - - if path.len() > dec_usize(path_len) { - return Err(WasiError::ENAMETOOLONG); - } - - trace!(" | (path_ptr,path_len)='{}'", path); - - 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, -) -> WasiResult<()> { - 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_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); - - fn copy_entities>>( - iter: T, - mut host_buf: &mut [u8], - ) -> WasiResult { - 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..]; - } - } - Ok(host_bufused) - } - - let host_bufused = match file { - Descriptor::OsHandle(file) => { - copy_entities(hostcalls_impl::fd_readdir(file, cookie)?, host_buf)? - } - Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, host_buf)?, - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - }; - - trace!(" | *buf_used={:?}", host_bufused); - - enc_usize_byref(memory, buf_used, host_bufused) -} diff --git a/crates/wasi-common/src/hostcalls_impl/misc.rs b/crates/wasi-common/src/hostcalls_impl/misc.rs deleted file mode 100644 index 9dc99db609..0000000000 --- a/crates/wasi-common/src/hostcalls_impl/misc.rs +++ /dev/null @@ -1,367 +0,0 @@ -#![allow(non_camel_case_types)] -use crate::ctx::WasiCtx; -use crate::entry::Descriptor; -use crate::memory::*; -use crate::sys::hostcalls_impl; -use crate::wasi::{self, WasiError, WasiResult}; -use crate::wasi32; -use log::{error, trace}; -use std::convert::TryFrom; - -pub(crate) fn args_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - argv_ptr: wasi32::uintptr_t, - argv_buf: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "args_get(argv_ptr={:#x?}, argv_buf={:#x?})", - argv_ptr, - argv_buf, - ); - - let mut argv_buf_offset = 0; - let mut argv = vec![]; - - for arg in &wasi_ctx.args { - let arg_bytes = arg.as_bytes_with_nul(); - let arg_ptr = argv_buf + argv_buf_offset; - - enc_slice_of_u8(memory, arg_bytes, arg_ptr)?; - - argv.push(arg_ptr); - - let len = wasi32::uintptr_t::try_from(arg_bytes.len())?; - argv_buf_offset = argv_buf_offset - .checked_add(len) - .ok_or(WasiError::EOVERFLOW)?; - } - - enc_slice_of_wasi32_uintptr(memory, argv.as_slice(), argv_ptr) -} - -pub(crate) fn args_sizes_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - argc_ptr: wasi32::uintptr_t, - argv_buf_size_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "args_sizes_get(argc_ptr={:#x?}, argv_buf_size_ptr={:#x?})", - argc_ptr, - argv_buf_size_ptr, - ); - - let argc = wasi_ctx.args.len(); - let argv_size = wasi_ctx - .args - .iter() - .map(|arg| arg.as_bytes_with_nul().len()) - .sum(); - - trace!(" | *argc_ptr={:?}", argc); - - enc_usize_byref(memory, argc_ptr, argc)?; - - trace!(" | *argv_buf_size_ptr={:?}", argv_size); - - enc_usize_byref(memory, argv_buf_size_ptr, argv_size) -} - -pub(crate) fn environ_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - environ_ptr: wasi32::uintptr_t, - environ_buf: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "environ_get(environ_ptr={:#x?}, environ_buf={:#x?})", - environ_ptr, - environ_buf, - ); - - let mut environ_buf_offset = 0; - let mut environ = vec![]; - - for pair in &wasi_ctx.env { - let env_bytes = pair.as_bytes_with_nul(); - let env_ptr = environ_buf + environ_buf_offset; - - enc_slice_of_u8(memory, env_bytes, env_ptr)?; - - environ.push(env_ptr); - - let len = wasi32::uintptr_t::try_from(env_bytes.len())?; - environ_buf_offset = environ_buf_offset - .checked_add(len) - .ok_or(WasiError::EOVERFLOW)?; - } - - enc_slice_of_wasi32_uintptr(memory, environ.as_slice(), environ_ptr) -} - -pub(crate) fn environ_sizes_get( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - environ_count_ptr: wasi32::uintptr_t, - environ_size_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "environ_sizes_get(environ_count_ptr={:#x?}, environ_size_ptr={:#x?})", - environ_count_ptr, - environ_size_ptr, - ); - - let environ_count = wasi_ctx.env.len(); - let environ_size = wasi_ctx - .env - .iter() - .try_fold(0, |acc: u32, pair| { - acc.checked_add(pair.as_bytes_with_nul().len() as u32) - }) - .ok_or(WasiError::EOVERFLOW)?; - - trace!(" | *environ_count_ptr={:?}", environ_count); - - enc_usize_byref(memory, environ_count_ptr, environ_count)?; - - trace!(" | *environ_size_ptr={:?}", environ_size); - - enc_usize_byref(memory, environ_size_ptr, environ_size as usize) -} - -pub(crate) fn random_get( - _wasi_ctx: &WasiCtx, - memory: &mut [u8], - buf_ptr: wasi32::uintptr_t, - buf_len: wasi32::size_t, -) -> WasiResult<()> { - trace!("random_get(buf_ptr={:#x?}, buf_len={:?})", buf_ptr, buf_len); - - let buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?; - - getrandom::getrandom(buf).map_err(|err| { - error!("getrandom failure: {:?}", err); - WasiError::EIO - }) -} - -pub(crate) fn clock_res_get( - _wasi_ctx: &WasiCtx, - memory: &mut [u8], - clock_id: wasi::__wasi_clockid_t, - resolution_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "clock_res_get(clock_id={:?}, resolution_ptr={:#x?})", - clock_id, - resolution_ptr, - ); - - let resolution = hostcalls_impl::clock_res_get(clock_id)?; - - trace!(" | *resolution_ptr={:?}", resolution); - - enc_timestamp_byref(memory, resolution_ptr, resolution) -} - -pub(crate) fn clock_time_get( - _wasi_ctx: &WasiCtx, - memory: &mut [u8], - clock_id: wasi::__wasi_clockid_t, - precision: wasi::__wasi_timestamp_t, - time_ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "clock_time_get(clock_id={:?}, precision={:?}, time_ptr={:#x?})", - clock_id, - precision, - time_ptr, - ); - - let time = hostcalls_impl::clock_time_get(clock_id)?; - - trace!(" | *time_ptr={:?}", time); - - enc_timestamp_byref(memory, time_ptr, time) -} - -pub(crate) fn sched_yield(_wasi_ctx: &WasiCtx, _memory: &mut [u8]) -> WasiResult<()> { - trace!("sched_yield()"); - - std::thread::yield_now(); - - Ok(()) -} - -pub(crate) fn poll_oneoff( - wasi_ctx: &WasiCtx, - memory: &mut [u8], - input: wasi32::uintptr_t, - output: wasi32::uintptr_t, - nsubscriptions: wasi32::size_t, - nevents: wasi32::uintptr_t, -) -> WasiResult<()> { - trace!( - "poll_oneoff(input={:#x?}, output={:#x?}, nsubscriptions={}, nevents={:#x?})", - input, - output, - nsubscriptions, - nevents, - ); - - if u64::from(nsubscriptions) > wasi::__wasi_filesize_t::max_value() { - return Err(WasiError::EINVAL); - } - - enc_int_byref(memory, nevents, 0)?; - - let subscriptions = dec_subscriptions(memory, input, nsubscriptions)?; - let mut events = Vec::new(); - - let mut timeout: Option = None; - let mut fd_events = Vec::new(); - - // As mandated by the WASI spec: - // > If `nsubscriptions` is 0, returns `errno::inval`. - if subscriptions.is_empty() { - return Err(WasiError::EINVAL); - } - for subscription in subscriptions { - match subscription.u.tag { - wasi::__WASI_EVENTTYPE_CLOCK => { - let clock = unsafe { subscription.u.u.clock }; - let delay = wasi_clock_to_relative_ns_delay(clock)?; - - log::debug!("poll_oneoff event.u.clock = {:?}", clock); - log::debug!("poll_oneoff delay = {:?}ns", delay); - - let current = ClockEventData { - delay, - userdata: subscription.userdata, - }; - let timeout = timeout.get_or_insert(current); - if current.delay < timeout.delay { - *timeout = current; - } - } - wasi::__WASI_EVENTTYPE_FD_READ => { - let wasi_fd = unsafe { subscription.u.u.fd_read.file_descriptor }; - let rights = wasi::__WASI_RIGHTS_FD_READ | wasi::__WASI_RIGHTS_POLL_FD_READWRITE; - match unsafe { - wasi_ctx - .get_entry(wasi_fd) - .and_then(|fe| fe.as_descriptor(rights, 0)) - } { - Ok(descriptor) => fd_events.push(FdEventData { - descriptor, - r#type: wasi::__WASI_EVENTTYPE_FD_READ, - userdata: subscription.userdata, - }), - Err(err) => { - let event = wasi::__wasi_event_t { - userdata: subscription.userdata, - error: err.as_raw_errno(), - r#type: wasi::__WASI_EVENTTYPE_FD_READ, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - nbytes: 0, - flags: 0, - }, - }; - events.push(event); - } - }; - } - - wasi::__WASI_EVENTTYPE_FD_WRITE => { - let wasi_fd = unsafe { subscription.u.u.fd_write.file_descriptor }; - let rights = wasi::__WASI_RIGHTS_FD_WRITE | wasi::__WASI_RIGHTS_POLL_FD_READWRITE; - match unsafe { - wasi_ctx - .get_entry(wasi_fd) - .and_then(|fe| fe.as_descriptor(rights, 0)) - } { - Ok(descriptor) => fd_events.push(FdEventData { - descriptor, - r#type: wasi::__WASI_EVENTTYPE_FD_WRITE, - userdata: subscription.userdata, - }), - Err(err) => { - let event = wasi::__wasi_event_t { - userdata: subscription.userdata, - error: err.as_raw_errno(), - r#type: wasi::__WASI_EVENTTYPE_FD_WRITE, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - nbytes: 0, - flags: 0, - }, - }; - events.push(event); - } - }; - } - - _ => unreachable!(), - } - } - - log::debug!("poll_oneoff timeout = {:?}", timeout); - log::debug!("poll_oneoff fd_events = {:?}", fd_events); - - // The underlying implementation should successfully and immediately return - // if no events have been passed. Such situation may occur if all provided - // events have been filtered out as errors in the code above. - hostcalls_impl::poll_oneoff(timeout, fd_events, &mut events)?; - - let events_count = u32::try_from(events.len()).map_err(|_| WasiError::EOVERFLOW)?; - - enc_events(memory, output, nsubscriptions, events)?; - - trace!(" | *nevents={:?}", events_count); - - enc_int_byref(memory, nevents, events_count) -} - -fn wasi_clock_to_relative_ns_delay( - wasi_clock: wasi::__wasi_subscription_clock_t, -) -> WasiResult { - use std::time::SystemTime; - - if wasi_clock.flags != wasi::__WASI_SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME { - return Ok(u128::from(wasi_clock.timeout)); - } - let now: u128 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_| WasiError::ENOTCAPABLE)? - .as_nanos(); - let deadline = u128::from(wasi_clock.timeout); - Ok(deadline.saturating_sub(now)) -} - -#[derive(Debug, Copy, Clone)] -pub(crate) struct ClockEventData { - pub(crate) delay: u128, // delay is expressed in nanoseconds - pub(crate) userdata: wasi::__wasi_userdata_t, -} - -#[derive(Debug)] -pub(crate) struct FdEventData<'a> { - pub(crate) descriptor: &'a Descriptor, - pub(crate) r#type: wasi::__wasi_eventtype_t, - pub(crate) userdata: wasi::__wasi_userdata_t, -} - -pub(crate) fn proc_exit(_wasi_ctx: &WasiCtx, _memory: &mut [u8], rval: wasi::__wasi_exitcode_t) { - trace!("proc_exit(rval={:?})", rval); - // TODO: Rather than call std::process::exit here, we should trigger a - // stack unwind similar to a trap. - std::process::exit(rval as i32); -} - -pub(crate) fn proc_raise( - _wasi_ctx: &WasiCtx, - _memory: &mut [u8], - _sig: wasi::__wasi_signal_t, -) -> WasiResult<()> { - unimplemented!("proc_raise") -} diff --git a/crates/wasi-common/src/hostcalls_impl/mod.rs b/crates/wasi-common/src/hostcalls_impl/mod.rs deleted file mode 100644 index 924c8c1ba9..0000000000 --- a/crates/wasi-common/src/hostcalls_impl/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod fs; -mod fs_helpers; -mod misc; -mod sock; - -pub(crate) use self::fs::*; -pub(crate) use self::fs_helpers::PathGet; -pub(crate) use self::misc::*; -pub(crate) use self::sock::*; diff --git a/crates/wasi-common/src/hostcalls_impl/sock.rs b/crates/wasi-common/src/hostcalls_impl/sock.rs deleted file mode 100644 index 6e133bb235..0000000000 --- a/crates/wasi-common/src/hostcalls_impl/sock.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::ctx::WasiCtx; -use crate::wasi::{self, WasiResult}; -use crate::wasi32; - -pub fn sock_recv( - _wasi_ctx: &WasiCtx, - _memory: &mut [u8], - _sock: wasi::__wasi_fd_t, - _ri_data: wasi32::uintptr_t, - _ri_data_len: wasi32::size_t, - _ri_flags: wasi::__wasi_riflags_t, - _ro_datalen: wasi32::uintptr_t, - _ro_flags: wasi32::uintptr_t, -) -> WasiResult<()> { - unimplemented!("sock_recv") -} - -pub fn sock_send( - _wasi_ctx: &WasiCtx, - _memory: &mut [u8], - _sock: wasi::__wasi_fd_t, - _si_data: wasi32::uintptr_t, - _si_data_len: wasi32::size_t, - _si_flags: wasi::__wasi_siflags_t, - _so_datalen: wasi32::uintptr_t, -) -> WasiResult<()> { - unimplemented!("sock_send") -} - -pub fn sock_shutdown( - _wasi_ctx: &WasiCtx, - _memory: &mut [u8], - _sock: wasi::__wasi_fd_t, - _how: wasi::__wasi_sdflags_t, -) -> WasiResult<()> { - unimplemented!("sock_shutdown") -} diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 3228dd8bf4..2ba471d02b 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -21,25 +21,21 @@ ) )] +mod clock; mod ctx; mod entry; +mod fd; mod fdpool; pub mod fs; -mod helpers; -mod host; -mod hostcalls_impl; -mod memory; pub mod old; +mod path; +mod poll; mod sandboxed_tty_writer; +mod snapshots; mod sys; mod virtfs; -pub use virtfs::{FileContents, VirtualDirEntry}; pub mod wasi; -pub mod wasi32; - -pub mod hostcalls { - wig::define_hostcalls!("snapshot" "wasi_snapshot_preview1"); -} pub use ctx::{WasiCtx, WasiCtxBuilder}; pub use sys::preopen_dir; +pub use virtfs::{FileContents, VirtualDirEntry}; diff --git a/crates/wasi-common/src/memory.rs b/crates/wasi-common/src/memory.rs deleted file mode 100644 index a45dc537ae..0000000000 --- a/crates/wasi-common/src/memory.rs +++ /dev/null @@ -1,484 +0,0 @@ -//! Functions to store and load data to and from wasm linear memory, -//! transforming them from and to host data types. -//! -//! Endianness concerns are completely encapsulated in this file, so -//! that users outside this file holding a `wasi::*` value never need -//! to consider what endianness it's in. Inside this file, -//! wasm linear-memory-ordered values are called "raw" values, and -//! are not held for long durations. - -#![allow(unused)] -use crate::wasi::{self, WasiError, WasiResult}; -use crate::{host, wasi32}; -use num::PrimInt; -use std::convert::TryFrom; -use std::mem::{align_of, size_of}; -use std::{ptr, slice}; - -fn dec_ptr(memory: &[u8], ptr: wasi32::uintptr_t, len: usize) -> WasiResult<*const u8> { - // check for overflow - let checked_len = (ptr as usize).checked_add(len).ok_or(WasiError::EFAULT)?; - - // translate the pointer - memory - .get(ptr as usize..checked_len) - .ok_or(WasiError::EFAULT) - .map(|mem| mem.as_ptr()) -} - -fn dec_ptr_mut(memory: &mut [u8], ptr: wasi32::uintptr_t, len: usize) -> WasiResult<*mut u8> { - // check for overflow - let checked_len = (ptr as usize).checked_add(len).ok_or(WasiError::EFAULT)?; - - // translate the pointer - memory - .get_mut(ptr as usize..checked_len) - .ok_or(WasiError::EFAULT) - .map(|mem| mem.as_mut_ptr()) -} - -fn dec_ptr_to<'memory, T>(memory: &'memory [u8], ptr: wasi32::uintptr_t) -> WasiResult<&'memory T> { - // check that the ptr is aligned - if ptr as usize % align_of::() != 0 { - return Err(WasiError::EINVAL); - } - - dec_ptr(memory, ptr, size_of::()).map(|p| unsafe { &*(p as *const T) }) -} - -fn dec_ptr_to_mut<'memory, T>( - memory: &'memory mut [u8], - ptr: wasi32::uintptr_t, -) -> WasiResult<&'memory mut T> { - // check that the ptr is aligned - if ptr as usize % align_of::() != 0 { - return Err(WasiError::EINVAL); - } - - dec_ptr_mut(memory, ptr, size_of::()).map(|p| unsafe { &mut *(p as *mut T) }) -} - -/// This function does not perform endianness conversions! -fn dec_raw_byref(memory: &[u8], ptr: wasi32::uintptr_t) -> WasiResult { - dec_ptr_to::(memory, ptr).map(|p| unsafe { ptr::read(p) }) -} - -/// This function does not perform endianness conversions! -fn enc_raw_byref(memory: &mut [u8], ptr: wasi32::uintptr_t, t: T) -> WasiResult<()> { - dec_ptr_to_mut::(memory, ptr).map(|p| unsafe { ptr::write(p, t) }) -} - -pub(crate) fn dec_int_byref(memory: &[u8], ptr: wasi32::uintptr_t) -> WasiResult -where - T: PrimInt, -{ - dec_raw_byref::(memory, ptr).map(|i| PrimInt::from_le(i)) -} - -pub(crate) fn enc_int_byref(memory: &mut [u8], ptr: wasi32::uintptr_t, t: T) -> WasiResult<()> -where - T: PrimInt, -{ - enc_raw_byref::(memory, ptr, PrimInt::to_le(t)) -} - -fn check_slice_of(ptr: wasi32::uintptr_t, len: wasi32::size_t) -> WasiResult<(usize, usize)> { - // check alignment, and that length doesn't overflow - if ptr as usize % align_of::() != 0 { - return Err(WasiError::EINVAL); - } - let len = dec_usize(len); - let len_bytes = if let Some(len) = size_of::().checked_mul(len) { - len - } else { - return Err(WasiError::EOVERFLOW); - }; - - Ok((len, len_bytes)) -} - -fn dec_raw_slice_of<'memory, T>( - memory: &'memory [u8], - ptr: wasi32::uintptr_t, - len: wasi32::size_t, -) -> WasiResult<&'memory [T]> { - let (len, len_bytes) = check_slice_of::(ptr, len)?; - let ptr = dec_ptr(memory, ptr, len_bytes)? as *const T; - Ok(unsafe { slice::from_raw_parts(ptr, len) }) -} - -fn dec_raw_slice_of_mut<'memory, T>( - memory: &'memory mut [u8], - ptr: wasi32::uintptr_t, - len: wasi32::size_t, -) -> WasiResult<&'memory mut [T]> { - let (len, len_bytes) = check_slice_of::(ptr, len)?; - let ptr = dec_ptr_mut(memory, ptr, len_bytes)? as *mut T; - Ok(unsafe { slice::from_raw_parts_mut(ptr, len) }) -} - -fn raw_slice_for_enc<'memory, T>( - memory: &'memory mut [u8], - slice: &[T], - ptr: wasi32::uintptr_t, -) -> WasiResult<&'memory mut [T]> { - // check alignment - if ptr as usize % align_of::() != 0 { - return Err(WasiError::EINVAL); - } - // check that length doesn't overflow - let len_bytes = if let Some(len) = size_of::().checked_mul(slice.len()) { - len - } else { - return Err(WasiError::EOVERFLOW); - }; - - // get the pointer into guest memory - let ptr = dec_ptr_mut(memory, ptr, len_bytes)? as *mut T; - - Ok(unsafe { slice::from_raw_parts_mut(ptr, slice.len()) }) -} - -pub(crate) fn dec_slice_of_u8<'memory>( - memory: &'memory [u8], - ptr: wasi32::uintptr_t, - len: wasi32::size_t, -) -> WasiResult<&'memory [u8]> { - dec_raw_slice_of::(memory, ptr, len) -} - -pub(crate) fn dec_slice_of_mut_u8<'memory>( - memory: &'memory mut [u8], - ptr: wasi32::uintptr_t, - len: wasi32::size_t, -) -> WasiResult<&'memory mut [u8]> { - dec_raw_slice_of_mut::(memory, ptr, len) -} - -pub(crate) fn enc_slice_of_u8( - memory: &mut [u8], - slice: &[u8], - ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - let output = raw_slice_for_enc::(memory, slice, ptr)?; - - output.copy_from_slice(slice); - - Ok(()) -} - -pub(crate) fn enc_slice_of_wasi32_uintptr( - memory: &mut [u8], - slice: &[wasi32::uintptr_t], - ptr: wasi32::uintptr_t, -) -> WasiResult<()> { - let mut output_iter = raw_slice_for_enc::(memory, slice, ptr)?.into_iter(); - - for p in slice { - *output_iter.next().unwrap() = PrimInt::to_le(*p); - } - - Ok(()) -} - -macro_rules! dec_enc_scalar { - ($ty:ident, $dec_byref:ident, $enc_byref:ident) => { - pub(crate) fn $dec_byref( - memory: &mut [u8], - ptr: wasi32::uintptr_t, - ) -> WasiResult { - dec_int_byref::(memory, ptr) - } - - pub(crate) fn $enc_byref( - memory: &mut [u8], - ptr: wasi32::uintptr_t, - x: wasi::$ty, - ) -> WasiResult<()> { - enc_int_byref::(memory, ptr, x) - } - }; -} - -pub(crate) fn dec_ciovec_slice( - memory: &[u8], - ptr: wasi32::uintptr_t, - len: wasi32::size_t, -) -> WasiResult> { - let raw_slice = dec_raw_slice_of::(memory, ptr, len)?; - - raw_slice - .iter() - .map(|raw_iov| { - let len = dec_usize(PrimInt::from_le(raw_iov.buf_len)); - let buf: u32 = PrimInt::from_le(raw_iov.buf); - Ok(host::__wasi_ciovec_t { - buf: dec_ptr(memory, buf, len)? as *const u8, - buf_len: len, - }) - }) - .collect() -} - -pub(crate) fn dec_iovec_slice( - memory: &[u8], - ptr: wasi32::uintptr_t, - len: wasi32::size_t, -) -> WasiResult> { - let raw_slice = dec_raw_slice_of::(memory, ptr, len)?; - - raw_slice - .iter() - .map(|raw_iov| { - let len = dec_usize(PrimInt::from_le(raw_iov.buf_len)); - let buf = PrimInt::from_le(raw_iov.buf); - Ok(host::__wasi_iovec_t { - buf: dec_ptr(memory, buf, len)? as *mut u8, - buf_len: len, - }) - }) - .collect() -} - -dec_enc_scalar!(__wasi_clockid_t, dec_clockid_byref, enc_clockid_byref); -dec_enc_scalar!(__wasi_errno_t, dec_errno_byref, enc_errno_byref); -dec_enc_scalar!(__wasi_exitcode_t, dec_exitcode_byref, enc_exitcode_byref); -dec_enc_scalar!(__wasi_fd_t, dec_fd_byref, enc_fd_byref); -dec_enc_scalar!(__wasi_fdflags_t, dec_fdflags_byref, enc_fdflags_byref); -dec_enc_scalar!(__wasi_device_t, dev_device_byref, enc_device_byref); -dec_enc_scalar!(__wasi_inode_t, dev_inode_byref, enc_inode_byref); -dec_enc_scalar!(__wasi_linkcount_t, dev_linkcount_byref, enc_linkcount_byref); - -pub(crate) fn dec_filestat_byref( - memory: &mut [u8], - filestat_ptr: wasi32::uintptr_t, -) -> WasiResult { - let raw = dec_raw_byref::(memory, filestat_ptr)?; - - Ok(wasi::__wasi_filestat_t { - dev: PrimInt::from_le(raw.dev), - ino: PrimInt::from_le(raw.ino), - filetype: PrimInt::from_le(raw.filetype), - nlink: PrimInt::from_le(raw.nlink), - size: PrimInt::from_le(raw.size), - atim: PrimInt::from_le(raw.atim), - mtim: PrimInt::from_le(raw.mtim), - ctim: PrimInt::from_le(raw.ctim), - }) -} - -pub(crate) fn enc_filestat_byref( - memory: &mut [u8], - filestat_ptr: wasi32::uintptr_t, - filestat: wasi::__wasi_filestat_t, -) -> WasiResult<()> { - let raw = wasi::__wasi_filestat_t { - dev: PrimInt::to_le(filestat.dev), - ino: PrimInt::to_le(filestat.ino), - filetype: PrimInt::to_le(filestat.filetype), - nlink: PrimInt::to_le(filestat.nlink), - size: PrimInt::to_le(filestat.size), - atim: PrimInt::to_le(filestat.atim), - mtim: PrimInt::to_le(filestat.mtim), - ctim: PrimInt::to_le(filestat.ctim), - }; - - enc_raw_byref::(memory, filestat_ptr, raw) -} - -pub(crate) fn dec_fdstat_byref( - memory: &mut [u8], - fdstat_ptr: wasi32::uintptr_t, -) -> WasiResult { - let raw = dec_raw_byref::(memory, fdstat_ptr)?; - - Ok(wasi::__wasi_fdstat_t { - fs_filetype: PrimInt::from_le(raw.fs_filetype), - fs_flags: PrimInt::from_le(raw.fs_flags), - fs_rights_base: PrimInt::from_le(raw.fs_rights_base), - fs_rights_inheriting: PrimInt::from_le(raw.fs_rights_inheriting), - }) -} - -pub(crate) fn enc_fdstat_byref( - memory: &mut [u8], - fdstat_ptr: wasi32::uintptr_t, - fdstat: wasi::__wasi_fdstat_t, -) -> WasiResult<()> { - let raw = wasi::__wasi_fdstat_t { - fs_filetype: PrimInt::to_le(fdstat.fs_filetype), - fs_flags: PrimInt::to_le(fdstat.fs_flags), - fs_rights_base: PrimInt::to_le(fdstat.fs_rights_base), - fs_rights_inheriting: PrimInt::to_le(fdstat.fs_rights_inheriting), - }; - - enc_raw_byref::(memory, fdstat_ptr, raw) -} - -dec_enc_scalar!(__wasi_filedelta_t, dec_filedelta_byref, enc_filedelta_byref); -dec_enc_scalar!(__wasi_filesize_t, dec_filesize_byref, enc_filesize_byref); -dec_enc_scalar!(__wasi_filetype_t, dec_filetype_byref, enc_filetype_byref); - -dec_enc_scalar!( - __wasi_lookupflags_t, - dec_lookupflags_byref, - enc_lookupflags_byref -); - -dec_enc_scalar!(__wasi_oflags_t, dec_oflags_byref, enc_oflags_byref); - -pub(crate) fn dec_prestat_byref( - memory: &mut [u8], - prestat_ptr: wasi32::uintptr_t, -) -> WasiResult { - let raw = dec_raw_byref::(memory, prestat_ptr)?; - - match PrimInt::from_le(raw.tag) { - wasi::__WASI_PREOPENTYPE_DIR => Ok(host::__wasi_prestat_t { - tag: wasi::__WASI_PREOPENTYPE_DIR, - u: host::__wasi_prestat_u_t { - dir: host::__wasi_prestat_dir_t { - pr_name_len: dec_usize(PrimInt::from_le(unsafe { raw.u.dir.pr_name_len })), - }, - }, - }), - _ => Err(WasiError::EINVAL), - } -} - -pub(crate) fn enc_prestat_byref( - memory: &mut [u8], - prestat_ptr: wasi32::uintptr_t, - prestat: host::__wasi_prestat_t, -) -> WasiResult<()> { - let raw = match prestat.tag { - wasi::__WASI_PREOPENTYPE_DIR => Ok(wasi32::__wasi_prestat_t { - tag: PrimInt::to_le(wasi::__WASI_PREOPENTYPE_DIR), - u: wasi32::__wasi_prestat_u_t { - dir: wasi32::__wasi_prestat_dir_t { - pr_name_len: enc_usize(unsafe { prestat.u.dir.pr_name_len }), - }, - }, - }), - _ => Err(WasiError::EINVAL), - }?; - - enc_raw_byref::(memory, prestat_ptr, raw) -} - -dec_enc_scalar!(__wasi_rights_t, dec_rights_byref, enc_rights_byref); -dec_enc_scalar!(__wasi_timestamp_t, dec_timestamp_byref, enc_timestamp_byref); - -pub(crate) fn dec_usize(size: wasi32::size_t) -> usize { - usize::try_from(size).unwrap() -} - -pub(crate) fn enc_usize(size: usize) -> wasi32::size_t { - wasi32::size_t::try_from(size).unwrap() -} - -pub(crate) fn enc_usize_byref( - memory: &mut [u8], - usize_ptr: wasi32::uintptr_t, - host_usize: usize, -) -> WasiResult<()> { - enc_int_byref::(memory, usize_ptr, enc_usize(host_usize)) -} - -dec_enc_scalar!(__wasi_whence_t, dec_whence_byref, enc_whence_byref); - -dec_enc_scalar!( - __wasi_subclockflags_t, - dec_subclockflags_byref, - enc_subclockflags_byref -); - -dec_enc_scalar!( - __wasi_eventrwflags_t, - dec_eventrwflags_byref, - enc_eventrwflags_byref -); - -dec_enc_scalar!(__wasi_eventtype_t, dec_eventtype_byref, enc_eventtype_byref); -dec_enc_scalar!(__wasi_userdata_t, dec_userdata_byref, enc_userdata_byref); - -pub(crate) fn dec_subscriptions( - memory: &mut [u8], - input: wasi32::uintptr_t, - nsubscriptions: wasi32::size_t, -) -> WasiResult> { - let raw_input_slice = - dec_raw_slice_of::(memory, input, nsubscriptions)?; - - raw_input_slice - .into_iter() - .map(|raw_subscription| { - let userdata = PrimInt::from_le(raw_subscription.userdata); - let tag = PrimInt::from_le(raw_subscription.u.tag); - let raw_u = raw_subscription.u.u; - let u = match tag { - wasi::__WASI_EVENTTYPE_CLOCK => wasi::__wasi_subscription_u_u_t { - clock: unsafe { - wasi::__wasi_subscription_clock_t { - id: PrimInt::from_le(raw_u.clock.id), - timeout: PrimInt::from_le(raw_u.clock.timeout), - precision: PrimInt::from_le(raw_u.clock.precision), - flags: PrimInt::from_le(raw_u.clock.flags), - } - }, - }, - wasi::__WASI_EVENTTYPE_FD_READ => wasi::__wasi_subscription_u_u_t { - fd_read: wasi::__wasi_subscription_fd_readwrite_t { - file_descriptor: PrimInt::from_le(unsafe { raw_u.fd_read.file_descriptor }), - }, - }, - wasi::__WASI_EVENTTYPE_FD_WRITE => wasi::__wasi_subscription_u_u_t { - fd_write: wasi::__wasi_subscription_fd_readwrite_t { - file_descriptor: PrimInt::from_le(unsafe { - raw_u.fd_write.file_descriptor - }), - }, - }, - _ => return Err(WasiError::EINVAL), - }; - Ok(wasi::__wasi_subscription_t { - userdata, - u: wasi::__wasi_subscription_u_t { tag, u }, - }) - }) - .collect::>>() -} - -pub(crate) fn enc_events( - memory: &mut [u8], - output: wasi32::uintptr_t, - nsubscriptions: wasi32::size_t, - events: Vec, -) -> WasiResult<()> { - let mut raw_output_iter = - dec_raw_slice_of_mut::(memory, output, nsubscriptions)?.into_iter(); - - for event in events.iter() { - *raw_output_iter - .next() - .expect("the number of events cannot exceed the number of subscriptions") = { - let userdata = PrimInt::to_le(event.userdata); - let error = PrimInt::to_le(event.error); - let r#type = PrimInt::to_le(event.r#type); - let flags = PrimInt::to_le(event.fd_readwrite.flags); - let nbytes = PrimInt::to_le(event.fd_readwrite.nbytes); - wasi::__wasi_event_t { - userdata, - error, - r#type, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { flags, nbytes }, - } - }; - } - - Ok(()) -} - -dec_enc_scalar!(__wasi_advice_t, dec_advice_byref, enc_advice_byref); -dec_enc_scalar!(__wasi_fstflags_t, dec_fstflags_byref, enc_fstflags_byref); -dec_enc_scalar!(__wasi_dircookie_t, dec_dircookie_byref, enc_dircookie_byref); diff --git a/crates/wasi-common/src/old/snapshot_0/entry.rs b/crates/wasi-common/src/old/snapshot_0/entry.rs index 84892a1167..f1863af112 100644 --- a/crates/wasi-common/src/old/snapshot_0/entry.rs +++ b/crates/wasi-common/src/old/snapshot_0/entry.rs @@ -47,8 +47,8 @@ impl Descriptor { /// accessed correctly. /// /// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or -/// stdin handle), and accessing it can only be done via the provided `FdEntry::as_descriptor` and -/// `FdEntry::as_descriptor_mut` methods which require a set of base and inheriting rights to be +/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` and +/// `Entry::as_descriptor_mut` methods which require a set of base and inheriting rights to be /// specified, verifying whether the stored `Descriptor` object is valid for the rights specified. #[derive(Debug)] pub(crate) struct Entry { @@ -61,8 +61,8 @@ pub(crate) struct Entry { } impl Entry { - /// Create an FdEntry with *maximal* possible rights from a given `File`. - /// If this is not desired, the rights of the resulting `FdEntry` should + /// Create an Entry with *maximal* possible rights from a given `File`. + /// If this is not desired, the rights of the resulting `Entry` should /// be manually restricted. pub(crate) fn from(file: fs::File) -> io::Result { unsafe { determine_type_and_access_rights(&file) }.map( @@ -116,13 +116,13 @@ impl Entry { Self::from(dev_null()?) } - /// Convert this `FdEntry` into a host `Descriptor` object provided the specified - /// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object. + /// Convert this `Entry` into a host `Descriptor` object provided the specified + /// `rights_base` and `rights_inheriting` rights are set on this `Entry` object. /// - /// The `FdEntry` can only be converted into a valid `Descriptor` object if + /// The `Entry` can only be converted into a valid `Descriptor` object if /// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting` - /// is a subset of rights attached to this `FdEntry`. The check is performed using - /// `FdEntry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned. + /// is a subset of rights attached to this `Entry`. The check is performed using + /// `Entry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned. pub(crate) fn as_descriptor( &self, rights_base: wasi::__wasi_rights_t, @@ -132,13 +132,13 @@ impl Entry { Ok(&self.descriptor) } - /// Convert this `FdEntry` into a mutable host `Descriptor` object provided the specified - /// `rights_base` and `rights_inheriting` rights are set on this `FdEntry` object. + /// Convert this `Entry` into a mutable host `Descriptor` object provided the specified + /// `rights_base` and `rights_inheriting` rights are set on this `Entry` object. /// - /// The `FdEntry` can only be converted into a valid `Descriptor` object if + /// The `Entry` can only be converted into a valid `Descriptor` object if /// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting` - /// is a subset of rights attached to this `FdEntry`. The check is performed using - /// `FdEntry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned. + /// is a subset of rights attached to this `Entry`. The check is performed using + /// `Entry::validate_rights` method. If the check fails, `Error::ENOTCAPABLE` is returned. pub(crate) fn as_descriptor_mut( &mut self, rights_base: wasi::__wasi_rights_t, @@ -148,8 +148,8 @@ impl Entry { Ok(&mut self.descriptor) } - /// Check if this `FdEntry` object satisfies the specified base rights `rights_base`, and - /// inheriting rights `rights_inheriting`; i.e., if rights attached to this `FdEntry` object + /// Check if this `Entry` object satisfies the specified base rights `rights_base`, and + /// inheriting rights `rights_inheriting`; i.e., if rights attached to this `Entry` object /// are a superset. /// /// Upon unsuccessful check, `Error::ENOTCAPABLE` is returned. diff --git a/crates/wasi-common/src/old/snapshot_0/wasi.rs b/crates/wasi-common/src/old/snapshot_0/wasi.rs index 966fed9bbd..eb039d0013 100644 --- a/crates/wasi-common/src/old/snapshot_0/wasi.rs +++ b/crates/wasi-common/src/old/snapshot_0/wasi.rs @@ -226,3 +226,12 @@ pub fn whence_to_str(whence: __wasi_whence_t) -> &'static str { } pub const __WASI_DIRCOOKIE_START: __wasi_dircookie_t = 0; + +impl crate::fdpool::Fd for __wasi_fd_t { + fn as_raw(&self) -> u32 { + *self + } + fn from_raw(raw_fd: u32) -> Self { + raw_fd + } +} diff --git a/crates/wasi-common/src/hostcalls_impl/fs_helpers.rs b/crates/wasi-common/src/path.rs similarity index 75% rename from crates/wasi-common/src/hostcalls_impl/fs_helpers.rs rename to crates/wasi-common/src/path.rs index 9869f83a24..27a23ba1d9 100644 --- a/crates/wasi-common/src/hostcalls_impl/fs_helpers.rs +++ b/crates/wasi-common/src/path.rs @@ -1,10 +1,12 @@ -#![allow(non_camel_case_types)] -use crate::sys::entry_impl::OsHandle; -use crate::sys::host_impl; -use crate::sys::hostcalls_impl::fs_helpers::*; -use crate::wasi::{self, WasiError, WasiResult}; +use crate::sys; +use crate::sys::entry::OsHandle; +use crate::wasi::{types, Errno, Result}; use crate::{entry::Descriptor, entry::Entry}; use std::path::{Component, Path}; +use std::str; +use wiggle_runtime::{GuestBorrows, GuestPtr}; + +pub(crate) use sys::path::*; #[derive(Debug)] pub(crate) struct PathGet { @@ -21,11 +23,9 @@ impl PathGet { &self.path } - pub(crate) fn path_create_directory(self) -> WasiResult<()> { + pub(crate) fn create_directory(self) -> Result<()> { match &self.dirfd { - Descriptor::OsHandle(file) => { - crate::sys::hostcalls_impl::path_create_directory(&file, &self.path) - } + Descriptor::OsHandle(file) => create_directory(&file, &self.path), Descriptor::VirtualFile(virt) => virt.create_directory(&Path::new(&self.path)), other => { panic!("invalid descriptor to create directory: {:?}", other); @@ -37,13 +37,12 @@ impl PathGet { self, read: bool, write: bool, - oflags: u16, - fs_flags: u16, - ) -> WasiResult { + oflags: types::Oflags, + fs_flags: types::Fdflags, + ) -> Result { match &self.dirfd { Descriptor::OsHandle(_) => { - crate::sys::hostcalls_impl::path_open(self, read, write, oflags, fs_flags) - .map_err(Into::into) + open(self, read, write, oflags, fs_flags).map_err(Into::into) } Descriptor::VirtualFile(virt) => virt .openat(Path::new(&self.path), read, write, oflags, fs_flags) @@ -65,7 +64,7 @@ impl<'a, 'b> PathRef<'a, 'b> { PathRef { dirfd, path } } - fn open(&self) -> WasiResult { + fn open(&self) -> Result { match self.dirfd { Descriptor::OsHandle(file) => Ok(Descriptor::OsHandle(OsHandle::from(openat( &file, &self.path, @@ -75,8 +74,8 @@ impl<'a, 'b> PathRef<'a, 'b> { Path::new(&self.path), false, false, - wasi::__WASI_OFLAGS_DIRECTORY, - 0, + types::Oflags::DIRECTORY, + types::Fdflags::empty(), ) .map(|file| Descriptor::VirtualFile(file)), other => { @@ -85,7 +84,7 @@ impl<'a, 'b> PathRef<'a, 'b> { } } - fn readlink(&self) -> WasiResult { + fn readlink(&self) -> Result { match self.dirfd { Descriptor::OsHandle(file) => readlinkat(file, self.path), Descriptor::VirtualFile(virt) => { @@ -101,24 +100,33 @@ impl<'a, 'b> PathRef<'a, 'b> { /// Normalizes a path to ensure that the target path is located under the directory provided. /// /// This is a workaround for not having Capsicum support in the OS. -pub(crate) fn path_get( +pub(crate) fn get( fe: &Entry, - rights_base: wasi::__wasi_rights_t, - rights_inheriting: wasi::__wasi_rights_t, - dirflags: wasi::__wasi_lookupflags_t, - path: &str, + rights_base: types::Rights, + rights_inheriting: types::Rights, + dirflags: types::Lookupflags, + path: &GuestPtr<'_, str>, needs_final_component: bool, -) -> WasiResult { +) -> Result { const MAX_SYMLINK_EXPANSIONS: usize = 128; + // Extract path as &str from guest's memory. + let path = unsafe { + let mut bc = GuestBorrows::new(); + let raw = path.as_raw(&mut bc)?; + &*raw + }; + + log::trace!(" | (path_ptr,path_len)='{}'", path); + if path.contains('\0') { - // if contains NUL, return EILSEQ - return Err(WasiError::EILSEQ); + // if contains NUL, return Ilseq + return Err(Errno::Ilseq); } - if fe.file_type != wasi::__WASI_FILETYPE_DIRECTORY { - // if `dirfd` doesn't refer to a directory, return `ENOTDIR`. - return Err(WasiError::ENOTDIR); + if fe.file_type != types::Filetype::Directory { + // if `dirfd` doesn't refer to a directory, return `Notdir`. + return Err(Errno::Notdir); } let dirfd = fe @@ -149,13 +157,13 @@ pub(crate) fn path_get( let ends_with_slash = cur_path.ends_with('/'); let mut components = Path::new(&cur_path).components(); let head = match components.next() { - None => return Err(WasiError::ENOENT), + None => return Err(Errno::Noent), Some(p) => p, }; let tail = components.as_path(); if tail.components().next().is_some() { - let mut tail = host_impl::path_from_host(tail.as_os_str())?; + let mut tail = from_host(tail.as_os_str())?; if ends_with_slash { tail.push('/'); } @@ -167,55 +175,50 @@ pub(crate) fn path_get( match head { Component::Prefix(_) | Component::RootDir => { // path is absolute! - return Err(WasiError::ENOTCAPABLE); + return Err(Errno::Notcapable); } Component::CurDir => { // "." so skip } Component::ParentDir => { // ".." so pop a dir - let _ = dir_stack.pop().ok_or(WasiError::ENOTCAPABLE)?; + let _ = dir_stack.pop().ok_or(Errno::Notcapable)?; // we're not allowed to pop past the original directory if dir_stack.is_empty() { - return Err(WasiError::ENOTCAPABLE); + return Err(Errno::Notcapable); } } Component::Normal(head) => { - let mut head = host_impl::path_from_host(head)?; + let mut head = from_host(head)?; if ends_with_slash { // preserve trailing slash head.push('/'); } if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) { - match PathRef::new( - dir_stack.last().ok_or(WasiError::ENOTCAPABLE)?, - &head, - ) - .open() + match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head) + .open() { Ok(new_dir) => { dir_stack.push(new_dir); } Err(e) => { match e { - WasiError::ELOOP - | WasiError::EMLINK - | WasiError::ENOTDIR => + Errno::Loop | Errno::Mlink | Errno::Notdir => // Check to see if it was a symlink. Linux indicates // this with ENOTDIR because of the O_DIRECTORY flag. { // attempt symlink expansion let mut link_path = PathRef::new( - dir_stack.last().ok_or(WasiError::ENOTCAPABLE)?, + dir_stack.last().ok_or(Errno::Notcapable)?, &head, ) .readlink()?; symlink_expansions += 1; if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return Err(WasiError::ELOOP); + return Err(Errno::Loop); } if head.ends_with('/') { @@ -238,20 +241,17 @@ pub(crate) fn path_get( continue; } else if ends_with_slash - || (dirflags & wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW) != 0 + || dirflags.contains(&types::Lookupflags::SYMLINK_FOLLOW) { // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt // symlink expansion - match PathRef::new( - dir_stack.last().ok_or(WasiError::ENOTCAPABLE)?, - &head, - ) - .readlink() + match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head) + .readlink() { Ok(mut link_path) => { symlink_expansions += 1; if symlink_expansions > MAX_SYMLINK_EXPANSIONS { - return Err(WasiError::ELOOP); + return Err(Errno::Loop); } if head.ends_with('/') { @@ -267,12 +267,12 @@ pub(crate) fn path_get( continue; } Err(e) => { - if e != WasiError::EINVAL - && e != WasiError::ENOENT + if e != Errno::Inval + && e != Errno::Noent // this handles the cases when trying to link to // a destination that already exists, and the target // path contains a slash - && e != WasiError::ENOTDIR + && e != Errno::Notdir { return Err(e); } @@ -282,7 +282,7 @@ pub(crate) fn path_get( // not a symlink, so we're done; return Ok(PathGet { - dirfd: dir_stack.pop().ok_or(WasiError::ENOTCAPABLE)?, + dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?, path: head, }); } @@ -292,7 +292,7 @@ pub(crate) fn path_get( // no further components to process. means we've hit a case like "." or "a/..", or if the // input path has trailing slashes and `needs_final_component` is not set return Ok(PathGet { - dirfd: dir_stack.pop().ok_or(WasiError::ENOTCAPABLE)?, + dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?, path: String::from("."), }); } diff --git a/crates/wasi-common/src/poll.rs b/crates/wasi-common/src/poll.rs new file mode 100644 index 0000000000..3842534dae --- /dev/null +++ b/crates/wasi-common/src/poll.rs @@ -0,0 +1,19 @@ +use crate::entry::Descriptor; +use crate::sys; +use crate::wasi::types; +use std::cell::Ref; + +pub(crate) use sys::poll::*; + +#[derive(Debug, Copy, Clone)] +pub(crate) struct ClockEventData { + pub(crate) delay: u128, // delay is expressed in nanoseconds + pub(crate) userdata: types::Userdata, +} + +#[derive(Debug)] +pub(crate) struct FdEventData<'a> { + pub(crate) descriptor: Ref<'a, Descriptor>, + pub(crate) r#type: types::Eventtype, + pub(crate) userdata: types::Userdata, +} diff --git a/crates/wasi-common/src/snapshots/mod.rs b/crates/wasi-common/src/snapshots/mod.rs new file mode 100644 index 0000000000..cd093e1013 --- /dev/null +++ b/crates/wasi-common/src/snapshots/mod.rs @@ -0,0 +1 @@ +mod wasi_snapshot_preview1; diff --git a/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs new file mode 100644 index 0000000000..60d788df13 --- /dev/null +++ b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs @@ -0,0 +1,1333 @@ +use crate::entry::{Descriptor, Entry}; +use crate::sandboxed_tty_writer::SandboxedTTYWriter; +use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1; +use crate::wasi::{types, AsBytes, Errno, Result}; +use crate::WasiCtx; +use crate::{clock, fd, path, poll}; +use log::{debug, error, trace}; +use std::cell::Ref; +use std::convert::TryInto; +use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::ops::DerefMut; +use wiggle_runtime::{GuestBorrows, GuestPtr}; + +impl<'a> WasiSnapshotPreview1 for WasiCtx { + fn args_get<'b>( + &self, + mut argv: GuestPtr<'b, GuestPtr<'b, u8>>, + mut argv_buf: GuestPtr<'b, u8>, + ) -> Result<()> { + trace!("args_get(argv_ptr={:?}, argv_buf={:?})", argv, argv_buf); + + for arg in &self.args { + let arg_bytes = arg.as_bytes_with_nul(); + let elems = arg_bytes.len().try_into()?; + argv_buf.as_array(elems).copy_from_slice(arg_bytes)?; + argv.write(argv_buf)?; + argv = argv.add(1)?; + argv_buf = argv_buf.add(elems)?; + } + + Ok(()) + } + + fn args_sizes_get(&self) -> Result<(types::Size, types::Size)> { + trace!("args_sizes_get"); + + let argc = self.args.len().try_into()?; + let mut argv_size: types::Size = 0; + for arg in &self.args { + let arg_len = arg.as_bytes_with_nul().len().try_into()?; + argv_size = argv_size.checked_add(arg_len).ok_or(Errno::Overflow)?; + } + + trace!(" | *argc_ptr={:?}", argc); + trace!(" | *argv_buf_size_ptr={:?}", argv_size); + + Ok((argc, argv_size)) + } + + fn environ_get<'b>( + &self, + mut environ: GuestPtr<'b, GuestPtr<'b, u8>>, + mut environ_buf: GuestPtr<'b, u8>, + ) -> Result<()> { + trace!( + "environ_get(environ={:?}, environ_buf={:?})", + environ, + environ_buf + ); + + for e in &self.env { + let environ_bytes = e.as_bytes_with_nul(); + let elems = environ_bytes.len().try_into()?; + environ_buf.as_array(elems).copy_from_slice(environ_bytes)?; + environ.write(environ_buf)?; + environ = environ.add(1)?; + environ_buf = environ_buf.add(elems)?; + } + + Ok(()) + } + + fn environ_sizes_get(&self) -> Result<(types::Size, types::Size)> { + trace!("environ_sizes_get"); + + let environ_count = self.env.len().try_into()?; + let mut environ_size: types::Size = 0; + for environ in &self.env { + let env_len = environ.as_bytes_with_nul().len().try_into()?; + environ_size = environ_size.checked_add(env_len).ok_or(Errno::Overflow)?; + } + + trace!(" | *environ_count_ptr={:?}", environ_count); + trace!(" | *environ_size_ptr={:?}", environ_size); + + Ok((environ_count, environ_size)) + } + + fn clock_res_get(&self, id: types::Clockid) -> Result { + trace!("clock_res_get(id={:?})", id); + let resolution = clock::res_get(id)?; + trace!(" | *resolution_ptr={:?}", resolution); + Ok(resolution) + } + + fn clock_time_get( + &self, + id: types::Clockid, + precision: types::Timestamp, + ) -> Result { + trace!("clock_time_get(id={:?}, precision={:?})", id, precision); + let time = clock::time_get(id)?; + trace!(" | *time_ptr={:?}", time); + Ok(time) + } + + fn fd_advise( + &self, + fd: types::Fd, + offset: types::Filesize, + len: types::Filesize, + advice: types::Advice, + ) -> Result<()> { + trace!( + "fd_advise(fd={:?}, offset={}, len={}, advice={})", + fd, + offset, + len, + advice + ); + let mut entry = self.get_entry_mut(fd)?; + let file = entry + .as_descriptor_mut(types::Rights::FD_ADVISE, types::Rights::empty())? + .as_file_mut()?; + match file { + Descriptor::OsHandle(fd) => fd::advise(&fd, advice, offset, len), + Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len), + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + } + } + + fn fd_allocate( + &self, + fd: types::Fd, + offset: types::Filesize, + len: types::Filesize, + ) -> Result<()> { + trace!("fd_allocate(fd={:?}, offset={}, len={})", fd, offset, len); + + let entry = self.get_entry(fd)?; + let file = entry + .as_descriptor(types::Rights::FD_ALLOCATE, types::Rights::empty())? + .as_file()?; + match file { + Descriptor::OsHandle(fd) => { + let metadata = fd.metadata()?; + let current_size = metadata.len(); + let wanted_size = offset.checked_add(len).ok_or(Errno::TooBig)?; + // This check will be unnecessary when rust-lang/rust#63326 is fixed + if wanted_size > i64::max_value() as u64 { + return Err(Errno::TooBig); + } + if wanted_size > current_size { + fd.set_len(wanted_size)?; + } + Ok(()) + } + Descriptor::VirtualFile(virt) => virt.allocate(offset, len), + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + } + } + + fn fd_close(&self, fd: types::Fd) -> Result<()> { + trace!("fd_close(fd={:?})", fd); + + if let Ok(fe) = self.get_entry(fd) { + // can't close preopened files + if fe.preopen_path.is_some() { + return Err(Errno::Notsup); + } + } + + self.remove_entry(fd)?; + Ok(()) + } + + fn fd_datasync(&self, fd: types::Fd) -> Result<()> { + trace!("fd_datasync(fd={:?})", fd); + + let entry = self.get_entry(fd)?; + let file = entry.as_descriptor(types::Rights::FD_DATASYNC, types::Rights::empty())?; + match file { + Descriptor::OsHandle(fd) => fd.sync_data()?, + Descriptor::VirtualFile(virt) => virt.datasync()?, + other => other.as_os_handle().sync_data()?, + }; + Ok(()) + } + + fn fd_fdstat_get(&self, fd: types::Fd) -> Result { + trace!("fd_fdstat_get(fd={:?})", fd); + + let fe = self.get_entry(fd)?; + let wasi_file = fe.as_descriptor(types::Rights::empty(), types::Rights::empty())?; + let fs_flags = match wasi_file { + Descriptor::OsHandle(wasi_fd) => fd::fdstat_get(&wasi_fd)?, + Descriptor::VirtualFile(virt) => virt.fdstat_get(), + other => fd::fdstat_get(&other.as_os_handle())?, + }; + let fdstat = types::Fdstat { + fs_filetype: fe.file_type, + fs_rights_base: fe.rights_base, + fs_rights_inheriting: fe.rights_inheriting, + fs_flags, + }; + + trace!(" | *buf={:?}", fdstat); + + Ok(fdstat) + } + + fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> { + trace!("fd_fdstat_set_flags(fd={:?}, fdflags={})", fd, flags); + + let mut entry = self.get_entry_mut(fd)?; + let descriptor = + entry.as_descriptor_mut(types::Rights::FD_FDSTAT_SET_FLAGS, types::Rights::empty())?; + match descriptor { + Descriptor::OsHandle(handle) => { + let set_result = fd::fdstat_set_flags(&handle, flags)?.map(Descriptor::OsHandle); + if let Some(new_descriptor) = set_result { + *descriptor = new_descriptor; + } + } + Descriptor::VirtualFile(handle) => { + handle.fdstat_set_flags(flags)?; + } + _ => { + let set_result = fd::fdstat_set_flags(&descriptor.as_os_handle(), flags)? + .map(Descriptor::OsHandle); + if let Some(new_descriptor) = set_result { + *descriptor = new_descriptor; + } + } + }; + Ok(()) + } + + fn fd_fdstat_set_rights( + &self, + fd: types::Fd, + fs_rights_base: types::Rights, + fs_rights_inheriting: types::Rights, + ) -> Result<()> { + trace!( + "fd_fdstat_set_rights(fd={:?}, fs_rights_base={}, fs_rights_inheriting={})", + fd, + fs_rights_base, + fs_rights_inheriting + ); + let mut entry = self.get_entry_mut(fd)?; + if entry.rights_base & fs_rights_base != fs_rights_base + || entry.rights_inheriting & fs_rights_inheriting != fs_rights_inheriting + { + return Err(Errno::Notcapable); + } + entry.rights_base = fs_rights_base; + entry.rights_inheriting = fs_rights_inheriting; + Ok(()) + } + + fn fd_filestat_get(&self, fd: types::Fd) -> Result { + trace!("fd_filestat_get(fd={:?})", fd); + + let entry = self.get_entry(fd)?; + let fd = entry + .as_descriptor(types::Rights::FD_FILESTAT_GET, types::Rights::empty())? + .as_file()?; + let host_filestat = match fd { + Descriptor::OsHandle(fd) => fd::filestat_get(&fd)?, + Descriptor::VirtualFile(virt) => virt.filestat_get()?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + + trace!(" | *filestat_ptr={:?}", host_filestat); + + Ok(host_filestat) + } + + fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> { + trace!("fd_filestat_set_size(fd={:?}, size={})", fd, size); + + let entry = self.get_entry(fd)?; + let file = entry + .as_descriptor(types::Rights::FD_FILESTAT_SET_SIZE, types::Rights::empty())? + .as_file()?; + // This check will be unnecessary when rust-lang/rust#63326 is fixed + if size > i64::max_value() as u64 { + return Err(Errno::TooBig); + } + match file { + Descriptor::OsHandle(fd) => fd.set_len(size)?, + Descriptor::VirtualFile(virt) => virt.filestat_set_size(size)?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + Ok(()) + } + + fn fd_filestat_set_times( + &self, + fd: types::Fd, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<()> { + trace!( + "fd_filestat_set_times(fd={:?}, atim={}, mtim={}, fst_flags={})", + fd, + atim, + mtim, + fst_flags + ); + let entry = self.get_entry(fd)?; + let fd = entry + .as_descriptor(types::Rights::FD_FILESTAT_SET_TIMES, types::Rights::empty())? + .as_file()?; + fd::filestat_set_times_impl(&fd, atim, mtim, fst_flags) + } + + fn fd_pread( + &self, + fd: types::Fd, + iovs: &types::IovecArray<'_>, + offset: types::Filesize, + ) -> Result { + trace!("fd_pread(fd={:?}, iovs={:?}, offset={})", fd, iovs, offset,); + + let mut buf = Vec::new(); + let mut bc = GuestBorrows::new(); + bc.borrow_slice(iovs)?; + for iov_ptr in iovs.iter() { + let iov_ptr = iov_ptr?; + let iov: types::Iovec = iov_ptr.read()?; + let slice = unsafe { + let buf = iov.buf.as_array(iov.buf_len); + let raw = buf.as_raw(&mut bc)?; + &mut *raw + }; + buf.push(io::IoSliceMut::new(slice)); + } + + let mut entry = self.get_entry_mut(fd)?; + let file = entry + .as_descriptor_mut( + types::Rights::FD_READ | types::Rights::FD_SEEK, + types::Rights::empty(), + )? + .as_file_mut()?; + + if offset > i64::max_value() as u64 { + return Err(Errno::Io); + } + + let host_nread = match file { + Descriptor::OsHandle(fd) => { + let cur_pos = fd.seek(SeekFrom::Current(0))?; + fd.seek(SeekFrom::Start(offset))?; + let nread = fd.read_vectored(&mut buf)?; + fd.seek(SeekFrom::Start(cur_pos))?; + nread + } + Descriptor::VirtualFile(virt) => virt.preadv(&mut buf, offset)?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + let host_nread = host_nread.try_into()?; + + trace!(" | *nread={:?}", host_nread); + + Ok(host_nread) + } + + fn fd_prestat_get(&self, fd: types::Fd) -> Result { + trace!("fd_prestat_get(fd={:?})", fd); + + // TODO: should we validate any rights here? + let fe = self.get_entry(fd)?; + let po_path = fe.preopen_path.as_ref().ok_or(Errno::Notsup)?; + if fe.file_type != types::Filetype::Directory { + return Err(Errno::Notdir); + } + + let path = path::from_host(po_path.as_os_str())?; + let prestat = types::PrestatDir { + pr_name_len: path.len().try_into()?, + }; + Ok(types::Prestat::Dir(prestat)) + } + + fn fd_prestat_dir_name( + &self, + fd: types::Fd, + path: GuestPtr, + path_len: types::Size, + ) -> Result<()> { + trace!( + "fd_prestat_dir_name(fd={:?}, path={:?}, path_len={})", + fd, + path, + path_len + ); + + // TODO: should we validate any rights here? + let fe = self.get_entry(fd)?; + let po_path = fe.preopen_path.as_ref().ok_or(Errno::Notsup)?; + if fe.file_type != types::Filetype::Directory { + return Err(Errno::Notdir); + } + + let host_path = path::from_host(po_path.as_os_str())?; + let host_path_len = host_path.len().try_into()?; + + if host_path_len > path_len { + return Err(Errno::Nametoolong); + } + + trace!(" | (path_ptr,path_len)='{}'", host_path); + + path.as_array(host_path_len) + .copy_from_slice(host_path.as_bytes())?; + + Ok(()) + } + + fn fd_pwrite( + &self, + fd: types::Fd, + ciovs: &types::CiovecArray<'_>, + offset: types::Filesize, + ) -> Result { + trace!( + "fd_pwrite(fd={:?}, ciovs={:?}, offset={})", + fd, + ciovs, + offset, + ); + + let mut buf = Vec::new(); + let mut bc = GuestBorrows::new(); + bc.borrow_slice(ciovs)?; + for ciov_ptr in ciovs.iter() { + let ciov_ptr = ciov_ptr?; + let ciov: types::Ciovec = ciov_ptr.read()?; + let slice = unsafe { + let buf = ciov.buf.as_array(ciov.buf_len); + let raw = buf.as_raw(&mut bc)?; + &*raw + }; + buf.push(io::IoSlice::new(slice)); + } + + let mut entry = self.get_entry_mut(fd)?; + let file = entry + .as_descriptor_mut( + types::Rights::FD_WRITE | types::Rights::FD_SEEK, + types::Rights::empty(), + )? + .as_file_mut()?; + + if offset > i64::max_value() as u64 { + return Err(Errno::Io); + } + + let host_nwritten = match file { + Descriptor::OsHandle(fd) => { + let cur_pos = fd.seek(SeekFrom::Current(0))?; + fd.seek(SeekFrom::Start(offset))?; + let nwritten = fd.write_vectored(&buf)?; + fd.seek(SeekFrom::Start(cur_pos))?; + nwritten + } + Descriptor::VirtualFile(virt) => virt.pwritev(&buf, offset)?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + trace!(" | *nwritten={:?}", host_nwritten); + let host_nwritten = host_nwritten.try_into()?; + + Ok(host_nwritten) + } + + fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result { + trace!("fd_read(fd={:?}, iovs={:?})", fd, iovs); + + let mut bc = GuestBorrows::new(); + let mut slices = Vec::new(); + bc.borrow_slice(&iovs)?; + for iov_ptr in iovs.iter() { + let iov_ptr = iov_ptr?; + let iov: types::Iovec = iov_ptr.read()?; + let slice = unsafe { + let buf = iov.buf.as_array(iov.buf_len); + let raw = buf.as_raw(&mut bc)?; + &mut *raw + }; + slices.push(io::IoSliceMut::new(slice)); + } + + let mut entry = self.get_entry_mut(fd)?; + let host_nread = + match entry.as_descriptor_mut(types::Rights::FD_READ, types::Rights::empty())? { + Descriptor::OsHandle(file) => file.read_vectored(&mut slices)?, + Descriptor::VirtualFile(virt) => virt.read_vectored(&mut slices)?, + Descriptor::Stdin => io::stdin().read_vectored(&mut slices)?, + _ => return Err(Errno::Badf), + }; + let host_nread = host_nread.try_into()?; + + trace!(" | *nread={:?}", host_nread); + + Ok(host_nread) + } + + fn fd_readdir( + &self, + fd: types::Fd, + buf: GuestPtr, + buf_len: types::Size, + cookie: types::Dircookie, + ) -> Result { + trace!( + "fd_readdir(fd={:?}, buf={:?}, buf_len={}, cookie={:?})", + fd, + buf, + buf_len, + cookie, + ); + + let mut entry = self.get_entry_mut(fd)?; + let file = entry + .as_descriptor_mut(types::Rights::FD_READDIR, types::Rights::empty())? + .as_file_mut()?; + + fn copy_entities>>( + iter: T, + mut buf: GuestPtr, + buf_len: types::Size, + ) -> Result { + let mut bufused = 0; + for pair in iter { + let (dirent, name) = pair?; + let dirent_raw = dirent.as_bytes()?; + let dirent_len: types::Size = dirent_raw.len().try_into()?; + let name_raw = name.as_bytes(); + let name_len = name_raw.len().try_into()?; + let offset = dirent_len.checked_add(name_len).ok_or(Errno::Overflow)?; + if (buf_len - bufused) < offset { + break; + } else { + buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?; + buf = buf.add(dirent_len)?; + buf.as_array(name_len).copy_from_slice(name_raw)?; + buf = buf.add(name_len)?; + bufused += offset; + } + } + Ok(bufused) + } + let bufused = match file { + Descriptor::OsHandle(file) => copy_entities(fd::readdir(file, cookie)?, buf, buf_len)?, + Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, buf, buf_len)?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + + trace!(" | *buf_used={:?}", bufused); + + Ok(bufused) + } + + fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<()> { + trace!("fd_renumber(from={:?}, to={:?})", from, to); + + if !self.contains_entry(from) { + return Err(Errno::Badf); + } + + // Don't allow renumbering over a pre-opened resource. + // TODO: Eventually, we do want to permit this, once libpreopen in + // userspace is capable of removing entries from its tables as well. + if let Ok(from_fe) = self.get_entry(from) { + if from_fe.preopen_path.is_some() { + return Err(Errno::Notsup); + } + } + if let Ok(to_fe) = self.get_entry(to) { + if to_fe.preopen_path.is_some() { + return Err(Errno::Notsup); + } + } + let fe = self.remove_entry(from)?; + self.insert_entry_at(to, fe); + Ok(()) + } + + fn fd_seek( + &self, + fd: types::Fd, + offset: types::Filedelta, + whence: types::Whence, + ) -> Result { + trace!( + "fd_seek(fd={:?}, offset={:?}, whence={:?})", + fd, + offset, + whence, + ); + + let rights = if offset == 0 && whence == types::Whence::Cur { + types::Rights::FD_TELL + } else { + types::Rights::FD_SEEK | types::Rights::FD_TELL + }; + let mut entry = self.get_entry_mut(fd)?; + let file = entry + .as_descriptor_mut(rights, types::Rights::empty())? + .as_file_mut()?; + let pos = match whence { + types::Whence::Cur => SeekFrom::Current(offset), + types::Whence::End => SeekFrom::End(offset), + types::Whence::Set => SeekFrom::Start(offset as u64), + }; + let host_newoffset = match file { + Descriptor::OsHandle(fd) => fd.seek(pos)?, + Descriptor::VirtualFile(virt) => virt.seek(pos)?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + + trace!(" | *newoffset={:?}", host_newoffset); + + Ok(host_newoffset) + } + + fn fd_sync(&self, fd: types::Fd) -> Result<()> { + trace!("fd_sync(fd={:?})", fd); + + let entry = self.get_entry(fd)?; + let file = entry + .as_descriptor(types::Rights::FD_SYNC, types::Rights::empty())? + .as_file()?; + match file { + Descriptor::OsHandle(fd) => fd.sync_all()?, + Descriptor::VirtualFile(virt) => virt.sync()?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + Ok(()) + } + + fn fd_tell(&self, fd: types::Fd) -> Result { + trace!("fd_tell(fd={:?})", fd); + + let mut entry = self.get_entry_mut(fd)?; + let file = entry + .as_descriptor_mut(types::Rights::FD_TELL, types::Rights::empty())? + .as_file_mut()?; + let host_offset = match file { + Descriptor::OsHandle(fd) => fd.seek(SeekFrom::Current(0))?, + Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?, + _ => { + unreachable!( + "implementation error: fd should have been checked to not be a stream already" + ); + } + }; + + trace!(" | *newoffset={:?}", host_offset); + + Ok(host_offset) + } + + fn fd_write(&self, fd: types::Fd, ciovs: &types::CiovecArray<'_>) -> Result { + trace!("fd_write(fd={:?}, ciovs={:#x?})", fd, ciovs); + + let mut bc = GuestBorrows::new(); + let mut slices = Vec::new(); + bc.borrow_slice(&ciovs)?; + for ciov_ptr in ciovs.iter() { + let ciov_ptr = ciov_ptr?; + let ciov: types::Ciovec = ciov_ptr.read()?; + let slice = unsafe { + let buf = ciov.buf.as_array(ciov.buf_len); + let raw = buf.as_raw(&mut bc)?; + &*raw + }; + slices.push(io::IoSlice::new(slice)); + } + + // perform unbuffered writes + let mut entry = self.get_entry_mut(fd)?; + let isatty = entry.isatty(); + let desc = entry.as_descriptor_mut(types::Rights::FD_WRITE, types::Rights::empty())?; + let host_nwritten = match desc { + Descriptor::OsHandle(file) => { + if isatty { + SandboxedTTYWriter::new(file.deref_mut()).write_vectored(&slices)? + } else { + file.write_vectored(&slices)? + } + } + Descriptor::VirtualFile(virt) => { + if isatty { + unimplemented!("writes to virtual tty"); + } else { + virt.write_vectored(&slices)? + } + } + Descriptor::Stdin => return Err(Errno::Badf), + Descriptor::Stdout => { + // lock for the duration of the scope + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + let nwritten = if isatty { + SandboxedTTYWriter::new(&mut stdout).write_vectored(&slices)? + } else { + stdout.write_vectored(&slices)? + }; + stdout.flush()?; + nwritten + } + // Always sanitize stderr, even if it's not directly connected to a tty, + // because stderr is meant for diagnostics rather than binary output, + // and may be redirected to a file which could end up being displayed + // on a tty later. + Descriptor::Stderr => { + SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&slices)? + } + }; + trace!(" | *nwritten={:?}", host_nwritten); + Ok(host_nwritten.try_into()?) + } + + fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { + trace!("path_create_directory(dirfd={:?}, path={:?})", dirfd, path); + + let rights = types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY; + let entry = self.get_entry(dirfd)?; + let resolved = path::get( + &entry, + rights, + types::Rights::empty(), + types::Lookupflags::empty(), + path, + false, + )?; + resolved.create_directory() + } + + fn path_filestat_get( + &self, + dirfd: types::Fd, + flags: types::Lookupflags, + path: &GuestPtr<'_, str>, + ) -> Result { + trace!( + "path_filestat_get(dirfd={:?}, flags={:?}, path={:?})", + dirfd, + flags, + path, + ); + + let entry = self.get_entry(dirfd)?; + let resolved = path::get( + &entry, + types::Rights::PATH_FILESTAT_GET, + types::Rights::empty(), + flags, + path, + false, + )?; + let host_filestat = match resolved.dirfd() { + Descriptor::VirtualFile(virt) => virt + .openat( + std::path::Path::new(resolved.path()), + false, + false, + types::Oflags::empty(), + types::Fdflags::empty(), + )? + .filestat_get()?, + _ => path::filestat_get(resolved, flags)?, + }; + + trace!(" | *filestat_ptr={:?}", host_filestat); + + Ok(host_filestat) + } + + fn path_filestat_set_times( + &self, + dirfd: types::Fd, + flags: types::Lookupflags, + path: &GuestPtr<'_, str>, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<()> { + trace!( + "path_filestat_set_times(dirfd={:?}, flags={:?}, path={:?}, atim={}, mtim={}, fst_flags={})", + dirfd, + flags, + path, + atim, + mtim, + fst_flags, + ); + + let entry = self.get_entry(dirfd)?; + let resolved = path::get( + &entry, + types::Rights::PATH_FILESTAT_SET_TIMES, + types::Rights::empty(), + flags, + path, + false, + )?; + match resolved.dirfd() { + Descriptor::VirtualFile(_virt) => { + unimplemented!("virtual filestat_set_times"); + } + _ => path::filestat_set_times(resolved, flags, atim, mtim, fst_flags), + } + } + + fn path_link( + &self, + old_fd: types::Fd, + old_flags: types::Lookupflags, + old_path: &GuestPtr<'_, str>, + new_fd: types::Fd, + new_path: &GuestPtr<'_, str>, + ) -> Result<()> { + trace!( + "path_link(old_fd={:?}, old_flags={:?}, old_path={:?}, new_fd={:?}, new_path={:?})", + old_fd, + old_flags, + old_path, + new_fd, + new_path, + ); + + let old_entry = self.get_entry(old_fd)?; + let resolved_old = path::get( + &old_entry, + types::Rights::PATH_LINK_SOURCE, + types::Rights::empty(), + types::Lookupflags::empty(), + old_path, + false, + )?; + let new_entry = self.get_entry(new_fd)?; + let resolved_new = path::get( + &new_entry, + types::Rights::PATH_LINK_TARGET, + types::Rights::empty(), + types::Lookupflags::empty(), + new_path, + false, + )?; + path::link( + resolved_old, + resolved_new, + old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW), + ) + } + + fn path_open( + &self, + dirfd: types::Fd, + dirflags: types::Lookupflags, + path: &GuestPtr<'_, str>, + oflags: types::Oflags, + fs_rights_base: types::Rights, + fs_rights_inheriting: types::Rights, + fdflags: types::Fdflags, + ) -> Result { + trace!( + "path_open(dirfd={:?}, dirflags={}, path={:?}, oflags={}, fs_rights_base={}, fs_rights_inheriting={}, fdflags={}", + dirfd, + dirflags, + path, + oflags, + fs_rights_base, + fs_rights_inheriting, + fdflags, + ); + + let (needed_base, needed_inheriting) = + path::open_rights(fs_rights_base, fs_rights_inheriting, oflags, fdflags); + + trace!( + " | needed_base = {}, needed_inheriting = {}", + needed_base, + needed_inheriting + ); + + let resolved = { + let entry = self.get_entry(dirfd)?; + path::get( + &entry, + needed_base, + needed_inheriting, + dirflags, + path, + oflags & types::Oflags::CREAT != types::Oflags::empty(), + )? + }; + + // which open mode do we need? + let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR) + != types::Rights::empty(); + let write = fs_rights_base + & (types::Rights::FD_DATASYNC + | types::Rights::FD_WRITE + | types::Rights::FD_ALLOCATE + | types::Rights::FD_FILESTAT_SET_SIZE) + != types::Rights::empty(); + + trace!( + " | calling path_open impl: read={}, write={}", + read, + write + ); + + let fd = resolved.open_with(read, write, oflags, fdflags)?; + let mut fe = Entry::from(fd)?; + // We need to manually deny the rights which are not explicitly requested + // because Entry::from will assign maximal consistent rights. + fe.rights_base &= fs_rights_base; + fe.rights_inheriting &= fs_rights_inheriting; + let guest_fd = self.insert_entry(fe)?; + + trace!(" | *fd={:?}", guest_fd); + + Ok(guest_fd) + } + + fn path_readlink( + &self, + dirfd: types::Fd, + path: &GuestPtr<'_, str>, + buf: GuestPtr, + buf_len: types::Size, + ) -> Result { + trace!( + "path_readlink(dirfd={:?}, path={:?}, buf={:?}, buf_len={})", + dirfd, + path, + buf, + buf_len, + ); + + let entry = self.get_entry(dirfd)?; + let resolved = path::get( + &entry, + types::Rights::PATH_READLINK, + types::Rights::empty(), + types::Lookupflags::empty(), + path, + false, + )?; + + let slice = unsafe { + let mut bc = GuestBorrows::new(); + let buf = buf.as_array(buf_len); + let raw = buf.as_raw(&mut bc)?; + &mut *raw + }; + let host_bufused = match resolved.dirfd() { + Descriptor::VirtualFile(_virt) => { + unimplemented!("virtual readlink"); + } + _ => path::readlink(resolved, slice)?, + }; + let host_bufused = host_bufused.try_into()?; + + trace!(" | (buf_ptr,*buf_used)={:?}", slice); + trace!(" | *buf_used={:?}", host_bufused); + + Ok(host_bufused) + } + + fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { + trace!("path_remove_directory(dirfd={:?}, path={:?})", dirfd, path); + + let entry = self.get_entry(dirfd)?; + let resolved = path::get( + &entry, + types::Rights::PATH_REMOVE_DIRECTORY, + types::Rights::empty(), + types::Lookupflags::empty(), + path, + true, + )?; + + debug!("path_remove_directory resolved={:?}", resolved); + + match resolved.dirfd() { + Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()), + _ => path::remove_directory(resolved), + } + } + + fn path_rename( + &self, + old_fd: types::Fd, + old_path: &GuestPtr<'_, str>, + new_fd: types::Fd, + new_path: &GuestPtr<'_, str>, + ) -> Result<()> { + trace!( + "path_rename(old_fd={:?}, old_path={:?}, new_fd={:?}, new_path={:?})", + old_fd, + old_path, + new_fd, + new_path, + ); + + let entry = self.get_entry(old_fd)?; + let resolved_old = path::get( + &entry, + types::Rights::PATH_RENAME_SOURCE, + types::Rights::empty(), + types::Lookupflags::empty(), + old_path, + true, + )?; + let entry = self.get_entry(new_fd)?; + let resolved_new = path::get( + &entry, + types::Rights::PATH_RENAME_TARGET, + types::Rights::empty(), + types::Lookupflags::empty(), + new_path, + true, + )?; + + log::debug!("path_rename resolved_old={:?}", resolved_old); + log::debug!("path_rename resolved_new={:?}", resolved_new); + + if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) = + (resolved_old.dirfd(), resolved_new.dirfd()) + { + path::rename(resolved_old, resolved_new) + } else { + // Virtual files do not support rename, at the moment, and streams don't have paths to + // rename, so any combination of Descriptor that gets here is an error in the making. + panic!("path_rename with one or more non-OS files"); + } + } + + fn path_symlink( + &self, + old_path: &GuestPtr<'_, str>, + dirfd: types::Fd, + new_path: &GuestPtr<'_, str>, + ) -> Result<()> { + trace!( + "path_symlink(old_path={:?}, dirfd={:?}, new_path={:?})", + old_path, + dirfd, + new_path, + ); + + let entry = self.get_entry(dirfd)?; + let resolved_new = path::get( + &entry, + types::Rights::PATH_SYMLINK, + types::Rights::empty(), + types::Lookupflags::empty(), + new_path, + true, + )?; + + let old_path = unsafe { + let mut bc = GuestBorrows::new(); + let raw = old_path.as_raw(&mut bc)?; + &*raw + }; + + trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); + + match resolved_new.dirfd() { + Descriptor::VirtualFile(_virt) => { + unimplemented!("virtual path_symlink"); + } + _non_virtual => path::symlink(old_path, resolved_new), + } + } + + fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { + trace!("path_unlink_file(dirfd={:?}, path={:?})", dirfd, path); + + let entry = self.get_entry(dirfd)?; + let resolved = path::get( + &entry, + types::Rights::PATH_UNLINK_FILE, + types::Rights::empty(), + types::Lookupflags::empty(), + path, + false, + )?; + match resolved.dirfd() { + Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()), + _ => path::unlink_file(resolved), + } + } + + fn poll_oneoff( + &self, + in_: GuestPtr, + out: GuestPtr, + nsubscriptions: types::Size, + ) -> Result { + trace!( + "poll_oneoff(in_={:?}, out={:?}, nsubscriptions={})", + in_, + out, + nsubscriptions, + ); + + if u64::from(nsubscriptions) > types::Filesize::max_value() { + return Err(Errno::Inval); + } + + let mut subscriptions = Vec::new(); + let mut bc = GuestBorrows::new(); + let subs = in_.as_array(nsubscriptions); + bc.borrow_slice(&subs)?; + for sub_ptr in subs.iter() { + let sub_ptr = sub_ptr?; + let sub: types::Subscription = sub_ptr.read()?; + subscriptions.push(sub); + } + + let mut events = Vec::new(); + let mut timeout: Option = None; + let mut fd_events = Vec::new(); + + // As mandated by the WASI spec: + // > If `nsubscriptions` is 0, returns `errno::inval`. + if subscriptions.is_empty() { + return Err(Errno::Inval); + } + + for subscription in subscriptions { + match subscription.u { + types::SubscriptionU::Clock(clock) => { + let delay = clock::to_relative_ns_delay(clock)?; + log::debug!("poll_oneoff event.u.clock = {:?}", clock); + log::debug!("poll_oneoff delay = {:?}ns", delay); + let current = poll::ClockEventData { + delay, + userdata: subscription.userdata, + }; + let timeout = timeout.get_or_insert(current); + if current.delay < timeout.delay { + *timeout = current; + } + } + types::SubscriptionU::FdRead(fd_read) => { + let fd = fd_read.file_descriptor; + let rights = types::Rights::FD_READ | types::Rights::POLL_FD_READWRITE; + let entry = match self.get_entry(fd) { + Ok(entry) => entry, + Err(error) => { + events.push(types::Event { + userdata: subscription.userdata, + error, + type_: types::Eventtype::FdRead, + fd_readwrite: types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::empty(), + }, + }); + continue; + } + }; + // TODO Can this be simplified? + // Validate rights on the entry before converting into host descriptor. + entry.validate_rights(rights, types::Rights::empty())?; + let descriptor = Ref::map(entry, |entry| { + entry.as_descriptor(rights, types::Rights::empty()).unwrap() + }); + fd_events.push(poll::FdEventData { + descriptor, + r#type: types::Eventtype::FdRead, + userdata: subscription.userdata, + }); + } + types::SubscriptionU::FdWrite(fd_write) => { + let fd = fd_write.file_descriptor; + let rights = types::Rights::FD_WRITE | types::Rights::POLL_FD_READWRITE; + let entry = match self.get_entry(fd) { + Ok(entry) => entry, + Err(error) => { + events.push(types::Event { + userdata: subscription.userdata, + error, + type_: types::Eventtype::FdWrite, + fd_readwrite: types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::empty(), + }, + }); + continue; + } + }; + // TODO Can this be simplified? + // Validate rights on the entry before converting into host descriptor. + entry.validate_rights(rights, types::Rights::empty())?; + let descriptor = Ref::map(entry, |entry| { + entry.as_descriptor(rights, types::Rights::empty()).unwrap() + }); + fd_events.push(poll::FdEventData { + descriptor, + r#type: types::Eventtype::FdWrite, + userdata: subscription.userdata, + }); + } + } + } + log::debug!("poll_oneoff events = {:?}", events); + log::debug!("poll_oneoff timeout = {:?}", timeout); + log::debug!("poll_oneoff fd_events = {:?}", fd_events); + // The underlying implementation should successfully and immediately return + // if no events have been passed. Such situation may occur if all provided + // events have been filtered out as errors in the code above. + poll::oneoff(timeout, fd_events, &mut events)?; + let nevents = events.len().try_into()?; + + let out_events = out.as_array(nevents); + bc.borrow_slice(&out_events)?; + for (event, event_ptr) in events.into_iter().zip(out_events.iter()) { + let event_ptr = event_ptr?; + event_ptr.write(event)?; + } + + trace!(" | *nevents={:?}", nevents); + + Ok(nevents) + } + + // This is just a temporary to ignore the warning which becomes a hard error + // in the CI. Once we figure out non-returns in `wiggle`, this should be gone. + #[allow(unreachable_code)] + fn proc_exit(&self, rval: types::Exitcode) -> std::result::Result<(), ()> { + trace!("proc_exit(rval={:?})", rval); + // TODO: Rather than call std::process::exit here, we should trigger a + // stack unwind similar to a trap. + std::process::exit(rval as i32); + Ok(()) + } + + fn proc_raise(&self, _sig: types::Signal) -> Result<()> { + unimplemented!("proc_raise") + } + + fn sched_yield(&self) -> Result<()> { + trace!("sched_yield()"); + std::thread::yield_now(); + Ok(()) + } + + fn random_get(&self, buf: GuestPtr, buf_len: types::Size) -> Result<()> { + trace!("random_get(buf={:?}, buf_len={:?})", buf, buf_len); + + let slice = unsafe { + let mut bc = GuestBorrows::new(); + let buf = buf.as_array(buf_len); + let raw = buf.as_raw(&mut bc)?; + &mut *raw + }; + getrandom::getrandom(slice).map_err(|err| { + error!("getrandom failure: {:?}", err); + Errno::Io + }) + } + + fn sock_recv( + &self, + _fd: types::Fd, + _ri_data: &types::IovecArray<'_>, + _ri_flags: types::Riflags, + ) -> Result<(types::Size, types::Roflags)> { + unimplemented!("sock_recv") + } + + fn sock_send( + &self, + _fd: types::Fd, + _si_data: &types::CiovecArray<'_>, + _si_flags: types::Siflags, + ) -> Result { + unimplemented!("sock_send") + } + + fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> { + unimplemented!("sock_shutdown") + } +} diff --git a/crates/wasi-common/src/sys/unix/bsd/fd.rs b/crates/wasi-common/src/sys/unix/bsd/fd.rs new file mode 100644 index 0000000000..11dfd274f5 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/bsd/fd.rs @@ -0,0 +1,26 @@ +use crate::sys::entry::OsHandle; +use crate::wasi::Result; +use std::sync::{Mutex, MutexGuard}; +use yanix::dir::Dir; + +pub(crate) fn get_dir_from_os_handle<'a>( + os_handle: &'a mut OsHandle, +) -> Result> { + 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)) + } + }; + // Note that from this point on, until the end of the parent scope (i.e., enclosing this + // function), we're locking the `Dir` member of this `OsHandle`. + Ok(dir.lock().unwrap()) +} diff --git a/crates/wasi-common/src/sys/unix/bsd/host_impl.rs b/crates/wasi-common/src/sys/unix/bsd/host_impl.rs deleted file mode 100644 index d2ab148fc6..0000000000 --- a/crates/wasi-common/src/sys/unix/bsd/host_impl.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::wasi::{self, WasiResult}; -use std::convert::TryFrom; - -pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; - -pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> WasiResult { - wasi::__wasi_device_t::try_from(dev).map_err(Into::into) -} - -pub(crate) fn stino_from_nix(ino: libc::ino_t) -> WasiResult { - wasi::__wasi_device_t::try_from(ino).map_err(Into::into) -} diff --git a/crates/wasi-common/src/sys/unix/bsd/mod.rs b/crates/wasi-common/src/sys/unix/bsd/mod.rs index b47019d43d..153a194641 100644 --- a/crates/wasi-common/src/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/sys/unix/bsd/mod.rs @@ -1,3 +1,5 @@ -pub(crate) mod host_impl; -pub(crate) mod hostcalls_impl; +pub(crate) mod fd; pub(crate) mod oshandle; +pub(crate) mod path; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::SYNC; diff --git a/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs b/crates/wasi-common/src/sys/unix/bsd/path.rs similarity index 71% rename from crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs rename to crates/wasi-common/src/sys/unix/bsd/path.rs index 74b02773cf..702c0d1b42 100644 --- a/crates/wasi-common/src/sys/unix/bsd/hostcalls_impl.rs +++ b/crates/wasi-common/src/sys/unix/bsd/path.rs @@ -1,8 +1,8 @@ -use crate::hostcalls_impl::PathGet; -use crate::wasi::{WasiError, WasiResult}; +use crate::path::PathGet; +use crate::wasi::{Errno, Result}; use std::os::unix::prelude::AsRawFd; -pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> { +pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { use yanix::file::{unlinkat, AtFlag}; match unsafe { unlinkat( @@ -32,7 +32,7 @@ pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> { } { Ok(stat) => { if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory { - return Err(WasiError::EISDIR); + return Err(Errno::Isdir); } } Err(err) => { @@ -47,7 +47,7 @@ pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> { } } -pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> { +pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { use yanix::file::{fstatat, symlinkat, AtFlag}; log::debug!("path_symlink old_path = {:?}", old_path); @@ -69,7 +69,7 @@ pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> AtFlag::SYMLINK_NOFOLLOW, ) } { - Ok(_) => return Err(WasiError::EEXIST), + Ok(_) => return Err(Errno::Exist), Err(err) => { log::debug!("path_symlink fstatat error: {:?}", err); } @@ -81,7 +81,7 @@ pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> } } -pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiResult<()> { +pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { use yanix::file::{fstatat, renameat, AtFlag}; match unsafe { renameat( @@ -113,9 +113,9 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiR Ok(_) => { // check if destination contains a trailing slash if resolved_new.path().contains('/') { - return Err(WasiError::ENOTDIR); + return Err(Errno::Notdir); } else { - return Err(WasiError::ENOENT); + return Err(Errno::Noent); } } Err(err) => { @@ -129,32 +129,3 @@ pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiR Ok(()) => Ok(()), } } - -pub(crate) mod fd_readdir_impl { - use crate::sys::entry_impl::OsHandle; - use crate::wasi::WasiResult; - use std::sync::{Mutex, MutexGuard}; - use yanix::dir::Dir; - - pub(crate) fn get_dir_from_os_handle<'a>( - os_handle: &'a mut OsHandle, - ) -> WasiResult> { - 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)) - } - }; - // Note that from this point on, until the end of the parent scope (i.e., enclosing this - // function), we're locking the `Dir` member of this `OsHandle`. - Ok(dir.lock().unwrap()) - } -} diff --git a/crates/wasi-common/src/sys/unix/clock.rs b/crates/wasi-common/src/sys/unix/clock.rs new file mode 100644 index 0000000000..d043283462 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/clock.rs @@ -0,0 +1,35 @@ +use crate::wasi::{types, Errno, Result}; +use yanix::clock::{clock_getres, clock_gettime, ClockId}; + +pub(crate) fn res_get(clock_id: types::Clockid) -> Result { + let clock_id: ClockId = clock_id.into(); + let timespec = clock_getres(clock_id)?; + + // convert to nanoseconds, returning EOVERFLOW in case of overflow; + // this is freelancing a bit from the spec but seems like it'll + // be an unusual situation to hit + (timespec.tv_sec as types::Timestamp) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as types::Timestamp)) + .map_or(Err(Errno::Overflow), |resolution| { + // a supported clock can never return zero; this case will probably never get hit, but + // make sure we follow the spec + if resolution == 0 { + Err(Errno::Inval) + } else { + Ok(resolution) + } + }) +} + +pub(crate) fn time_get(clock_id: types::Clockid) -> Result { + let clock_id: ClockId = clock_id.into(); + let timespec = clock_gettime(clock_id)?; + + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as types::Timestamp) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as types::Timestamp)) + .map_or(Err(Errno::Overflow), Ok) +} diff --git a/crates/wasi-common/src/sys/unix/emscripten/mod.rs b/crates/wasi-common/src/sys/unix/emscripten/mod.rs index 04a3a8316d..9fca512635 100644 --- a/crates/wasi-common/src/sys/unix/emscripten/mod.rs +++ b/crates/wasi-common/src/sys/unix/emscripten/mod.rs @@ -1,6 +1,8 @@ -#[path = "../linux/host_impl.rs"] -pub(crate) mod host_impl; -#[path = "../linux/hostcalls_impl.rs"] -pub(crate) mod hostcalls_impl; +#[path = "../linux/fd.rs"] +pub(crate) mod fd; #[path = "../linux/oshandle.rs"] pub(crate) mod oshandle; +#[path = "../linux/path.rs"] +pub(crate) mod path; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; diff --git a/crates/wasi-common/src/sys/unix/entry_impl.rs b/crates/wasi-common/src/sys/unix/entry.rs similarity index 67% rename from crates/wasi-common/src/sys/unix/entry_impl.rs rename to crates/wasi-common/src/sys/unix/entry.rs index d6bc582235..2592ef7cd2 100644 --- a/crates/wasi-common/src/sys/unix/entry_impl.rs +++ b/crates/wasi-common/src/sys/unix/entry.rs @@ -1,11 +1,11 @@ use crate::entry::{Descriptor, OsHandleRef}; -use crate::{sys::unix::sys_impl, wasi}; +use crate::wasi::{types, RightsExt}; use std::fs::File; use std::io; use std::mem::ManuallyDrop; use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd}; -pub(crate) use sys_impl::oshandle::*; +pub(crate) use super::sys_impl::oshandle::*; impl AsRawFd for Descriptor { fn as_raw_fd(&self) -> RawFd { @@ -33,20 +33,16 @@ pub(crate) fn descriptor_as_oshandle<'lifetime>( /// This function is unsafe because it operates on a raw file descriptor. pub(crate) unsafe fn determine_type_and_access_rights( fd: &Fd, -) -> io::Result<( - wasi::__wasi_filetype_t, - wasi::__wasi_rights_t, - wasi::__wasi_rights_t, -)> { +) -> io::Result<(types::Filetype, types::Rights, types::Rights)> { let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(fd)?; use yanix::{fcntl, file::OFlag}; let flags = fcntl::get_status_flags(fd.as_raw_fd())?; let accmode = flags & OFlag::ACCMODE; if accmode == OFlag::RDONLY { - rights_base &= !wasi::__WASI_RIGHTS_FD_WRITE; + rights_base &= !types::Rights::FD_WRITE; } else if accmode == OFlag::WRONLY { - rights_base &= !wasi::__WASI_RIGHTS_FD_READ; + rights_base &= !types::Rights::FD_READ; } Ok((file_type, rights_base, rights_inheriting)) @@ -57,11 +53,7 @@ pub(crate) unsafe fn determine_type_and_access_rights( /// This function is unsafe because it operates on a raw file descriptor. pub(crate) unsafe fn determine_type_rights( fd: &Fd, -) -> io::Result<( - wasi::__wasi_filetype_t, - wasi::__wasi_rights_t, - wasi::__wasi_rights_t, -)> { +) -> io::Result<(types::Filetype, types::Rights, types::Rights)> { let (file_type, rights_base, rights_inheriting) = { // we just make a `File` here for convenience; we don't want it to close when it drops let file = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd.as_raw_fd())); @@ -69,62 +61,62 @@ pub(crate) unsafe fn determine_type_rights( if ft.is_block_device() { log::debug!("Host fd {:?} is a block device", fd.as_raw_fd()); ( - wasi::__WASI_FILETYPE_BLOCK_DEVICE, - wasi::RIGHTS_BLOCK_DEVICE_BASE, - wasi::RIGHTS_BLOCK_DEVICE_INHERITING, + types::Filetype::BlockDevice, + types::Rights::block_device_base(), + types::Rights::block_device_inheriting(), ) } else if ft.is_char_device() { log::debug!("Host fd {:?} is a char device", fd.as_raw_fd()); use yanix::file::isatty; if isatty(fd.as_raw_fd())? { ( - wasi::__WASI_FILETYPE_CHARACTER_DEVICE, - wasi::RIGHTS_TTY_BASE, - wasi::RIGHTS_TTY_BASE, + types::Filetype::CharacterDevice, + types::Rights::tty_base(), + types::Rights::tty_base(), ) } else { ( - wasi::__WASI_FILETYPE_CHARACTER_DEVICE, - wasi::RIGHTS_CHARACTER_DEVICE_BASE, - wasi::RIGHTS_CHARACTER_DEVICE_INHERITING, + types::Filetype::CharacterDevice, + types::Rights::character_device_base(), + types::Rights::character_device_inheriting(), ) } } else if ft.is_dir() { log::debug!("Host fd {:?} is a directory", fd.as_raw_fd()); ( - wasi::__WASI_FILETYPE_DIRECTORY, - wasi::RIGHTS_DIRECTORY_BASE, - wasi::RIGHTS_DIRECTORY_INHERITING, + types::Filetype::Directory, + types::Rights::directory_base(), + types::Rights::directory_inheriting(), ) } else if ft.is_file() { log::debug!("Host fd {:?} is a file", fd.as_raw_fd()); ( - wasi::__WASI_FILETYPE_REGULAR_FILE, - wasi::RIGHTS_REGULAR_FILE_BASE, - wasi::RIGHTS_REGULAR_FILE_INHERITING, + types::Filetype::RegularFile, + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), ) } else if ft.is_socket() { log::debug!("Host fd {:?} is a socket", fd.as_raw_fd()); use yanix::socket::{get_socket_type, SockType}; match get_socket_type(fd.as_raw_fd())? { SockType::Datagram => ( - wasi::__WASI_FILETYPE_SOCKET_DGRAM, - wasi::RIGHTS_SOCKET_BASE, - wasi::RIGHTS_SOCKET_INHERITING, + types::Filetype::SocketDgram, + types::Rights::socket_base(), + types::Rights::socket_inheriting(), ), SockType::Stream => ( - wasi::__WASI_FILETYPE_SOCKET_STREAM, - wasi::RIGHTS_SOCKET_BASE, - wasi::RIGHTS_SOCKET_INHERITING, + types::Filetype::SocketStream, + types::Rights::socket_base(), + types::Rights::socket_inheriting(), ), _ => return Err(io::Error::from_raw_os_error(libc::EINVAL)), } } else if ft.is_fifo() { log::debug!("Host fd {:?} is a fifo", fd.as_raw_fd()); ( - wasi::__WASI_FILETYPE_UNKNOWN, - wasi::RIGHTS_REGULAR_FILE_BASE, - wasi::RIGHTS_REGULAR_FILE_INHERITING, + types::Filetype::Unknown, + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), ) } else { log::debug!("Host fd {:?} is unknown", fd.as_raw_fd()); diff --git a/crates/wasi-common/src/sys/unix/fd.rs b/crates/wasi-common/src/sys/unix/fd.rs new file mode 100644 index 0000000000..dd7eec5f07 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/fd.rs @@ -0,0 +1,78 @@ +use super::sys_impl::fd::get_dir_from_os_handle; +use crate::sys::entry::OsHandle; +use crate::wasi::{self, types, Result}; +use std::convert::TryInto; +use std::fs::File; +use std::os::unix::prelude::AsRawFd; + +pub(crate) fn fdstat_get(fd: &File) -> Result { + let fdflags = unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd())? }; + Ok(fdflags.into()) +} + +pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result> { + unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), fdflags.into())? }; + // TODO why are we returning Ok(None) here? + Ok(None) +} + +pub(crate) fn advise( + file: &File, + advice: types::Advice, + offset: types::Filesize, + len: types::Filesize, +) -> Result<()> { + use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice}; + let offset = offset.try_into()?; + let len = len.try_into()?; + let host_advice = match advice { + types::Advice::Dontneed => PosixFadviseAdvice::DontNeed, + types::Advice::Sequential => PosixFadviseAdvice::Sequential, + types::Advice::Willneed => PosixFadviseAdvice::WillNeed, + types::Advice::Noreuse => PosixFadviseAdvice::NoReuse, + types::Advice::Random => PosixFadviseAdvice::Random, + types::Advice::Normal => PosixFadviseAdvice::Normal, + }; + unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice)? }; + Ok(()) +} + +pub(crate) fn filestat_get(file: &std::fs::File) -> Result { + use yanix::file::fstat; + let stat = unsafe { fstat(file.as_raw_fd())? }; + Ok(stat.try_into()?) +} + +pub(crate) fn readdir<'a>( + os_handle: &'a mut OsHandle, + cookie: types::Dircookie, +) -> Result> + 'a> { + use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc}; + + // Get an instance of `Dir`; this is host-specific due to intricasies + // of managing a dir stream between Linux and BSD *nixes + let mut dir = get_dir_from_os_handle(os_handle)?; + + // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, + // new items may not be returned to the caller. + if cookie == 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::new(dir).map(|entry| { + let entry: Entry = entry?; + let name = entry.file_name().to_str()?.to_owned(); + let dirent = types::Dirent { + d_next: entry.seek_loc()?.to_raw().try_into()?, + d_ino: entry.ino(), + d_namlen: name.len().try_into()?, + d_type: entry.file_type().into(), + }; + Ok((dirent, name)) + })) +} diff --git a/crates/wasi-common/src/sys/unix/host_impl.rs b/crates/wasi-common/src/sys/unix/host_impl.rs deleted file mode 100644 index 3a3010b1f7..0000000000 --- a/crates/wasi-common/src/sys/unix/host_impl.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! WASI host types specific to *nix host. -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(dead_code)] -use crate::host::FileType; -use crate::wasi::{self, WasiError, WasiResult}; -use crate::{helpers, sys::unix::sys_impl}; -use std::ffi::OsStr; -use std::io; -use std::os::unix::prelude::OsStrExt; -use yanix::file::OFlag; - -pub(crate) use sys_impl::host_impl::*; - -impl From for WasiError { - fn from(err: io::Error) -> Self { - match err.raw_os_error() { - Some(code) => match code { - libc::EPERM => Self::EPERM, - libc::ENOENT => Self::ENOENT, - libc::ESRCH => Self::ESRCH, - libc::EINTR => Self::EINTR, - libc::EIO => Self::EIO, - libc::ENXIO => Self::ENXIO, - libc::E2BIG => Self::E2BIG, - libc::ENOEXEC => Self::ENOEXEC, - libc::EBADF => Self::EBADF, - libc::ECHILD => Self::ECHILD, - libc::EAGAIN => Self::EAGAIN, - libc::ENOMEM => Self::ENOMEM, - libc::EACCES => Self::EACCES, - libc::EFAULT => Self::EFAULT, - libc::EBUSY => Self::EBUSY, - libc::EEXIST => Self::EEXIST, - libc::EXDEV => Self::EXDEV, - libc::ENODEV => Self::ENODEV, - libc::ENOTDIR => Self::ENOTDIR, - libc::EISDIR => Self::EISDIR, - libc::EINVAL => Self::EINVAL, - libc::ENFILE => Self::ENFILE, - libc::EMFILE => Self::EMFILE, - libc::ENOTTY => Self::ENOTTY, - libc::ETXTBSY => Self::ETXTBSY, - libc::EFBIG => Self::EFBIG, - libc::ENOSPC => Self::ENOSPC, - libc::ESPIPE => Self::ESPIPE, - libc::EROFS => Self::EROFS, - libc::EMLINK => Self::EMLINK, - libc::EPIPE => Self::EPIPE, - libc::EDOM => Self::EDOM, - libc::ERANGE => Self::ERANGE, - libc::EDEADLK => Self::EDEADLK, - libc::ENAMETOOLONG => Self::ENAMETOOLONG, - libc::ENOLCK => Self::ENOLCK, - libc::ENOSYS => Self::ENOSYS, - libc::ENOTEMPTY => Self::ENOTEMPTY, - libc::ELOOP => Self::ELOOP, - libc::ENOMSG => Self::ENOMSG, - libc::EIDRM => Self::EIDRM, - libc::ENOLINK => Self::ENOLINK, - libc::EPROTO => Self::EPROTO, - libc::EMULTIHOP => Self::EMULTIHOP, - libc::EBADMSG => Self::EBADMSG, - libc::EOVERFLOW => Self::EOVERFLOW, - libc::EILSEQ => Self::EILSEQ, - libc::ENOTSOCK => Self::ENOTSOCK, - libc::EDESTADDRREQ => Self::EDESTADDRREQ, - libc::EMSGSIZE => Self::EMSGSIZE, - libc::EPROTOTYPE => Self::EPROTOTYPE, - libc::ENOPROTOOPT => Self::ENOPROTOOPT, - libc::EPROTONOSUPPORT => Self::EPROTONOSUPPORT, - libc::EAFNOSUPPORT => Self::EAFNOSUPPORT, - libc::EADDRINUSE => Self::EADDRINUSE, - libc::EADDRNOTAVAIL => Self::EADDRNOTAVAIL, - libc::ENETDOWN => Self::ENETDOWN, - libc::ENETUNREACH => Self::ENETUNREACH, - libc::ENETRESET => Self::ENETRESET, - libc::ECONNABORTED => Self::ECONNABORTED, - libc::ECONNRESET => Self::ECONNRESET, - libc::ENOBUFS => Self::ENOBUFS, - libc::EISCONN => Self::EISCONN, - libc::ENOTCONN => Self::ENOTCONN, - libc::ETIMEDOUT => Self::ETIMEDOUT, - libc::ECONNREFUSED => Self::ECONNREFUSED, - libc::EHOSTUNREACH => Self::EHOSTUNREACH, - libc::EALREADY => Self::EALREADY, - libc::EINPROGRESS => Self::EINPROGRESS, - libc::ESTALE => Self::ESTALE, - libc::EDQUOT => Self::EDQUOT, - libc::ECANCELED => Self::ECANCELED, - libc::EOWNERDEAD => Self::EOWNERDEAD, - libc::ENOTRECOVERABLE => Self::ENOTRECOVERABLE, - libc::ENOTSUP => Self::ENOTSUP, - x => { - log::debug!("Unknown errno value: {}", x); - Self::EIO - } - }, - None => { - log::debug!("Other I/O error: {}", err); - Self::EIO - } - } - } -} - -pub(crate) fn nix_from_fdflags(fdflags: wasi::__wasi_fdflags_t) -> OFlag { - let mut nix_flags = OFlag::empty(); - if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 { - nix_flags.insert(OFlag::APPEND); - } - if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0 { - nix_flags.insert(OFlag::DSYNC); - } - if fdflags & wasi::__WASI_FDFLAGS_NONBLOCK != 0 { - nix_flags.insert(OFlag::NONBLOCK); - } - if fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0 { - nix_flags.insert(O_RSYNC); - } - if fdflags & wasi::__WASI_FDFLAGS_SYNC != 0 { - nix_flags.insert(OFlag::SYNC); - } - nix_flags -} - -pub(crate) fn fdflags_from_nix(oflags: OFlag) -> wasi::__wasi_fdflags_t { - let mut fdflags = 0; - if oflags.contains(OFlag::APPEND) { - fdflags |= wasi::__WASI_FDFLAGS_APPEND; - } - if oflags.contains(OFlag::DSYNC) { - fdflags |= wasi::__WASI_FDFLAGS_DSYNC; - } - if oflags.contains(OFlag::NONBLOCK) { - fdflags |= wasi::__WASI_FDFLAGS_NONBLOCK; - } - if oflags.contains(O_RSYNC) { - fdflags |= wasi::__WASI_FDFLAGS_RSYNC; - } - if oflags.contains(OFlag::SYNC) { - fdflags |= wasi::__WASI_FDFLAGS_SYNC; - } - fdflags -} - -pub(crate) fn nix_from_oflags(oflags: wasi::__wasi_oflags_t) -> OFlag { - let mut nix_flags = OFlag::empty(); - if oflags & wasi::__WASI_OFLAGS_CREAT != 0 { - nix_flags.insert(OFlag::CREAT); - } - if oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 { - nix_flags.insert(OFlag::DIRECTORY); - } - if oflags & wasi::__WASI_OFLAGS_EXCL != 0 { - nix_flags.insert(OFlag::EXCL); - } - if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 { - nix_flags.insert(OFlag::TRUNC); - } - nix_flags -} - -pub(crate) fn filestat_from_nix(filestat: libc::stat) -> WasiResult { - use std::convert::TryInto; - - fn filestat_to_timestamp(secs: u64, nsecs: u64) -> WasiResult { - secs.checked_mul(1_000_000_000) - .and_then(|sec_nsec| sec_nsec.checked_add(nsecs)) - .ok_or(WasiError::EOVERFLOW) - } - - let filetype = yanix::file::FileType::from_stat_st_mode(filestat.st_mode); - let dev = stdev_from_nix(filestat.st_dev)?; - let ino = stino_from_nix(filestat.st_ino)?; - let atim = filestat_to_timestamp( - filestat.st_atime.try_into()?, - filestat.st_atime_nsec.try_into()?, - )?; - let ctim = filestat_to_timestamp( - filestat.st_ctime.try_into()?, - filestat.st_ctime_nsec.try_into()?, - )?; - let mtim = filestat_to_timestamp( - filestat.st_mtime.try_into()?, - filestat.st_mtime_nsec.try_into()?, - )?; - - Ok(wasi::__wasi_filestat_t { - dev, - ino, - nlink: wasi::__wasi_linkcount_t::from(filestat.st_nlink), - size: filestat.st_size as wasi::__wasi_filesize_t, - atim, - ctim, - mtim, - filetype: FileType::from(filetype).to_wasi(), - }) -} - -/// Creates owned WASI path from OS string. -/// -/// NB WASI spec requires OS string to be valid UTF-8. Otherwise, -/// `__WASI_ERRNO_ILSEQ` error is returned. -pub(crate) fn path_from_host>(s: S) -> WasiResult { - helpers::path_from_slice(s.as_ref().as_bytes()).map(String::from) -} - -impl From for FileType { - fn from(ft: yanix::file::FileType) -> Self { - use yanix::file::FileType::*; - match ft { - RegularFile => Self::RegularFile, - Symlink => Self::Symlink, - Directory => Self::Directory, - BlockDevice => Self::BlockDevice, - CharacterDevice => Self::CharacterDevice, - /* Unknown | Socket | Fifo */ - _ => Self::Unknown, - // TODO how to discriminate between STREAM and DGRAM? - // Perhaps, we should create a more general WASI filetype - // such as __WASI_FILETYPE_SOCKET, and then it would be - // up to the client to check whether it's actually - // STREAM or DGRAM? - } - } -} diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs deleted file mode 100644 index 08507ec2ea..0000000000 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs +++ /dev/null @@ -1,329 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(unused_unsafe)] -use crate::entry::Descriptor; -use crate::host::Dirent; -use crate::hostcalls_impl::PathGet; -use crate::sys::entry_impl::OsHandle; -use crate::sys::{host_impl, unix::sys_impl}; -use crate::wasi::{self, WasiError, WasiResult}; -use std::convert::TryInto; -use std::fs::File; -use std::os::unix::fs::FileExt; -use std::os::unix::prelude::{AsRawFd, FromRawFd}; - -pub(crate) use sys_impl::hostcalls_impl::*; - -pub(crate) fn fd_pread( - file: &File, - buf: &mut [u8], - offset: wasi::__wasi_filesize_t, -) -> WasiResult { - file.read_at(buf, offset).map_err(Into::into) -} - -pub(crate) fn fd_pwrite( - file: &File, - buf: &[u8], - offset: wasi::__wasi_filesize_t, -) -> WasiResult { - file.write_at(buf, offset).map_err(Into::into) -} - -pub(crate) fn fd_fdstat_get(fd: &File) -> WasiResult { - unsafe { yanix::fcntl::get_status_flags(fd.as_raw_fd()) } - .map(host_impl::fdflags_from_nix) - .map_err(Into::into) -} - -pub(crate) fn fd_fdstat_set_flags( - fd: &File, - fdflags: wasi::__wasi_fdflags_t, -) -> WasiResult> { - let nix_flags = host_impl::nix_from_fdflags(fdflags); - unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), nix_flags) } - .map(|_| None) - .map_err(Into::into) -} - -pub(crate) fn fd_advise( - file: &File, - advice: wasi::__wasi_advice_t, - offset: wasi::__wasi_filesize_t, - len: wasi::__wasi_filesize_t, -) -> WasiResult<()> { - use yanix::fadvise::{posix_fadvise, PosixFadviseAdvice}; - let offset = offset.try_into()?; - let len = len.try_into()?; - let host_advice = match advice { - wasi::__WASI_ADVICE_DONTNEED => PosixFadviseAdvice::DontNeed, - wasi::__WASI_ADVICE_SEQUENTIAL => PosixFadviseAdvice::Sequential, - wasi::__WASI_ADVICE_WILLNEED => PosixFadviseAdvice::WillNeed, - wasi::__WASI_ADVICE_NOREUSE => PosixFadviseAdvice::NoReuse, - wasi::__WASI_ADVICE_RANDOM => PosixFadviseAdvice::Random, - wasi::__WASI_ADVICE_NORMAL => PosixFadviseAdvice::Normal, - _ => return Err(WasiError::EINVAL), - }; - unsafe { posix_fadvise(file.as_raw_fd(), offset, len, host_advice) }.map_err(Into::into) -} - -pub(crate) fn path_create_directory(base: &File, path: &str) -> WasiResult<()> { - use yanix::file::{mkdirat, Mode}; - unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777)) }.map_err(Into::into) -} - -pub(crate) fn path_link( - resolved_old: PathGet, - resolved_new: PathGet, - follow_symlinks: bool, -) -> WasiResult<()> { - use yanix::file::{linkat, AtFlag}; - let flags = if follow_symlinks { - AtFlag::SYMLINK_FOLLOW - } else { - AtFlag::empty() - }; - unsafe { - linkat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - resolved_new.dirfd().as_raw_fd(), - resolved_new.path(), - flags, - ) - } - .map_err(Into::into) -} - -pub(crate) fn path_open( - resolved: PathGet, - read: bool, - write: bool, - oflags: wasi::__wasi_oflags_t, - fs_flags: wasi::__wasi_fdflags_t, -) -> WasiResult { - use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag}; - - let mut nix_all_oflags = if read && write { - OFlag::RDWR - } else if write { - OFlag::WRONLY - } else { - OFlag::RDONLY - }; - - // on non-Capsicum systems, we always want nofollow - nix_all_oflags.insert(OFlag::NOFOLLOW); - - // convert open flags - nix_all_oflags.insert(host_impl::nix_from_oflags(oflags)); - - // convert file descriptor flags - nix_all_oflags.insert(host_impl::nix_from_fdflags(fs_flags)); - - // Call openat. Use mode 0o666 so that we follow whatever the user's - // umask is, but don't set the executable flag, because it isn't yet - // meaningful for WASI programs to create executable files. - - log::debug!("path_open resolved = {:?}", resolved); - log::debug!("path_open oflags = {:?}", nix_all_oflags); - - let fd_no = unsafe { - openat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - nix_all_oflags, - Mode::from_bits_truncate(0o666), - ) - }; - let new_fd = match fd_no { - Ok(fd) => fd, - Err(e) => { - match e.raw_os_error().unwrap() { - // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket - libc::ENXIO => { - match unsafe { - fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::SYMLINK_NOFOLLOW, - ) - } { - Ok(stat) => { - if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket { - return Err(WasiError::ENOTSUP); - } - } - Err(err) => { - log::debug!("path_open fstatat error: {:?}", err); - } - } - } - // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY - // on a symlink. - libc::ENOTDIR - if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() => - { - match unsafe { - fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::SYMLINK_NOFOLLOW, - ) - } { - Ok(stat) => { - if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink { - return Err(WasiError::ELOOP); - } - } - Err(err) => { - log::debug!("path_open fstatat error: {:?}", err); - } - } - } - // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on - // a symlink. - libc::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => { - return Err(WasiError::ELOOP); - } - _ => {} - } - - return Err(e.into()); - } - }; - - log::debug!("path_open (host) new_fd = {:?}", new_fd); - - // Determine the type of the new file descriptor and which rights contradict with this type - Ok(OsHandle::from(unsafe { File::from_raw_fd(new_fd) }).into()) -} - -pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> WasiResult { - use std::cmp::min; - use yanix::file::readlinkat; - let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path()) } - .map_err(Into::into) - .and_then(host_impl::path_from_host)?; - let copy_len = min(read_link.len(), buf.len()); - if copy_len > 0 { - buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]); - } - Ok(copy_len) -} - -pub(crate) fn fd_filestat_get(file: &std::fs::File) -> WasiResult { - use yanix::file::fstat; - unsafe { fstat(file.as_raw_fd()) } - .map_err(Into::into) - .and_then(host_impl::filestat_from_nix) -} - -pub(crate) fn path_filestat_get( - resolved: PathGet, - dirflags: wasi::__wasi_lookupflags_t, -) -> WasiResult { - use yanix::file::{fstatat, AtFlag}; - let atflags = match dirflags { - 0 => AtFlag::empty(), - _ => AtFlag::SYMLINK_NOFOLLOW, - }; - unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags) } - .map_err(Into::into) - .and_then(host_impl::filestat_from_nix) -} - -pub(crate) fn path_filestat_set_times( - resolved: PathGet, - dirflags: wasi::__wasi_lookupflags_t, - st_atim: wasi::__wasi_timestamp_t, - st_mtim: wasi::__wasi_timestamp_t, - fst_flags: wasi::__wasi_fstflags_t, -) -> WasiResult<()> { - use std::time::{Duration, UNIX_EPOCH}; - use yanix::filetime::*; - - let set_atim = fst_flags & wasi::__WASI_FSTFLAGS_ATIM != 0; - let set_atim_now = fst_flags & wasi::__WASI_FSTFLAGS_ATIM_NOW != 0; - let set_mtim = fst_flags & wasi::__WASI_FSTFLAGS_MTIM != 0; - let set_mtim_now = fst_flags & wasi::__WASI_FSTFLAGS_MTIM_NOW != 0; - - if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) { - return Err(WasiError::EINVAL); - } - - let symlink_nofollow = wasi::__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW != dirflags; - let atim = if set_atim { - let time = UNIX_EPOCH + Duration::from_nanos(st_atim); - FileTime::FileTime(filetime::FileTime::from_system_time(time)) - } else if set_atim_now { - FileTime::Now - } else { - FileTime::Omit - }; - let mtim = if set_mtim { - let time = UNIX_EPOCH + Duration::from_nanos(st_mtim); - FileTime::FileTime(filetime::FileTime::from_system_time(time)) - } else if set_mtim_now { - FileTime::Now - } else { - FileTime::Omit - }; - - utimensat( - &resolved.dirfd().as_os_handle(), - resolved.path(), - atim, - mtim, - symlink_nofollow, - ) - .map_err(Into::into) -} - -pub(crate) fn path_remove_directory(resolved: PathGet) -> WasiResult<()> { - use yanix::file::{unlinkat, AtFlag}; - - unsafe { - unlinkat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::REMOVEDIR, - ) - } - .map_err(Into::into) -} - -pub(crate) fn fd_readdir<'a>( - os_handle: &'a mut OsHandle, - cookie: wasi::__wasi_dircookie_t, -) -> WasiResult> + 'a> { - use yanix::dir::{DirIter, Entry, EntryExt, SeekLoc}; - - // Get an instance of `Dir`; this is host-specific due to intricasies - // of managing a dir stream between Linux and BSD *nixes - let mut dir = fd_readdir_impl::get_dir_from_os_handle(os_handle)?; - - // 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::new(dir).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()?, - }) - })) -} diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs deleted file mode 100644 index f151feee1a..0000000000 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs_helpers.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(unused_unsafe)] -use crate::sys::host_impl; -use crate::wasi::{self, WasiResult}; -use std::fs::File; -use yanix::file::OFlag; - -pub(crate) fn path_open_rights( - rights_base: wasi::__wasi_rights_t, - rights_inheriting: wasi::__wasi_rights_t, - oflags: wasi::__wasi_oflags_t, - fs_flags: wasi::__wasi_fdflags_t, -) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) { - // which rights are needed on the dirfd? - let mut needed_base = wasi::__WASI_RIGHTS_PATH_OPEN; - let mut needed_inheriting = rights_base | rights_inheriting; - - // convert open flags - let oflags = host_impl::nix_from_oflags(oflags); - if oflags.contains(OFlag::CREAT) { - needed_base |= wasi::__WASI_RIGHTS_PATH_CREATE_FILE; - } - if oflags.contains(OFlag::TRUNC) { - needed_base |= wasi::__WASI_RIGHTS_PATH_FILESTAT_SET_SIZE; - } - - // convert file descriptor flags - let fdflags = host_impl::nix_from_fdflags(fs_flags); - if fdflags.contains(OFlag::DSYNC) { - needed_inheriting |= wasi::__WASI_RIGHTS_FD_DATASYNC; - } - if fdflags.intersects(host_impl::O_RSYNC | OFlag::SYNC) { - needed_inheriting |= wasi::__WASI_RIGHTS_FD_SYNC; - } - - (needed_base, needed_inheriting) -} - -pub(crate) fn openat(dirfd: &File, path: &str) -> WasiResult { - use std::os::unix::prelude::{AsRawFd, FromRawFd}; - use yanix::file::{openat, Mode}; - - log::debug!("path_get openat path = {:?}", path); - - unsafe { - openat( - dirfd.as_raw_fd(), - path, - OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW, - Mode::empty(), - ) - } - .map(|new_fd| unsafe { File::from_raw_fd(new_fd) }) - .map_err(Into::into) -} - -pub(crate) fn readlinkat(dirfd: &File, path: &str) -> WasiResult { - use std::os::unix::prelude::AsRawFd; - use yanix::file::readlinkat; - - log::debug!("path_get readlinkat path = {:?}", path); - - unsafe { readlinkat(dirfd.as_raw_fd(), path) } - .map_err(Into::into) - .and_then(host_impl::path_from_host) -} diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs deleted file mode 100644 index c710923a02..0000000000 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/misc.rs +++ /dev/null @@ -1,213 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(unused_unsafe)] -use crate::hostcalls_impl::{ClockEventData, FdEventData}; -use crate::wasi::{self, WasiError, WasiResult}; -use std::io; -use yanix::clock::{clock_getres, clock_gettime, ClockId}; - -fn wasi_clock_id_to_unix(clock_id: wasi::__wasi_clockid_t) -> WasiResult { - // convert the supported clocks to libc types, or return EINVAL - match clock_id { - wasi::__WASI_CLOCKID_REALTIME => Ok(ClockId::Realtime), - wasi::__WASI_CLOCKID_MONOTONIC => Ok(ClockId::Monotonic), - wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => Ok(ClockId::ProcessCPUTime), - wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => Ok(ClockId::ThreadCPUTime), - _ => Err(WasiError::EINVAL), - } -} - -pub(crate) fn clock_res_get( - clock_id: wasi::__wasi_clockid_t, -) -> WasiResult { - let clock_id = wasi_clock_id_to_unix(clock_id)?; - let timespec = clock_getres(clock_id)?; - - // convert to nanoseconds, returning EOVERFLOW in case of overflow; - // this is freelancing a bit from the spec but seems like it'll - // be an unusual situation to hit - (timespec.tv_sec as wasi::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t)) - .map_or(Err(WasiError::EOVERFLOW), |resolution| { - // a supported clock can never return zero; this case will probably never get hit, but - // make sure we follow the spec - if resolution == 0 { - Err(WasiError::EINVAL) - } else { - Ok(resolution) - } - }) -} - -pub(crate) fn clock_time_get( - clock_id: wasi::__wasi_clockid_t, -) -> WasiResult { - let clock_id = wasi_clock_id_to_unix(clock_id)?; - let timespec = clock_gettime(clock_id)?; - - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as wasi::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as wasi::__wasi_timestamp_t)) - .map_or(Err(WasiError::EOVERFLOW), Ok) -} - -pub(crate) fn poll_oneoff( - timeout: Option, - fd_events: Vec, - events: &mut Vec, -) -> WasiResult<()> { - use std::{convert::TryInto, os::unix::prelude::AsRawFd}; - use yanix::poll::{poll, PollFd, PollFlags}; - - if fd_events.is_empty() && timeout.is_none() { - return Ok(()); - } - - let mut poll_fds: Vec<_> = fd_events - .iter() - .map(|event| { - let mut flags = PollFlags::empty(); - match event.r#type { - wasi::__WASI_EVENTTYPE_FD_READ => flags.insert(PollFlags::POLLIN), - wasi::__WASI_EVENTTYPE_FD_WRITE => flags.insert(PollFlags::POLLOUT), - // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE - // Nothing else has been defined in the specification, and these are also the only two - // events we filtered before. If we get something else here, the code has a serious bug. - _ => unreachable!(), - }; - unsafe { PollFd::new(event.descriptor.as_raw_fd(), flags) } - }) - .collect(); - - let poll_timeout = timeout.map_or(-1, |timeout| { - let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds - delay.try_into().unwrap_or(libc::c_int::max_value()) - }); - log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout); - - let ready = loop { - match poll(&mut poll_fds, poll_timeout) { - Err(_) => { - let last_err = io::Error::last_os_error(); - if last_err.raw_os_error().unwrap() == libc::EINTR { - continue; - } - return Err(last_err.into()); - } - Ok(ready) => break ready, - } - }; - - Ok(if ready == 0 { - poll_oneoff_handle_timeout_event(timeout.expect("timeout should not be None"), events) - } else { - let ready_events = fd_events.into_iter().zip(poll_fds.into_iter()).take(ready); - poll_oneoff_handle_fd_event(ready_events, events)? - }) -} - -fn poll_oneoff_handle_timeout_event( - timeout: ClockEventData, - events: &mut Vec, -) { - events.push(wasi::__wasi_event_t { - userdata: timeout.userdata, - error: wasi::__WASI_ERRNO_SUCCESS, - r#type: wasi::__WASI_EVENTTYPE_CLOCK, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - flags: 0, - nbytes: 0, - }, - }); -} - -fn poll_oneoff_handle_fd_event<'a>( - ready_events: impl Iterator, yanix::poll::PollFd)>, - events: &mut Vec, -) -> WasiResult<()> { - use crate::entry::Descriptor; - use std::{convert::TryInto, os::unix::prelude::AsRawFd}; - use yanix::{file::fionread, poll::PollFlags}; - - fn query_nbytes(fd: &Descriptor) -> WasiResult { - // fionread may overflow for large files, so use another way for regular files. - if let Descriptor::OsHandle(os_handle) = fd { - let meta = os_handle.metadata()?; - if meta.file_type().is_file() { - use yanix::file::tell; - let len = meta.len(); - let host_offset = unsafe { tell(os_handle.as_raw_fd())? }; - return Ok(len - host_offset); - } - } - unsafe { Ok(fionread(fd.as_raw_fd())?.into()) } - } - - for (fd_event, poll_fd) in ready_events { - log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event); - log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd); - - let revents = match poll_fd.revents() { - Some(revents) => revents, - None => continue, - }; - - log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents); - - let nbytes = if fd_event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { - query_nbytes(fd_event.descriptor)? - } else { - 0 - }; - - let output_event = if revents.contains(PollFlags::POLLNVAL) { - wasi::__wasi_event_t { - userdata: fd_event.userdata, - error: wasi::__WASI_ERRNO_BADF, - r#type: fd_event.r#type, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - nbytes: 0, - flags: wasi::__WASI_EVENTRWFLAGS_FD_READWRITE_HANGUP, - }, - } - } else if revents.contains(PollFlags::POLLERR) { - wasi::__wasi_event_t { - userdata: fd_event.userdata, - error: wasi::__WASI_ERRNO_IO, - r#type: fd_event.r#type, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - nbytes: 0, - flags: wasi::__WASI_EVENTRWFLAGS_FD_READWRITE_HANGUP, - }, - } - } else if revents.contains(PollFlags::POLLHUP) { - wasi::__wasi_event_t { - userdata: fd_event.userdata, - error: wasi::__WASI_ERRNO_SUCCESS, - r#type: fd_event.r#type, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - nbytes: 0, - flags: wasi::__WASI_EVENTRWFLAGS_FD_READWRITE_HANGUP, - }, - } - } else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) { - wasi::__wasi_event_t { - userdata: fd_event.userdata, - error: wasi::__WASI_ERRNO_SUCCESS, - r#type: fd_event.r#type, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { - nbytes: nbytes.try_into()?, - flags: 0, - }, - } - } else { - continue; - }; - - events.push(output_event); - } - - Ok(()) -} diff --git a/crates/wasi-common/src/sys/unix/hostcalls_impl/mod.rs b/crates/wasi-common/src/sys/unix/hostcalls_impl/mod.rs deleted file mode 100644 index 69a0d25ae2..0000000000 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Unix-specific hostcalls that implement -//! [WASI](https://github.com/WebAssembly/WASI). -mod fs; -pub(crate) mod fs_helpers; -mod misc; - -pub(crate) use self::fs::*; -pub(crate) use self::misc::*; diff --git a/crates/wasi-common/src/sys/unix/linux/fd.rs b/crates/wasi-common/src/sys/unix/linux/fd.rs new file mode 100644 index 0000000000..6017edaef2 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/linux/fd.rs @@ -0,0 +1,19 @@ +use crate::sys::entry::OsHandle; +use crate::wasi::Result; +use yanix::dir::Dir; + +pub(crate) fn get_dir_from_os_handle(os_handle: &mut OsHandle) -> 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 = os_handle.try_clone()?; + // TODO This doesn't look very clean. Can we do something about it? + // Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter` + // where `T: Deref`. + Ok(Box::new(Dir::from(fd)?)) +} diff --git a/crates/wasi-common/src/sys/unix/linux/host_impl.rs b/crates/wasi-common/src/sys/unix/linux/host_impl.rs deleted file mode 100644 index b1850f9daa..0000000000 --- a/crates/wasi-common/src/sys/unix/linux/host_impl.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::wasi::{self, WasiResult}; - -pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; - -pub(crate) fn stdev_from_nix(dev: libc::dev_t) -> WasiResult { - Ok(wasi::__wasi_device_t::from(dev)) -} - -pub(crate) fn stino_from_nix(ino: libc::ino_t) -> WasiResult { - Ok(wasi::__wasi_device_t::from(ino)) -} diff --git a/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs b/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs deleted file mode 100644 index 9d36e9993a..0000000000 --- a/crates/wasi-common/src/sys/unix/linux/hostcalls_impl.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::entry::Descriptor; -use crate::hostcalls_impl::PathGet; -use crate::wasi::WasiResult; -use std::os::unix::prelude::AsRawFd; - -pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> { - use yanix::file::{unlinkat, AtFlag}; - unsafe { - unlinkat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::empty(), - ) - } - .map_err(Into::into) -} - -pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> { - use yanix::file::symlinkat; - - log::debug!("path_symlink old_path = {:?}", old_path); - log::debug!("path_symlink resolved = {:?}", resolved); - - unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) } - .map_err(Into::into) -} - -pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiResult<()> { - use yanix::file::renameat; - match (resolved_old.dirfd(), resolved_new.dirfd()) { - (Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => { - unsafe { - renameat( - resolved_old_file.as_raw_fd(), - resolved_old.path(), - resolved_new_file.as_raw_fd(), - resolved_new.path(), - ) - } - .map_err(Into::into) - } - _ => { - unimplemented!("path_link with one or more virtual files"); - } - } -} - -pub(crate) mod fd_readdir_impl { - use crate::sys::entry_impl::OsHandle; - use crate::wasi::WasiResult; - use yanix::dir::Dir; - - pub(crate) fn get_dir_from_os_handle(os_handle: &mut OsHandle) -> WasiResult> { - // 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 = os_handle.try_clone()?; - // TODO This doesn't look very clean. Can we do something about it? - // Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter` - // where `T: Deref`. - Ok(Box::new(Dir::from(fd)?)) - } -} diff --git a/crates/wasi-common/src/sys/unix/linux/mod.rs b/crates/wasi-common/src/sys/unix/linux/mod.rs index b47019d43d..a0427fcdc3 100644 --- a/crates/wasi-common/src/sys/unix/linux/mod.rs +++ b/crates/wasi-common/src/sys/unix/linux/mod.rs @@ -1,3 +1,5 @@ -pub(crate) mod host_impl; -pub(crate) mod hostcalls_impl; +pub(crate) mod fd; pub(crate) mod oshandle; +pub(crate) mod path; + +pub(crate) const O_RSYNC: yanix::file::OFlag = yanix::file::OFlag::RSYNC; diff --git a/crates/wasi-common/src/sys/unix/linux/path.rs b/crates/wasi-common/src/sys/unix/linux/path.rs new file mode 100644 index 0000000000..89a53b27f2 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/linux/path.rs @@ -0,0 +1,46 @@ +use crate::entry::Descriptor; +use crate::path::PathGet; +use crate::wasi::Result; +use std::os::unix::prelude::AsRawFd; + +pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { + use yanix::file::{unlinkat, AtFlag}; + unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::empty(), + )? + }; + Ok(()) +} + +pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { + use yanix::file::symlinkat; + + log::debug!("path_symlink old_path = {:?}", old_path); + log::debug!("path_symlink resolved = {:?}", resolved); + + unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path())? }; + Ok(()) +} + +pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { + use yanix::file::renameat; + match (resolved_old.dirfd(), resolved_new.dirfd()) { + (Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => { + unsafe { + renameat( + resolved_old_file.as_raw_fd(), + resolved_old.path(), + resolved_new_file.as_raw_fd(), + resolved_new.path(), + )? + }; + Ok(()) + } + _ => { + unimplemented!("path_link with one or more virtual files"); + } + } +} diff --git a/crates/wasi-common/src/sys/unix/mod.rs b/crates/wasi-common/src/sys/unix/mod.rs index 84badddc25..cafc26f3aa 100644 --- a/crates/wasi-common/src/sys/unix/mod.rs +++ b/crates/wasi-common/src/sys/unix/mod.rs @@ -1,14 +1,16 @@ -pub(crate) mod entry_impl; -pub(crate) mod host_impl; -pub(crate) mod hostcalls_impl; +pub(crate) mod clock; +pub(crate) mod entry; +pub(crate) mod fd; +pub(crate) mod path; +pub(crate) mod poll; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { mod linux; - use self::linux as sys_impl; + use linux as sys_impl; } else if #[cfg(target_os = "emscripten")] { mod emscripten; - use self::emscripten as sys_impl; + use emscripten as sys_impl; } else if #[cfg(any(target_os = "macos", target_os = "netbsd", target_os = "freebsd", @@ -16,18 +18,258 @@ cfg_if::cfg_if! { target_os = "ios", target_os = "dragonfly"))] { mod bsd; - use self::bsd as sys_impl; + use bsd as sys_impl; } } +use crate::wasi::{types, Errno, Result}; +use std::convert::{TryFrom, TryInto}; use std::fs::{File, OpenOptions}; -use std::io::Result; +use std::io; use std::path::Path; +use sys_impl::O_RSYNC; +use yanix::clock::ClockId; +use yanix::file::{AtFlag, OFlag}; -pub(crate) fn dev_null() -> Result { +pub(crate) fn dev_null() -> io::Result { OpenOptions::new().read(true).write(true).open("/dev/null") } -pub fn preopen_dir>(path: P) -> Result { +pub fn preopen_dir>(path: P) -> io::Result { File::open(path) } + +impl From for ClockId { + fn from(clock_id: types::Clockid) -> Self { + use types::Clockid::*; + match clock_id { + Realtime => Self::Realtime, + Monotonic => Self::Monotonic, + ProcessCputimeId => Self::ProcessCPUTime, + ThreadCputimeId => Self::ThreadCPUTime, + } + } +} + +impl From for Errno { + fn from(err: io::Error) -> Self { + match err.raw_os_error() { + Some(code) => match code { + libc::EPERM => Self::Perm, + libc::ENOENT => Self::Noent, + libc::ESRCH => Self::Srch, + libc::EINTR => Self::Intr, + libc::EIO => Self::Io, + libc::ENXIO => Self::Nxio, + libc::E2BIG => Self::TooBig, + libc::ENOEXEC => Self::Noexec, + libc::EBADF => Self::Badf, + libc::ECHILD => Self::Child, + libc::EAGAIN => Self::Again, + libc::ENOMEM => Self::Nomem, + libc::EACCES => Self::Acces, + libc::EFAULT => Self::Fault, + libc::EBUSY => Self::Busy, + libc::EEXIST => Self::Exist, + libc::EXDEV => Self::Xdev, + libc::ENODEV => Self::Nodev, + libc::ENOTDIR => Self::Notdir, + libc::EISDIR => Self::Isdir, + libc::EINVAL => Self::Inval, + libc::ENFILE => Self::Nfile, + libc::EMFILE => Self::Mfile, + libc::ENOTTY => Self::Notty, + libc::ETXTBSY => Self::Txtbsy, + libc::EFBIG => Self::Fbig, + libc::ENOSPC => Self::Nospc, + libc::ESPIPE => Self::Spipe, + libc::EROFS => Self::Rofs, + libc::EMLINK => Self::Mlink, + libc::EPIPE => Self::Pipe, + libc::EDOM => Self::Dom, + libc::ERANGE => Self::Range, + libc::EDEADLK => Self::Deadlk, + libc::ENAMETOOLONG => Self::Nametoolong, + libc::ENOLCK => Self::Nolck, + libc::ENOSYS => Self::Nosys, + libc::ENOTEMPTY => Self::Notempty, + libc::ELOOP => Self::Loop, + libc::ENOMSG => Self::Nomsg, + libc::EIDRM => Self::Idrm, + libc::ENOLINK => Self::Nolink, + libc::EPROTO => Self::Proto, + libc::EMULTIHOP => Self::Multihop, + libc::EBADMSG => Self::Badmsg, + libc::EOVERFLOW => Self::Overflow, + libc::EILSEQ => Self::Ilseq, + libc::ENOTSOCK => Self::Notsock, + libc::EDESTADDRREQ => Self::Destaddrreq, + libc::EMSGSIZE => Self::Msgsize, + libc::EPROTOTYPE => Self::Prototype, + libc::ENOPROTOOPT => Self::Noprotoopt, + libc::EPROTONOSUPPORT => Self::Protonosupport, + libc::EAFNOSUPPORT => Self::Afnosupport, + libc::EADDRINUSE => Self::Addrinuse, + libc::EADDRNOTAVAIL => Self::Addrnotavail, + libc::ENETDOWN => Self::Netdown, + libc::ENETUNREACH => Self::Netunreach, + libc::ENETRESET => Self::Netreset, + libc::ECONNABORTED => Self::Connaborted, + libc::ECONNRESET => Self::Connreset, + libc::ENOBUFS => Self::Nobufs, + libc::EISCONN => Self::Isconn, + libc::ENOTCONN => Self::Notconn, + libc::ETIMEDOUT => Self::Timedout, + libc::ECONNREFUSED => Self::Connrefused, + libc::EHOSTUNREACH => Self::Hostunreach, + libc::EALREADY => Self::Already, + libc::EINPROGRESS => Self::Inprogress, + libc::ESTALE => Self::Stale, + libc::EDQUOT => Self::Dquot, + libc::ECANCELED => Self::Canceled, + libc::EOWNERDEAD => Self::Ownerdead, + libc::ENOTRECOVERABLE => Self::Notrecoverable, + libc::ENOTSUP => Self::Notsup, + x => { + log::debug!("Unknown errno value: {}", x); + Self::Io + } + }, + None => { + log::debug!("Other I/O error: {}", err); + Self::Io + } + } + } +} + +impl From for OFlag { + fn from(fdflags: types::Fdflags) -> Self { + let mut nix_flags = Self::empty(); + if fdflags.contains(&types::Fdflags::APPEND) { + nix_flags.insert(Self::APPEND); + } + if fdflags.contains(&types::Fdflags::DSYNC) { + nix_flags.insert(Self::DSYNC); + } + if fdflags.contains(&types::Fdflags::NONBLOCK) { + nix_flags.insert(Self::NONBLOCK); + } + if fdflags.contains(&types::Fdflags::RSYNC) { + nix_flags.insert(O_RSYNC); + } + if fdflags.contains(&types::Fdflags::SYNC) { + nix_flags.insert(Self::SYNC); + } + nix_flags + } +} + +impl From for types::Fdflags { + fn from(oflags: OFlag) -> Self { + let mut fdflags = Self::empty(); + if oflags.contains(OFlag::APPEND) { + fdflags |= Self::APPEND; + } + if oflags.contains(OFlag::DSYNC) { + fdflags |= Self::DSYNC; + } + if oflags.contains(OFlag::NONBLOCK) { + fdflags |= Self::NONBLOCK; + } + if oflags.contains(O_RSYNC) { + fdflags |= Self::RSYNC; + } + if oflags.contains(OFlag::SYNC) { + fdflags |= Self::SYNC; + } + fdflags + } +} + +impl From for OFlag { + fn from(oflags: types::Oflags) -> Self { + let mut nix_flags = Self::empty(); + if oflags.contains(&types::Oflags::CREAT) { + nix_flags.insert(Self::CREAT); + } + if oflags.contains(&types::Oflags::DIRECTORY) { + nix_flags.insert(Self::DIRECTORY); + } + if oflags.contains(&types::Oflags::EXCL) { + nix_flags.insert(Self::EXCL); + } + if oflags.contains(&types::Oflags::TRUNC) { + nix_flags.insert(Self::TRUNC); + } + nix_flags + } +} + +impl TryFrom for types::Filestat { + type Error = Errno; + + fn try_from(filestat: libc::stat) -> Result { + fn filestat_to_timestamp(secs: u64, nsecs: u64) -> Result { + secs.checked_mul(1_000_000_000) + .and_then(|sec_nsec| sec_nsec.checked_add(nsecs)) + .ok_or(Errno::Overflow) + } + + let filetype = yanix::file::FileType::from_stat_st_mode(filestat.st_mode); + let dev = filestat.st_dev.try_into()?; + let ino = filestat.st_ino.try_into()?; + let atim = filestat_to_timestamp( + filestat.st_atime.try_into()?, + filestat.st_atime_nsec.try_into()?, + )?; + let ctim = filestat_to_timestamp( + filestat.st_ctime.try_into()?, + filestat.st_ctime_nsec.try_into()?, + )?; + let mtim = filestat_to_timestamp( + filestat.st_mtime.try_into()?, + filestat.st_mtime_nsec.try_into()?, + )?; + + Ok(Self { + dev, + ino, + nlink: filestat.st_nlink.into(), + size: filestat.st_size as types::Filesize, + atim, + ctim, + mtim, + filetype: filetype.into(), + }) + } +} + +impl From for types::Filetype { + fn from(ft: yanix::file::FileType) -> Self { + use yanix::file::FileType::*; + match ft { + RegularFile => Self::RegularFile, + Symlink => Self::SymbolicLink, + Directory => Self::Directory, + BlockDevice => Self::BlockDevice, + CharacterDevice => Self::CharacterDevice, + /* Unknown | Socket | Fifo */ + _ => Self::Unknown, + // TODO how to discriminate between STREAM and DGRAM? + // Perhaps, we should create a more general WASI filetype + // such as __WASI_FILETYPE_SOCKET, and then it would be + // up to the client to check whether it's actually + // STREAM or DGRAM? + } + } +} + +impl From for AtFlag { + fn from(flags: types::Lookupflags) -> Self { + match flags { + types::Lookupflags::SYMLINK_FOLLOW => Self::empty(), + _ => Self::SYMLINK_NOFOLLOW, + } + } +} diff --git a/crates/wasi-common/src/sys/unix/path.rs b/crates/wasi-common/src/sys/unix/path.rs new file mode 100644 index 0000000000..1dd0858227 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/path.rs @@ -0,0 +1,298 @@ +use crate::entry::Descriptor; +use crate::path::PathGet; +use crate::sys::entry::OsHandle; +use crate::sys::unix::sys_impl; +use crate::wasi::{types, Errno, Result}; +use std::convert::TryInto; +use std::ffi::OsStr; +use std::fs::File; +use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt}; +use std::str; +use yanix::file::OFlag; + +pub(crate) use sys_impl::path::*; + +/// Creates owned WASI path from OS string. +/// +/// NB WASI spec requires OS string to be valid UTF-8. Otherwise, +/// `__WASI_ERRNO_ILSEQ` error is returned. +pub(crate) fn from_host>(s: S) -> Result { + let s = str::from_utf8(s.as_ref().as_bytes())?; + Ok(s.to_owned()) +} + +pub(crate) fn open_rights( + rights_base: types::Rights, + rights_inheriting: types::Rights, + oflags: types::Oflags, + fs_flags: types::Fdflags, +) -> (types::Rights, types::Rights) { + // which rights are needed on the dirfd? + let mut needed_base = types::Rights::PATH_OPEN; + let mut needed_inheriting = rights_base | rights_inheriting; + + // convert open flags + let oflags: OFlag = oflags.into(); + if oflags.contains(OFlag::CREAT) { + needed_base |= types::Rights::PATH_CREATE_FILE; + } + if oflags.contains(OFlag::TRUNC) { + needed_base |= types::Rights::PATH_FILESTAT_SET_SIZE; + } + + // convert file descriptor flags + let fdflags: OFlag = fs_flags.into(); + if fdflags.contains(OFlag::DSYNC) { + needed_inheriting |= types::Rights::FD_DATASYNC; + } + if fdflags.intersects(sys_impl::O_RSYNC | OFlag::SYNC) { + needed_inheriting |= types::Rights::FD_SYNC; + } + + (needed_base, needed_inheriting) +} + +pub(crate) fn openat(dirfd: &File, path: &str) -> Result { + use std::os::unix::prelude::{AsRawFd, FromRawFd}; + use yanix::file::{openat, Mode}; + + log::debug!("path_get openat path = {:?}", path); + + let raw_fd = unsafe { + openat( + dirfd.as_raw_fd(), + path, + OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW, + Mode::empty(), + )? + }; + let file = unsafe { File::from_raw_fd(raw_fd) }; + Ok(file) +} + +pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result { + use std::os::unix::prelude::AsRawFd; + use yanix::file::readlinkat; + + log::debug!("path_get readlinkat path = {:?}", path); + + let path = unsafe { readlinkat(dirfd.as_raw_fd(), path)? }; + let path = from_host(path)?; + Ok(path) +} + +pub(crate) fn create_directory(base: &File, path: &str) -> Result<()> { + use yanix::file::{mkdirat, Mode}; + unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? }; + Ok(()) +} + +pub(crate) fn link( + resolved_old: PathGet, + resolved_new: PathGet, + follow_symlinks: bool, +) -> Result<()> { + use yanix::file::{linkat, AtFlag}; + let flags = if follow_symlinks { + AtFlag::SYMLINK_FOLLOW + } else { + AtFlag::empty() + }; + unsafe { + linkat( + resolved_old.dirfd().as_raw_fd(), + resolved_old.path(), + resolved_new.dirfd().as_raw_fd(), + resolved_new.path(), + flags, + )? + }; + Ok(()) +} + +pub(crate) fn open( + resolved: PathGet, + read: bool, + write: bool, + oflags: types::Oflags, + fs_flags: types::Fdflags, +) -> Result { + use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag}; + + let mut nix_all_oflags = if read && write { + OFlag::RDWR + } else if write { + OFlag::WRONLY + } else { + OFlag::RDONLY + }; + + // on non-Capsicum systems, we always want nofollow + nix_all_oflags.insert(OFlag::NOFOLLOW); + + // convert open flags + nix_all_oflags.insert(oflags.into()); + + // convert file descriptor flags + nix_all_oflags.insert(fs_flags.into()); + + // Call openat. Use mode 0o666 so that we follow whatever the user's + // umask is, but don't set the executable flag, because it isn't yet + // meaningful for WASI programs to create executable files. + + log::debug!("path_open resolved = {:?}", resolved); + log::debug!("path_open oflags = {:?}", nix_all_oflags); + + let fd_no = unsafe { + openat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + nix_all_oflags, + Mode::from_bits_truncate(0o666), + ) + }; + let new_fd = match fd_no { + Ok(fd) => fd, + Err(e) => { + match e.raw_os_error().unwrap() { + // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket + libc::ENXIO => { + match unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + Ok(stat) => { + if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket { + return Err(Errno::Notsup); + } + } + Err(err) => { + log::debug!("path_open fstatat error: {:?}", err); + } + } + } + // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY + // on a symlink. + libc::ENOTDIR + if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() => + { + match unsafe { + fstatat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::SYMLINK_NOFOLLOW, + ) + } { + Ok(stat) => { + if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink { + return Err(Errno::Loop); + } + } + Err(err) => { + log::debug!("path_open fstatat error: {:?}", err); + } + } + } + // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on + // a symlink. + libc::EMLINK if !(nix_all_oflags & OFlag::NOFOLLOW).is_empty() => { + return Err(Errno::Loop); + } + _ => {} + } + + return Err(e.into()); + } + }; + + log::debug!("path_open (host) new_fd = {:?}", new_fd); + + // Determine the type of the new file descriptor and which rights contradict with this type + Ok(OsHandle::from(unsafe { File::from_raw_fd(new_fd) }).into()) +} + +pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result { + use std::cmp::min; + use yanix::file::readlinkat; + let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path())? }; + let read_link = from_host(read_link)?; + let copy_len = min(read_link.len(), buf.len()); + if copy_len > 0 { + buf[..copy_len].copy_from_slice(&read_link.as_bytes()[..copy_len]); + } + Ok(copy_len) +} + +pub(crate) fn filestat_get( + resolved: PathGet, + dirflags: types::Lookupflags, +) -> Result { + use yanix::file::fstatat; + let atflags = dirflags.into(); + let filestat = unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags)? }; + let filestat = filestat.try_into()?; + Ok(filestat) +} + +pub(crate) fn filestat_set_times( + resolved: PathGet, + dirflags: types::Lookupflags, + st_atim: types::Timestamp, + st_mtim: types::Timestamp, + fst_flags: types::Fstflags, +) -> Result<()> { + use std::time::{Duration, UNIX_EPOCH}; + use yanix::filetime::*; + + let set_atim = fst_flags.contains(&types::Fstflags::ATIM); + let set_atim_now = fst_flags.contains(&types::Fstflags::ATIM_NOW); + let set_mtim = fst_flags.contains(&types::Fstflags::MTIM); + let set_mtim_now = fst_flags.contains(&types::Fstflags::MTIM_NOW); + + if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) { + return Err(Errno::Inval); + } + + let symlink_nofollow = types::Lookupflags::SYMLINK_FOLLOW != dirflags; + let atim = if set_atim { + let time = UNIX_EPOCH + Duration::from_nanos(st_atim); + FileTime::FileTime(filetime::FileTime::from_system_time(time)) + } else if set_atim_now { + FileTime::Now + } else { + FileTime::Omit + }; + let mtim = if set_mtim { + let time = UNIX_EPOCH + Duration::from_nanos(st_mtim); + FileTime::FileTime(filetime::FileTime::from_system_time(time)) + } else if set_mtim_now { + FileTime::Now + } else { + FileTime::Omit + }; + + utimensat( + &resolved.dirfd().as_os_handle(), + resolved.path(), + atim, + mtim, + symlink_nofollow, + )?; + Ok(()) +} + +pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> { + use yanix::file::{unlinkat, AtFlag}; + + unsafe { + unlinkat( + resolved.dirfd().as_raw_fd(), + resolved.path(), + AtFlag::REMOVEDIR, + )? + }; + Ok(()) +} diff --git a/crates/wasi-common/src/sys/unix/poll.rs b/crates/wasi-common/src/sys/unix/poll.rs new file mode 100644 index 0000000000..93cac39a68 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/poll.rs @@ -0,0 +1,159 @@ +use crate::poll::{ClockEventData, FdEventData}; +use crate::wasi::{types, Errno, Result}; +use std::io; + +pub(crate) fn oneoff( + timeout: Option, + fd_events: Vec, + events: &mut Vec, +) -> Result<()> { + use std::{convert::TryInto, os::unix::prelude::AsRawFd}; + use yanix::poll::{poll, PollFd, PollFlags}; + + if fd_events.is_empty() && timeout.is_none() { + return Ok(()); + } + + let mut poll_fds: Vec<_> = fd_events + .iter() + .map(|event| { + let mut flags = PollFlags::empty(); + match event.r#type { + types::Eventtype::FdRead => flags.insert(PollFlags::POLLIN), + types::Eventtype::FdWrite => flags.insert(PollFlags::POLLOUT), + // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE + // Nothing else has been defined in the specification, and these are also the only two + // events we filtered before. If we get something else here, the code has a serious bug. + _ => unreachable!(), + }; + unsafe { PollFd::new(event.descriptor.as_raw_fd(), flags) } + }) + .collect(); + + let poll_timeout = timeout.map_or(-1, |timeout| { + let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds + delay.try_into().unwrap_or(libc::c_int::max_value()) + }); + log::debug!("poll_oneoff poll_timeout = {:?}", poll_timeout); + + let ready = loop { + match poll(&mut poll_fds, poll_timeout) { + Err(_) => { + let last_err = io::Error::last_os_error(); + if last_err.raw_os_error().unwrap() == libc::EINTR { + continue; + } + return Err(last_err.into()); + } + Ok(ready) => break ready, + } + }; + + Ok(if ready == 0 { + handle_timeout_event(timeout.expect("timeout should not be None"), events) + } else { + let ready_events = fd_events.into_iter().zip(poll_fds.into_iter()).take(ready); + handle_fd_event(ready_events, events)? + }) +} + +fn handle_timeout_event(timeout: ClockEventData, events: &mut Vec) { + events.push(types::Event { + userdata: timeout.userdata, + error: Errno::Success, + type_: types::Eventtype::Clock, + fd_readwrite: types::EventFdReadwrite { + flags: types::Eventrwflags::empty(), + nbytes: 0, + }, + }); +} + +fn handle_fd_event<'a>( + ready_events: impl Iterator, yanix::poll::PollFd)>, + events: &mut Vec, +) -> Result<()> { + use crate::entry::Descriptor; + use std::{convert::TryInto, os::unix::prelude::AsRawFd}; + use yanix::{file::fionread, poll::PollFlags}; + + fn query_nbytes(fd: &Descriptor) -> Result { + // fionread may overflow for large files, so use another way for regular files. + if let Descriptor::OsHandle(os_handle) = fd { + let meta = os_handle.metadata()?; + if meta.file_type().is_file() { + use yanix::file::tell; + let len = meta.len(); + let host_offset = unsafe { tell(os_handle.as_raw_fd())? }; + return Ok(len - host_offset); + } + } + unsafe { Ok(fionread(fd.as_raw_fd())?.into()) } + } + + for (fd_event, poll_fd) in ready_events { + log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event); + log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd); + + let revents = match poll_fd.revents() { + Some(revents) => revents, + None => continue, + }; + + log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents); + + let nbytes = if fd_event.r#type == types::Eventtype::FdRead { + query_nbytes(&fd_event.descriptor)? + } else { + 0 + }; + + let output_event = if revents.contains(PollFlags::POLLNVAL) { + types::Event { + userdata: fd_event.userdata, + error: Errno::Badf, + type_: fd_event.r#type, + fd_readwrite: types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::FD_READWRITE_HANGUP, + }, + } + } else if revents.contains(PollFlags::POLLERR) { + types::Event { + userdata: fd_event.userdata, + error: Errno::Io, + type_: fd_event.r#type, + fd_readwrite: types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::FD_READWRITE_HANGUP, + }, + } + } else if revents.contains(PollFlags::POLLHUP) { + types::Event { + userdata: fd_event.userdata, + error: Errno::Success, + type_: fd_event.r#type, + fd_readwrite: types::EventFdReadwrite { + nbytes: 0, + flags: types::Eventrwflags::FD_READWRITE_HANGUP, + }, + } + } else if revents.contains(PollFlags::POLLIN) | revents.contains(PollFlags::POLLOUT) { + types::Event { + userdata: fd_event.userdata, + error: Errno::Success, + type_: fd_event.r#type, + fd_readwrite: types::EventFdReadwrite { + nbytes: nbytes.try_into()?, + flags: types::Eventrwflags::empty(), + }, + } + } else { + continue; + }; + + events.push(output_event); + } + + Ok(()) +} diff --git a/crates/wasi-common/src/sys/windows/clock.rs b/crates/wasi-common/src/sys/windows/clock.rs new file mode 100644 index 0000000000..e72820ad6d --- /dev/null +++ b/crates/wasi-common/src/sys/windows/clock.rs @@ -0,0 +1,104 @@ +use crate::wasi::{types, Errno, Result}; +use cpu_time::{ProcessTime, ThreadTime}; +use lazy_static::lazy_static; +use std::convert::TryInto; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +lazy_static! { + static ref START_MONOTONIC: Instant = Instant::now(); + static ref PERF_COUNTER_RES: u64 = get_perf_counter_resolution_ns(); +} + +// Timer resolution on Windows is really hard. We may consider exposing the resolution of the respective +// timers as an associated function in the future. +pub(crate) fn res_get(clock_id: types::Clockid) -> Result { + let ts = match clock_id { + // This is the best that we can do with std::time::SystemTime. + // Rust uses GetSystemTimeAsFileTime, which is said to have the resolution of + // 10ms or 55ms, [1] but MSDN doesn't confirm this in any way. + // Even the MSDN article on high resolution timestamps doesn't even mention the precision + // for this method. [3] + // + // The timer resolution can be queried using one of the functions: [2, 5] + // * NtQueryTimerResolution, which is undocumented and thus not exposed by the winapi crate + // * timeGetDevCaps, which returns the upper and lower bound for the precision, in ms. + // While the upper bound seems like something we could use, it's typically too high to be meaningful. + // For instance, the intervals return by the syscall are: + // * [1, 65536] on Wine + // * [1, 1000000] on Windows 10, which is up to (sic) 1000 seconds. + // + // It's possible to manually set the timer resolution, but this sounds like something which should + // only be done temporarily. [5] + // + // Alternatively, we could possibly use GetSystemTimePreciseAsFileTime in clock_time_get, but + // this syscall is only available starting from Windows 8. + // (we could possibly emulate it on earlier versions of Windows, see [4]) + // The MSDN are not clear on the resolution of GetSystemTimePreciseAsFileTime either, but a + // Microsoft devblog entry [1] suggests that it kind of combines GetSystemTimeAsFileTime with + // QueryPeformanceCounter, which probably means that those two should have the same resolution. + // + // See also this discussion about the use of GetSystemTimePreciseAsFileTime in Python stdlib, + // which in particular contains some resolution benchmarks. + // + // [1] https://devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057 + // [2] http://www.windowstimestamp.com/description + // [3] https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps?redirectedfrom=MSDN + // [4] https://www.codeproject.com/Tips/1011902/High-Resolution-Time-For-Windows + // [5] https://stackoverflow.com/questions/7685762/windows-7-timing-functions-how-to-use-getsystemtimeadjustment-correctly + // [6] https://bugs.python.org/issue19007 + types::Clockid::Realtime => 55_000_000, + // std::time::Instant uses QueryPerformanceCounter & QueryPerformanceFrequency internally + types::Clockid::Monotonic => *PERF_COUNTER_RES, + // The best we can do is to hardcode the value from the docs. + // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes + types::Clockid::ProcessCputimeId => 100, + // The best we can do is to hardcode the value from the docs. + // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes + types::Clockid::ThreadCputimeId => 100, + }; + Ok(ts) +} + +pub(crate) fn time_get(clock_id: types::Clockid) -> Result { + let duration = match clock_id { + types::Clockid::Realtime => get_monotonic_time(), + types::Clockid::Monotonic => get_realtime_time()?, + types::Clockid::ProcessCputimeId => get_proc_cputime()?, + types::Clockid::ThreadCputimeId => get_thread_cputime()?, + }; + let duration = duration.as_nanos().try_into()?; + Ok(duration) +} + +fn get_monotonic_time() -> Duration { + // We're circumventing the fact that we can't get a Duration from an Instant + // The epoch of __WASI_CLOCKID_MONOTONIC is undefined, so we fix a time point once + // and count relative to this time point. + // + // The alternative would be to copy over the implementation of std::time::Instant + // to our source tree and add a conversion to std::time::Duration + START_MONOTONIC.elapsed() +} + +fn get_realtime_time() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| Errno::Fault) +} + +fn get_proc_cputime() -> Result { + Ok(ProcessTime::try_now()?.as_duration()) +} + +fn get_thread_cputime() -> Result { + Ok(ThreadTime::try_now()?.as_duration()) +} + +fn get_perf_counter_resolution_ns() -> u64 { + use winx::time::perf_counter_frequency; + const NANOS_PER_SEC: u64 = 1_000_000_000; + // This should always succeed starting from Windows XP, so it's fine to panic in case of an error. + let freq = perf_counter_frequency().expect("QueryPerformanceFrequency returned an error"); + let epsilon = NANOS_PER_SEC / freq; + epsilon +} diff --git a/crates/wasi-common/src/sys/windows/entry_impl.rs b/crates/wasi-common/src/sys/windows/entry.rs similarity index 78% rename from crates/wasi-common/src/sys/windows/entry_impl.rs rename to crates/wasi-common/src/sys/windows/entry.rs index 182475fc38..d2d0c04497 100644 --- a/crates/wasi-common/src/sys/windows/entry_impl.rs +++ b/crates/wasi-common/src/sys/windows/entry.rs @@ -1,5 +1,5 @@ use crate::entry::{Descriptor, OsHandleRef}; -use crate::wasi; +use crate::wasi::{types, RightsExt}; use std::fs::File; use std::io; use std::mem::ManuallyDrop; @@ -63,23 +63,19 @@ pub(crate) fn descriptor_as_oshandle<'lifetime>( /// This function is unsafe because it operates on a raw file descriptor. pub(crate) unsafe fn determine_type_and_access_rights( handle: &Handle, -) -> io::Result<( - wasi::__wasi_filetype_t, - wasi::__wasi_rights_t, - wasi::__wasi_rights_t, -)> { +) -> io::Result<(types::Filetype, types::Rights, types::Rights)> { use winx::file::{query_access_information, AccessMode}; let (file_type, mut rights_base, rights_inheriting) = determine_type_rights(handle)?; match file_type { - wasi::__WASI_FILETYPE_DIRECTORY | wasi::__WASI_FILETYPE_REGULAR_FILE => { + types::Filetype::Directory | types::Filetype::RegularFile => { let mode = query_access_information(handle.as_raw_handle())?; if mode.contains(AccessMode::FILE_GENERIC_READ) { - rights_base |= wasi::__WASI_RIGHTS_FD_READ; + rights_base |= types::Rights::FD_READ; } if mode.contains(AccessMode::FILE_GENERIC_WRITE) { - rights_base |= wasi::__WASI_RIGHTS_FD_WRITE; + rights_base |= types::Rights::FD_WRITE; } } _ => { @@ -96,20 +92,16 @@ pub(crate) unsafe fn determine_type_and_access_rights( /// This function is unsafe because it operates on a raw file descriptor. pub(crate) unsafe fn determine_type_rights( handle: &Handle, -) -> io::Result<( - wasi::__wasi_filetype_t, - wasi::__wasi_rights_t, - wasi::__wasi_rights_t, -)> { +) -> io::Result<(types::Filetype, types::Rights, types::Rights)> { let (file_type, rights_base, rights_inheriting) = { let file_type = winx::file::get_file_type(handle.as_raw_handle())?; if file_type.is_char() { // character file: LPT device or console // TODO: rule out LPT device ( - wasi::__WASI_FILETYPE_CHARACTER_DEVICE, - wasi::RIGHTS_TTY_BASE, - wasi::RIGHTS_TTY_BASE, + types::Filetype::CharacterDevice, + types::Rights::tty_base(), + types::Rights::tty_base(), ) } else if file_type.is_disk() { // disk file: file, dir or disk device @@ -117,15 +109,15 @@ pub(crate) unsafe fn determine_type_rights( let meta = file.metadata()?; if meta.is_dir() { ( - wasi::__WASI_FILETYPE_DIRECTORY, - wasi::RIGHTS_DIRECTORY_BASE, - wasi::RIGHTS_DIRECTORY_INHERITING, + types::Filetype::Directory, + types::Rights::directory_base(), + types::Rights::directory_inheriting(), ) } else if meta.is_file() { ( - wasi::__WASI_FILETYPE_REGULAR_FILE, - wasi::RIGHTS_REGULAR_FILE_BASE, - wasi::RIGHTS_REGULAR_FILE_INHERITING, + types::Filetype::RegularFile, + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), ) } else { return Err(io::Error::from_raw_os_error(libc::EINVAL)); @@ -134,9 +126,9 @@ pub(crate) unsafe fn determine_type_rights( // pipe object: socket, named pipe or anonymous pipe // TODO: what about pipes, etc? ( - wasi::__WASI_FILETYPE_SOCKET_STREAM, - wasi::RIGHTS_SOCKET_BASE, - wasi::RIGHTS_SOCKET_INHERITING, + types::Filetype::SocketStream, + types::Rights::socket_base(), + types::Rights::socket_inheriting(), ) } else { return Err(io::Error::from_raw_os_error(libc::EINVAL)); diff --git a/crates/wasi-common/src/sys/windows/fd.rs b/crates/wasi-common/src/sys/windows/fd.rs new file mode 100644 index 0000000000..d9be83b9c3 --- /dev/null +++ b/crates/wasi-common/src/sys/windows/fd.rs @@ -0,0 +1,188 @@ +use super::file_serial_no; +use crate::path; +use crate::sys::entry::OsHandle; +use crate::wasi::{types, Result}; +use log::trace; +use std::convert::TryInto; +use std::fs::{File, OpenOptions}; +use std::os::windows::fs::OpenOptionsExt; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; +use std::path::Path; +use winx::file::{AccessMode, FileModeInformation, Flags}; + +pub(crate) fn fdstat_get(fd: &File) -> Result { + let mut fdflags = types::Fdflags::empty(); + let handle = 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 |= types::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 |= types::Fdflags::SYNC; + } + + // Files do not support the `__WASI_FDFLAGS_NONBLOCK` flag + + Ok(fdflags) +} + +pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result> { + let handle = fd.as_raw_handle(); + + let access_mode = winx::file::query_access_information(handle)?; + + let new_access_mode = file_access_mode_from_fdflags( + fdflags, + access_mode.contains(AccessMode::FILE_READ_DATA), + access_mode.contains(AccessMode::FILE_WRITE_DATA) + | access_mode.contains(AccessMode::FILE_APPEND_DATA), + ); + + unsafe { + Ok(Some(OsHandle::from(File::from_raw_handle( + winx::file::reopen_file(handle, new_access_mode, fdflags.into())?, + )))) + } +} + +pub(crate) fn advise( + _file: &File, + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, +) -> Result<()> { + Ok(()) +} + +fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode { + let mut access_mode = AccessMode::READ_CONTROL; + + // Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode + // The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead + // These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below) + if read { + access_mode.insert(AccessMode::FILE_GENERIC_READ); + } + + if write { + access_mode.insert(AccessMode::FILE_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.contains(&types::Fdflags::APPEND) { + access_mode.insert(AccessMode::FILE_APPEND_DATA); + access_mode.remove(AccessMode::FILE_WRITE_DATA); + } + + access_mode +} + +// 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 readdir( + fd: &File, + cookie: types::Dircookie, +) -> 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?; + let ftype = dir.file_type()?; + let name = path::from_host(dir.file_name())?; + let d_ino = File::open(dir.path()).and_then(|f| file_serial_no(&f))?; + let dirent = types::Dirent { + d_namlen: name.len().try_into()?, + d_type: ftype.into(), + d_ino, + d_next: no, + }; + + Ok((dirent, name)) + }); + + // 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)) +} + +fn dirent_from_path>( + path: P, + name: &str, + cookie: types::Dircookie, +) -> Result<(types::Dirent, String)> { + 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(); + let name = name.to_owned(); + let dirent = types::Dirent { + d_namlen: name.len().try_into()?, + d_next: cookie, + d_type: ty.into(), + d_ino: file_serial_no(&file)?, + }; + Ok((dirent, name)) +} + +pub(crate) fn filestat_get(file: &std::fs::File) -> Result { + let filestat = file.try_into()?; + Ok(filestat) +} diff --git a/crates/wasi-common/src/sys/windows/host_impl.rs b/crates/wasi-common/src/sys/windows/host_impl.rs deleted file mode 100644 index cadb7205bf..0000000000 --- a/crates/wasi-common/src/sys/windows/host_impl.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! WASI host types specific to Windows host. -use crate::host::FileType; -use crate::wasi::{self, WasiError, WasiResult}; -use std::convert::TryInto; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::io; -use std::os::windows::ffi::OsStrExt; -use std::time::{SystemTime, UNIX_EPOCH}; -use winapi::shared::winerror; - -impl From for WasiError { - fn from(err: io::Error) -> Self { - match err.raw_os_error() { - Some(code) => match code as u32 { - winerror::ERROR_SUCCESS => Self::ESUCCESS, - winerror::ERROR_BAD_ENVIRONMENT => Self::E2BIG, - winerror::ERROR_FILE_NOT_FOUND => Self::ENOENT, - winerror::ERROR_PATH_NOT_FOUND => Self::ENOENT, - winerror::ERROR_TOO_MANY_OPEN_FILES => Self::ENFILE, - winerror::ERROR_ACCESS_DENIED => Self::EACCES, - winerror::ERROR_SHARING_VIOLATION => Self::EACCES, - winerror::ERROR_PRIVILEGE_NOT_HELD => Self::ENOTCAPABLE, - winerror::ERROR_INVALID_HANDLE => Self::EBADF, - winerror::ERROR_INVALID_NAME => Self::ENOENT, - winerror::ERROR_NOT_ENOUGH_MEMORY => Self::ENOMEM, - winerror::ERROR_OUTOFMEMORY => Self::ENOMEM, - winerror::ERROR_DIR_NOT_EMPTY => Self::ENOTEMPTY, - winerror::ERROR_NOT_READY => Self::EBUSY, - winerror::ERROR_BUSY => Self::EBUSY, - winerror::ERROR_NOT_SUPPORTED => Self::ENOTSUP, - winerror::ERROR_FILE_EXISTS => Self::EEXIST, - winerror::ERROR_BROKEN_PIPE => Self::EPIPE, - winerror::ERROR_BUFFER_OVERFLOW => Self::ENAMETOOLONG, - winerror::ERROR_NOT_A_REPARSE_POINT => Self::EINVAL, - winerror::ERROR_NEGATIVE_SEEK => Self::EINVAL, - winerror::ERROR_DIRECTORY => Self::ENOTDIR, - winerror::ERROR_ALREADY_EXISTS => Self::EEXIST, - x => { - log::debug!("unknown error value: {}", x); - Self::EIO - } - }, - None => { - log::debug!("Other I/O error: {}", err); - Self::EIO - } - } - } -} - -pub(crate) fn filetype_from_std(ftype: &fs::FileType) -> FileType { - if ftype.is_file() { - FileType::RegularFile - } else if ftype.is_dir() { - FileType::Directory - } else if ftype.is_symlink() { - FileType::Symlink - } else { - FileType::Unknown - } -} - -fn num_hardlinks(file: &File) -> io::Result { - Ok(winx::file::get_fileinfo(file)?.nNumberOfLinks.into()) -} - -fn device_id(file: &File) -> io::Result { - Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into()) -} - -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; - let no = (u64::from(high) << 32) | u64::from(low); - Ok(no) -} - -fn change_time(file: &File) -> io::Result { - winx::file::change_time(file) -} - -fn systemtime_to_timestamp(st: SystemTime) -> WasiResult { - st.duration_since(UNIX_EPOCH) - .map_err(|_| WasiError::EINVAL)? // date earlier than UNIX_EPOCH - .as_nanos() - .try_into() - .map_err(Into::into) // u128 doesn't fit into u64 -} - -pub(crate) fn filestat_from_win(file: &File) -> WasiResult { - let metadata = file.metadata()?; - Ok(wasi::__wasi_filestat_t { - dev: device_id(file)?, - ino: file_serial_no(file)?, - nlink: num_hardlinks(file)?.try_into()?, // u64 doesn't fit into u32 - size: metadata.len(), - atim: systemtime_to_timestamp(metadata.accessed()?)?, - ctim: change_time(file)?.try_into()?, // i64 doesn't fit into u64 - mtim: systemtime_to_timestamp(metadata.modified()?)?, - filetype: filetype_from_std(&metadata.file_type()).to_wasi(), - }) -} - -/// Creates owned WASI path from OS string. -/// -/// NB WASI spec requires OS string to be valid UTF-8. Otherwise, -/// `__WASI_ERRNO_ILSEQ` error is returned. -pub(crate) fn path_from_host>(s: S) -> WasiResult { - let vec: Vec = s.as_ref().encode_wide().collect(); - String::from_utf16(&vec).map_err(|_| WasiError::EILSEQ) -} diff --git a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs b/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs deleted file mode 100644 index 929ea1a177..0000000000 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs +++ /dev/null @@ -1,599 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(unused)] -use super::fs_helpers::*; -use crate::ctx::WasiCtx; -use crate::entry::{Descriptor, Entry}; -use crate::host::{Dirent, FileType}; -use crate::hostcalls_impl::{fd_filestat_set_times_impl, PathGet}; -use crate::sys::entry_impl::{determine_type_rights, OsHandle}; -use crate::sys::host_impl::{self, path_from_host}; -use crate::sys::hostcalls_impl::fs_helpers::PathGetExt; -use crate::wasi::{self, WasiError, WasiResult}; -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 winapi::shared::winerror; -use winx::file::{AccessMode, CreationDisposition, FileModeInformation, Flags}; - -fn read_at(mut file: &File, buf: &mut [u8], offset: u64) -> io::Result { - // get current cursor position - let cur_pos = file.seek(SeekFrom::Current(0))?; - // perform a seek read by a specified offset - let nread = file.seek_read(buf, offset)?; - // rewind the cursor back to the original position - file.seek(SeekFrom::Start(cur_pos))?; - Ok(nread) -} - -fn write_at(mut file: &File, buf: &[u8], offset: u64) -> io::Result { - // get current cursor position - let cur_pos = file.seek(SeekFrom::Current(0))?; - // perform a seek write by a specified offset - let nwritten = file.seek_write(buf, offset)?; - // rewind the cursor back to the original position - file.seek(SeekFrom::Start(cur_pos))?; - Ok(nwritten) -} - -// TODO refactor common code with unix -pub(crate) fn fd_pread( - file: &File, - buf: &mut [u8], - offset: wasi::__wasi_filesize_t, -) -> WasiResult { - read_at(file, buf, offset).map_err(Into::into) -} - -// TODO refactor common code with unix -pub(crate) fn fd_pwrite( - file: &File, - buf: &[u8], - offset: wasi::__wasi_filesize_t, -) -> WasiResult { - write_at(file, buf, offset).map_err(Into::into) -} - -pub(crate) fn fd_fdstat_get(fd: &File) -> WasiResult { - 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, -) -> WasiResult> { - let handle = unsafe { fd.as_raw_handle() }; - - let access_mode = winx::file::query_access_information(handle)?; - - let new_access_mode = file_access_mode_from_fdflags( - fdflags, - access_mode.contains(AccessMode::FILE_READ_DATA), - access_mode.contains(AccessMode::FILE_WRITE_DATA) - | access_mode.contains(AccessMode::FILE_APPEND_DATA), - ); - - unsafe { - Ok(Some(OsHandle::from(File::from_raw_handle( - winx::file::reopen_file(handle, new_access_mode, file_flags_from_fdflags(fdflags))?, - )))) - } -} - -pub(crate) fn fd_advise( - _file: &File, - advice: wasi::__wasi_advice_t, - _offset: wasi::__wasi_filesize_t, - _len: wasi::__wasi_filesize_t, -) -> WasiResult<()> { - match advice { - wasi::__WASI_ADVICE_DONTNEED - | wasi::__WASI_ADVICE_SEQUENTIAL - | wasi::__WASI_ADVICE_WILLNEED - | wasi::__WASI_ADVICE_NOREUSE - | wasi::__WASI_ADVICE_RANDOM - | wasi::__WASI_ADVICE_NORMAL => {} - _ => return Err(WasiError::EINVAL), - } - - Ok(()) -} - -pub(crate) fn path_create_directory(file: &File, path: &str) -> WasiResult<()> { - let path = concatenate(file, path)?; - std::fs::create_dir(&path).map_err(Into::into) -} - -pub(crate) fn path_link( - resolved_old: PathGet, - resolved_new: PathGet, - follow_symlinks: bool, -) -> WasiResult<()> { - unimplemented!("path_link") -} - -pub(crate) fn path_open( - resolved: PathGet, - read: bool, - write: bool, - oflags: wasi::__wasi_oflags_t, - fdflags: wasi::__wasi_fdflags_t, -) -> WasiResult { - use winx::file::{AccessMode, CreationDisposition, Flags}; - - let is_trunc = oflags & wasi::__WASI_OFLAGS_TRUNC != 0; - - if is_trunc { - // Windows does not support append mode when opening for truncation - // This is because truncation requires `GENERIC_WRITE` access, which will override the removal - // of the `FILE_WRITE_DATA` permission. - if fdflags & wasi::__WASI_FDFLAGS_APPEND != 0 { - return Err(WasiError::ENOTSUP); - } - } - - // 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 calculated below. - let mut opts = OpenOptions::new(); - match creation_disposition_from_oflags(oflags) { - CreationDisposition::CREATE_ALWAYS => { - opts.create(true).write(true); - } - CreationDisposition::CREATE_NEW => { - opts.create_new(true).write(true); - } - CreationDisposition::TRUNCATE_EXISTING => { - opts.truncate(true).write(true); - } - _ => {} - } - - let path = resolved.concatenate()?; - - match path.symlink_metadata().map(|metadata| metadata.file_type()) { - Ok(file_type) => { - // check if we are trying to open a symlink - if file_type.is_symlink() { - return Err(WasiError::ELOOP); - } - // check if we are trying to open a file as a dir - if file_type.is_file() && oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 { - return Err(WasiError::ENOTDIR); - } - } - Err(err) => match err.raw_os_error() { - Some(code) => { - log::debug!("path_open at symlink_metadata error code={:?}", code); - - if code as u32 != winerror::ERROR_FILE_NOT_FOUND { - return Err(err.into()); - } - // file not found, let it proceed to actually - // trying to open it - } - None => { - log::debug!("Inconvertible OS error: {}", err); - return Err(WasiError::EIO); - } - }, - } - - let mut access_mode = file_access_mode_from_fdflags(fdflags, read, write); - - // Truncation requires the special `GENERIC_WRITE` bit set (this is why it doesn't work with append-only mode) - if is_trunc { - access_mode |= AccessMode::GENERIC_WRITE; - } - - opts.access_mode(access_mode.bits()) - .custom_flags(file_flags_from_fdflags(fdflags).bits()) - .open(&path) - .map(|f| OsHandle::from(f).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; - - // Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode - // The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead - // These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below) - if read { - access_mode.insert(AccessMode::FILE_GENERIC_READ); - } - - if write { - access_mode.insert(AccessMode::FILE_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, - cookie: wasi::__wasi_dircookie_t, -) -> WasiResult { - 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: host_impl::filetype_from_std(&ty), - name: name.to_owned(), - cookie, - ino: host_impl::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( - fd: &File, - cookie: wasi::__wasi_dircookie_t, -) -> WasiResult>> { - 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: host_impl::filetype_from_std(&dir.file_type()?), - ino: File::open(dir.path()).and_then(|f| host_impl::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)) -} - -pub(crate) fn path_readlink(resolved: PathGet, buf: &mut [u8]) -> WasiResult { - use winx::file::get_file_path; - - let path = resolved.concatenate()?; - let target_path = path.read_link()?; - - // since on Windows we are effectively emulating 'at' syscalls - // we need to strip the prefix from the absolute path - // as otherwise we will error out since WASI is not capable - // of dealing with absolute paths - let dir_path = get_file_path(&resolved.dirfd().as_os_handle())?; - let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); - let target_path = target_path - .strip_prefix(dir_path) - .map_err(|_| WasiError::ENOTCAPABLE) - .and_then(|path| path.to_str().map(String::from).ok_or(WasiError::EILSEQ))?; - - if buf.len() > 0 { - let mut chars = target_path.chars(); - let mut nread = 0usize; - - for i in 0..buf.len() { - match chars.next() { - Some(ch) => { - buf[i] = ch as u8; - nread += 1; - } - None => break, - } - } - - Ok(nread) - } else { - Ok(0) - } -} - -fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> WasiResult> { - if resolved.path().ends_with('/') { - let suffix = resolved.path().trim_end_matches('/'); - concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some) - } else { - Ok(None) - } -} - -pub(crate) fn path_rename(resolved_old: PathGet, resolved_new: PathGet) -> WasiResult<()> { - use std::fs; - - let old_path = resolved_old.concatenate()?; - let new_path = resolved_new.concatenate()?; - - // First sanity check: check we're not trying to rename dir to file or vice versa. - // NB on Windows, the former is actually permitted [std::fs::rename]. - // - // [std::fs::rename]: https://doc.rust-lang.org/std/fs/fn.rename.html - if old_path.is_dir() && new_path.is_file() { - return Err(WasiError::ENOTDIR); - } - // Second sanity check: check we're not trying to rename a file into a path - // ending in a trailing slash. - if old_path.is_file() && resolved_new.path().ends_with('/') { - return Err(WasiError::ENOTDIR); - } - - // TODO handle symlinks - let err = match fs::rename(&old_path, &new_path) { - Ok(()) => return Ok(()), - Err(e) => e, - }; - match err.raw_os_error() { - Some(code) => { - log::debug!("path_rename at rename error code={:?}", code); - match code as u32 { - winerror::ERROR_ACCESS_DENIED => { - // So most likely dealing with new_path == dir. - // Eliminate case old_path == file first. - if old_path.is_file() { - return Err(WasiError::EISDIR); - } else { - // Ok, let's try removing an empty dir at new_path if it exists - // and is a nonempty dir. - fs::remove_dir(&new_path)?; - fs::rename(old_path, new_path)?; - return Ok(()); - } - } - winerror::ERROR_INVALID_NAME => { - // If source contains trailing slashes, check if we are dealing with - // a file instead of a dir, and if so, throw ENOTDIR. - if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? { - if path.is_file() { - return Err(WasiError::ENOTDIR); - } - } - } - _ => {} - } - - Err(err.into()) - } - None => { - log::debug!("Inconvertible OS error: {}", err); - Err(WasiError::EIO) - } - } -} - -pub(crate) fn fd_filestat_get(file: &std::fs::File) -> WasiResult { - host_impl::filestat_from_win(file) -} - -pub(crate) fn path_filestat_get( - resolved: PathGet, - dirflags: wasi::__wasi_lookupflags_t, -) -> WasiResult { - let path = resolved.concatenate()?; - let file = File::open(path)?; - host_impl::filestat_from_win(&file) -} - -pub(crate) fn path_filestat_set_times( - resolved: PathGet, - dirflags: wasi::__wasi_lookupflags_t, - st_atim: wasi::__wasi_timestamp_t, - mut st_mtim: wasi::__wasi_timestamp_t, - fst_flags: wasi::__wasi_fstflags_t, -) -> WasiResult<()> { - use winx::file::AccessMode; - let path = resolved.concatenate()?; - let file = OpenOptions::new() - .access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits()) - .open(path)?; - let modifiable_fd = Descriptor::OsHandle(OsHandle::from(file)); - fd_filestat_set_times_impl(&modifiable_fd, st_atim, st_mtim, fst_flags) -} - -pub(crate) fn path_symlink(old_path: &str, resolved: PathGet) -> WasiResult<()> { - use std::os::windows::fs::{symlink_dir, symlink_file}; - - let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?; - let new_path = resolved.concatenate()?; - - // try creating a file symlink - let err = match symlink_file(&old_path, &new_path) { - Ok(()) => return Ok(()), - Err(e) => e, - }; - match err.raw_os_error() { - Some(code) => { - log::debug!("path_symlink at symlink_file error code={:?}", code); - match code as u32 { - winerror::ERROR_NOT_A_REPARSE_POINT => { - // try creating a dir symlink instead - return symlink_dir(old_path, new_path).map_err(Into::into); - } - winerror::ERROR_ACCESS_DENIED => { - // does the target exist? - if new_path.exists() { - return Err(WasiError::EEXIST); - } - } - winerror::ERROR_INVALID_NAME => { - // does the target without trailing slashes exist? - if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? { - if path.exists() { - return Err(WasiError::EEXIST); - } - } - } - _ => {} - } - - Err(err.into()) - } - None => { - log::debug!("Inconvertible OS error: {}", err); - Err(WasiError::EIO) - } - } -} - -pub(crate) fn path_unlink_file(resolved: PathGet) -> WasiResult<()> { - use std::fs; - - let path = resolved.concatenate()?; - let file_type = path - .symlink_metadata() - .map(|metadata| metadata.file_type())?; - - // check if we're unlinking a symlink - // NB this will get cleaned up a lot when [std::os::windows::fs::FileTypeExt] - // stabilises - // - // [std::os::windows::fs::FileTypeExt]: https://doc.rust-lang.org/std/os/windows/fs/trait.FileTypeExt.html - if file_type.is_symlink() { - let err = match fs::remove_file(&path) { - Ok(()) => return Ok(()), - Err(e) => e, - }; - match err.raw_os_error() { - Some(code) => { - log::debug!("path_unlink_file at symlink_file error code={:?}", code); - if code as u32 == winerror::ERROR_ACCESS_DENIED { - // try unlinking a dir symlink instead - return fs::remove_dir(path).map_err(Into::into); - } - - Err(err.into()) - } - None => { - log::debug!("Inconvertible OS error: {}", err); - Err(WasiError::EIO) - } - } - } else if file_type.is_dir() { - Err(WasiError::EISDIR) - } else if file_type.is_file() { - fs::remove_file(path).map_err(Into::into) - } else { - Err(WasiError::EINVAL) - } -} - -pub(crate) fn path_remove_directory(resolved: PathGet) -> WasiResult<()> { - let path = resolved.concatenate()?; - std::fs::remove_dir(&path).map_err(Into::into) -} diff --git a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs b/crates/wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs deleted file mode 100644 index 41ea9bbacf..0000000000 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs_helpers.rs +++ /dev/null @@ -1,145 +0,0 @@ -#![allow(non_camel_case_types)] -use crate::entry::Descriptor; -use crate::hostcalls_impl::PathGet; -use crate::wasi::{self, WasiError, WasiResult}; -use std::ffi::{OsStr, OsString}; -use std::fs::File; -use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::path::{Path, PathBuf}; -use winapi::shared::winerror; - -pub(crate) trait PathGetExt { - fn concatenate(&self) -> WasiResult; -} - -impl PathGetExt for PathGet { - fn concatenate(&self) -> WasiResult { - match self.dirfd() { - Descriptor::OsHandle(file) => concatenate(file, Path::new(self.path())), - Descriptor::VirtualFile(_virt) => { - panic!("concatenate on a virtual base"); - } - Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => { - unreachable!("streams do not have paths and should not be accessible via PathGet"); - } - } - } -} - -pub(crate) fn path_open_rights( - rights_base: wasi::__wasi_rights_t, - rights_inheriting: wasi::__wasi_rights_t, - oflags: wasi::__wasi_oflags_t, - fdflags: wasi::__wasi_fdflags_t, -) -> (wasi::__wasi_rights_t, wasi::__wasi_rights_t) { - // which rights are needed on the dirfd? - let mut needed_base = wasi::__WASI_RIGHTS_PATH_OPEN; - let mut needed_inheriting = rights_base | rights_inheriting; - - // convert open flags - if oflags & wasi::__WASI_OFLAGS_CREAT != 0 { - needed_base |= wasi::__WASI_RIGHTS_PATH_CREATE_FILE; - } else if oflags & wasi::__WASI_OFLAGS_TRUNC != 0 { - needed_base |= wasi::__WASI_RIGHTS_PATH_FILESTAT_SET_SIZE; - } - - // convert file descriptor flags - if fdflags & wasi::__WASI_FDFLAGS_DSYNC != 0 - || fdflags & wasi::__WASI_FDFLAGS_RSYNC != 0 - || fdflags & wasi::__WASI_FDFLAGS_SYNC != 0 - { - needed_inheriting |= wasi::__WASI_RIGHTS_FD_DATASYNC; - needed_inheriting |= wasi::__WASI_RIGHTS_FD_SYNC; - } - - (needed_base, needed_inheriting) -} - -pub(crate) fn openat(dirfd: &File, path: &str) -> WasiResult { - use std::fs::OpenOptions; - use std::os::windows::fs::OpenOptionsExt; - use winx::file::Flags; - - let path = concatenate(dirfd, Path::new(path))?; - let err = match OpenOptions::new() - .read(true) - .custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits()) - .open(&path) - { - Ok(file) => return Ok(file), - Err(e) => e, - }; - if let Some(code) = err.raw_os_error() { - log::debug!("openat error={:?}", code); - if code as u32 == winerror::ERROR_INVALID_NAME { - return Err(WasiError::ENOTDIR); - } - } - Err(err.into()) -} - -pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> WasiResult { - use winx::file::get_file_path; - - let path = concatenate(dirfd, Path::new(s_path))?; - let err = match path.read_link() { - Ok(target_path) => { - // since on Windows we are effectively emulating 'at' syscalls - // we need to strip the prefix from the absolute path - // as otherwise we will error out since WASI is not capable - // of dealing with absolute paths - let dir_path = get_file_path(dirfd)?; - let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); - let target_path = target_path - .strip_prefix(dir_path) - .map_err(|_| WasiError::ENOTCAPABLE)?; - let target_path = target_path.to_str().ok_or(WasiError::EILSEQ)?; - return Ok(target_path.to_owned()); - } - Err(e) => e, - }; - if let Some(code) = err.raw_os_error() { - log::debug!("readlinkat error={:?}", code); - if code as u32 == winerror::ERROR_INVALID_NAME { - if s_path.ends_with('/') { - // strip "/" and check if exists - let path = concatenate(dirfd, Path::new(s_path.trim_end_matches('/')))?; - if path.exists() && !path.is_dir() { - return Err(WasiError::ENOTDIR); - } - } - } - } - Err(err.into()) -} - -pub(crate) fn strip_extended_prefix>(path: P) -> OsString { - let path: Vec = path.as_ref().encode_wide().collect(); - if &[92, 92, 63, 92] == &path[0..4] { - OsString::from_wide(&path[4..]) - } else { - OsString::from_wide(&path) - } -} - -pub(crate) fn concatenate>(file: &File, path: P) -> WasiResult { - use winx::file::get_file_path; - - // WASI is not able to deal with absolute paths - // so error out if absolute - if path.as_ref().is_absolute() { - return Err(WasiError::ENOTCAPABLE); - } - - let dir_path = get_file_path(file)?; - // concatenate paths - let mut out_path = PathBuf::from(dir_path); - out_path.push(path.as_ref()); - // strip extended prefix; otherwise we will error out on any relative - // components with `out_path` - let out_path = PathBuf::from(strip_extended_prefix(out_path)); - - log::debug!("out_path={:?}", out_path); - - Ok(out_path) -} diff --git a/crates/wasi-common/src/sys/windows/hostcalls_impl/mod.rs b/crates/wasi-common/src/sys/windows/hostcalls_impl/mod.rs deleted file mode 100644 index c8c383831d..0000000000 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Windows-specific hostcalls that implement -//! [WASI](https://github.com/WebAssembly/WASI). -mod fs; -pub(crate) mod fs_helpers; -mod misc; - -pub(crate) use self::fs::*; -pub(crate) use self::misc::*; diff --git a/crates/wasi-common/src/sys/windows/mod.rs b/crates/wasi-common/src/sys/windows/mod.rs index 7bece1e056..bb559be5e2 100644 --- a/crates/wasi-common/src/sys/windows/mod.rs +++ b/crates/wasi-common/src/sys/windows/mod.rs @@ -1,16 +1,23 @@ -pub(crate) mod entry_impl; -pub(crate) mod host_impl; -pub(crate) mod hostcalls_impl; +pub(crate) mod clock; +pub(crate) mod entry; +pub(crate) mod fd; +pub(crate) mod path; +pub(crate) mod poll; +use crate::wasi::{types, Errno, Result}; +use std::convert::{TryFrom, TryInto}; use std::fs::{File, OpenOptions}; -use std::io::Result; use std::path::Path; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::{io, string}; +use winapi::shared::winerror; +use winx::file::{CreationDisposition, Flags}; -pub(crate) fn dev_null() -> Result { +pub(crate) fn dev_null() -> io::Result { OpenOptions::new().read(true).write(true).open("NUL") } -pub fn preopen_dir>(path: P) -> Result { +pub fn preopen_dir>(path: P) -> io::Result { use std::fs::OpenOptions; use std::os::windows::fs::OpenOptionsExt; use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS; @@ -25,3 +32,132 @@ pub fn preopen_dir>(path: P) -> Result { .attributes(FILE_FLAG_BACKUP_SEMANTICS) .open(path) } + +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; + let no = (u64::from(high) << 32) | u64::from(low); + Ok(no) +} + +impl From for Errno { + fn from(err: io::Error) -> Self { + match err.raw_os_error() { + Some(code) => match code as u32 { + winerror::ERROR_SUCCESS => Self::Success, + winerror::ERROR_BAD_ENVIRONMENT => Self::TooBig, + winerror::ERROR_FILE_NOT_FOUND => Self::Noent, + winerror::ERROR_PATH_NOT_FOUND => Self::Noent, + winerror::ERROR_TOO_MANY_OPEN_FILES => Self::Nfile, + winerror::ERROR_ACCESS_DENIED => Self::Acces, + winerror::ERROR_SHARING_VIOLATION => Self::Acces, + winerror::ERROR_PRIVILEGE_NOT_HELD => Self::Notcapable, + winerror::ERROR_INVALID_HANDLE => Self::Badf, + winerror::ERROR_INVALID_NAME => Self::Noent, + winerror::ERROR_NOT_ENOUGH_MEMORY => Self::Nomem, + winerror::ERROR_OUTOFMEMORY => Self::Nomem, + winerror::ERROR_DIR_NOT_EMPTY => Self::Notempty, + winerror::ERROR_NOT_READY => Self::Busy, + winerror::ERROR_BUSY => Self::Busy, + winerror::ERROR_NOT_SUPPORTED => Self::Notsup, + winerror::ERROR_FILE_EXISTS => Self::Exist, + winerror::ERROR_BROKEN_PIPE => Self::Pipe, + winerror::ERROR_BUFFER_OVERFLOW => Self::Nametoolong, + winerror::ERROR_NOT_A_REPARSE_POINT => Self::Inval, + winerror::ERROR_NEGATIVE_SEEK => Self::Inval, + winerror::ERROR_DIRECTORY => Self::Notdir, + winerror::ERROR_ALREADY_EXISTS => Self::Exist, + x => { + log::debug!("unknown error value: {}", x); + Self::Io + } + }, + None => { + log::debug!("Other I/O error: {}", err); + Self::Io + } + } + } +} + +impl From for Errno { + fn from(_err: string::FromUtf16Error) -> Self { + Self::Ilseq + } +} + +fn num_hardlinks(file: &File) -> io::Result { + Ok(winx::file::get_fileinfo(file)?.nNumberOfLinks.into()) +} + +fn device_id(file: &File) -> io::Result { + Ok(winx::file::get_fileinfo(file)?.dwVolumeSerialNumber.into()) +} + +fn change_time(file: &File) -> io::Result { + winx::file::change_time(file) +} + +fn systemtime_to_timestamp(st: SystemTime) -> Result { + st.duration_since(UNIX_EPOCH) + .map_err(|_| Errno::Inval)? // date earlier than UNIX_EPOCH + .as_nanos() + .try_into() + .map_err(Into::into) // u128 doesn't fit into u64 +} + +impl TryFrom<&File> for types::Filestat { + type Error = Errno; + + fn try_from(file: &File) -> Result { + let metadata = file.metadata()?; + Ok(types::Filestat { + dev: device_id(file)?, + ino: file_serial_no(file)?, + nlink: num_hardlinks(file)?.try_into()?, // u64 doesn't fit into u32 + size: metadata.len(), + atim: systemtime_to_timestamp(metadata.accessed()?)?, + ctim: change_time(file)?.try_into()?, // i64 doesn't fit into u64 + mtim: systemtime_to_timestamp(metadata.modified()?)?, + filetype: metadata.file_type().into(), + }) + } +} + +impl From for CreationDisposition { + fn from(oflags: types::Oflags) -> Self { + if oflags.contains(&types::Oflags::CREAT) { + if oflags.contains(&types::Oflags::EXCL) { + CreationDisposition::CREATE_NEW + } else { + CreationDisposition::CREATE_ALWAYS + } + } else if oflags.contains(&types::Oflags::TRUNC) { + CreationDisposition::TRUNCATE_EXISTING + } else { + CreationDisposition::OPEN_EXISTING + } + } +} + +impl From for Flags { + fn from(fdflags: types::Fdflags) -> Self { + // 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.contains(&types::Fdflags::DSYNC) + || fdflags.contains(&types::Fdflags::RSYNC) + || fdflags.contains(&types::Fdflags::SYNC) + { + flags.insert(Flags::FILE_FLAG_WRITE_THROUGH); + } + + flags + } +} diff --git a/crates/wasi-common/src/sys/windows/path.rs b/crates/wasi-common/src/sys/windows/path.rs new file mode 100644 index 0000000000..32f9d7e46d --- /dev/null +++ b/crates/wasi-common/src/sys/windows/path.rs @@ -0,0 +1,506 @@ +use crate::entry::Descriptor; +use crate::fd; +use crate::path::PathGet; +use crate::sys::entry::OsHandle; +use crate::wasi::{types, Errno, Result}; +use std::convert::TryInto; +use std::ffi::{OsStr, OsString}; +use std::fs::{File, OpenOptions}; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::os::windows::fs::OpenOptionsExt; +use std::path::{Path, PathBuf}; +use winapi::shared::winerror; +use winx::file::AccessMode; + +/// Creates owned WASI path from OS string. +/// +/// NB WASI spec requires OS string to be valid UTF-8. Otherwise, +/// `__WASI_ERRNO_ILSEQ` error is returned. +pub(crate) fn from_host>(s: S) -> Result { + let vec: Vec = s.as_ref().encode_wide().collect(); + let s = String::from_utf16(&vec)?; + Ok(s) +} + +pub(crate) trait PathGetExt { + fn concatenate(&self) -> Result; +} + +impl PathGetExt for PathGet { + fn concatenate(&self) -> Result { + match self.dirfd() { + Descriptor::OsHandle(file) => concatenate(file, Path::new(self.path())), + Descriptor::VirtualFile(_virt) => { + panic!("concatenate on a virtual base"); + } + Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => { + unreachable!("streams do not have paths and should not be accessible via PathGet"); + } + } + } +} + +pub(crate) fn open_rights( + rights_base: types::Rights, + rights_inheriting: types::Rights, + oflags: types::Oflags, + fdflags: types::Fdflags, +) -> (types::Rights, types::Rights) { + // which rights are needed on the dirfd? + let mut needed_base = types::Rights::PATH_OPEN; + let mut needed_inheriting = rights_base | rights_inheriting; + + // convert open flags + if oflags.contains(&types::Oflags::CREAT) { + needed_base |= types::Rights::PATH_CREATE_FILE; + } else if oflags.contains(&types::Oflags::TRUNC) { + needed_base |= types::Rights::PATH_FILESTAT_SET_SIZE; + } + + // convert file descriptor flags + if fdflags.contains(&types::Fdflags::DSYNC) + || fdflags.contains(&types::Fdflags::RSYNC) + || fdflags.contains(&types::Fdflags::SYNC) + { + needed_inheriting |= types::Rights::FD_DATASYNC; + needed_inheriting |= types::Rights::FD_SYNC; + } + + (needed_base, needed_inheriting) +} + +pub(crate) fn openat(dirfd: &File, path: &str) -> Result { + use std::fs::OpenOptions; + use std::os::windows::fs::OpenOptionsExt; + use winx::file::Flags; + + let path = concatenate(dirfd, Path::new(path))?; + let err = match OpenOptions::new() + .read(true) + .custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits()) + .open(&path) + { + Ok(file) => return Ok(file), + Err(e) => e, + }; + if let Some(code) = err.raw_os_error() { + log::debug!("openat error={:?}", code); + if code as u32 == winerror::ERROR_INVALID_NAME { + return Err(Errno::Notdir); + } + } + Err(err.into()) +} + +pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result { + use winx::file::get_file_path; + + let path = concatenate(dirfd, Path::new(s_path))?; + let err = match path.read_link() { + Ok(target_path) => { + // since on Windows we are effectively emulating 'at' syscalls + // we need to strip the prefix from the absolute path + // as otherwise we will error out since WASI is not capable + // of dealing with absolute paths + let dir_path = get_file_path(dirfd)?; + let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); + let target_path = target_path + .strip_prefix(dir_path) + .map_err(|_| Errno::Notcapable)?; + let target_path = target_path.to_str().ok_or(Errno::Ilseq)?; + return Ok(target_path.to_owned()); + } + Err(e) => e, + }; + if let Some(code) = err.raw_os_error() { + log::debug!("readlinkat error={:?}", code); + if code as u32 == winerror::ERROR_INVALID_NAME { + if s_path.ends_with('/') { + // strip "/" and check if exists + let path = concatenate(dirfd, Path::new(s_path.trim_end_matches('/')))?; + if path.exists() && !path.is_dir() { + return Err(Errno::Notdir); + } + } + } + } + Err(err.into()) +} + +fn strip_extended_prefix>(path: P) -> OsString { + let path: Vec = path.as_ref().encode_wide().collect(); + if &[92, 92, 63, 92] == &path[0..4] { + OsString::from_wide(&path[4..]) + } else { + OsString::from_wide(&path) + } +} + +fn concatenate>(file: &File, path: P) -> Result { + use winx::file::get_file_path; + + // WASI is not able to deal with absolute paths + // so error out if absolute + if path.as_ref().is_absolute() { + return Err(Errno::Notcapable); + } + + let dir_path = get_file_path(file)?; + // concatenate paths + let mut out_path = PathBuf::from(dir_path); + out_path.push(path.as_ref()); + // strip extended prefix; otherwise we will error out on any relative + // components with `out_path` + let out_path = PathBuf::from(strip_extended_prefix(out_path)); + + log::debug!("out_path={:?}", out_path); + + Ok(out_path) +} + +pub(crate) fn create_directory(file: &File, path: &str) -> Result<()> { + let path = concatenate(file, path)?; + std::fs::create_dir(&path)?; + Ok(()) +} + +pub(crate) fn link( + _resolved_old: PathGet, + _resolved_new: PathGet, + _follow_symlinks: bool, +) -> Result<()> { + unimplemented!("path_link") +} + +pub(crate) fn open( + resolved: PathGet, + read: bool, + write: bool, + oflags: types::Oflags, + fdflags: types::Fdflags, +) -> Result { + use winx::file::{AccessMode, CreationDisposition, Flags}; + + let is_trunc = oflags.contains(&types::Oflags::TRUNC); + + if is_trunc { + // Windows does not support append mode when opening for truncation + // This is because truncation requires `GENERIC_WRITE` access, which will override the removal + // of the `FILE_WRITE_DATA` permission. + if fdflags.contains(&types::Fdflags::APPEND) { + return Err(Errno::Notsup); + } + } + + // 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 calculated below. + let mut opts = OpenOptions::new(); + match oflags.into() { + CreationDisposition::CREATE_ALWAYS => { + opts.create(true).write(true); + } + CreationDisposition::CREATE_NEW => { + opts.create_new(true).write(true); + } + CreationDisposition::TRUNCATE_EXISTING => { + opts.truncate(true).write(true); + } + _ => {} + } + + let path = resolved.concatenate()?; + + match path.symlink_metadata().map(|metadata| metadata.file_type()) { + Ok(file_type) => { + // check if we are trying to open a symlink + if file_type.is_symlink() { + return Err(Errno::Loop); + } + // check if we are trying to open a file as a dir + if file_type.is_file() && oflags.contains(&types::Oflags::DIRECTORY) { + return Err(Errno::Notdir); + } + } + Err(err) => match err.raw_os_error() { + Some(code) => { + log::debug!("path_open at symlink_metadata error code={:?}", code); + + if code as u32 != winerror::ERROR_FILE_NOT_FOUND { + return Err(err.into()); + } + // file not found, let it proceed to actually + // trying to open it + } + None => { + log::debug!("Inconvertible OS error: {}", err); + return Err(Errno::Io); + } + }, + } + + let mut access_mode = file_access_mode_from_fdflags(fdflags, read, write); + + // Truncation requires the special `GENERIC_WRITE` bit set (this is why it doesn't work with append-only mode) + if is_trunc { + access_mode |= AccessMode::GENERIC_WRITE; + } + + let flags: Flags = fdflags.into(); + opts.access_mode(access_mode.bits()) + .custom_flags(flags.bits()) + .open(&path) + .map(|f| OsHandle::from(f).into()) + .map_err(Into::into) +} + +fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode { + let mut access_mode = AccessMode::READ_CONTROL; + + // Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode + // The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead + // These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below) + if read { + access_mode.insert(AccessMode::FILE_GENERIC_READ); + } + + if write { + access_mode.insert(AccessMode::FILE_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.contains(&types::Fdflags::APPEND) { + access_mode.insert(AccessMode::FILE_APPEND_DATA); + access_mode.remove(AccessMode::FILE_WRITE_DATA); + } + + access_mode +} + +pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result { + use winx::file::get_file_path; + + let path = resolved.concatenate()?; + let target_path = path.read_link()?; + + // since on Windows we are effectively emulating 'at' syscalls + // we need to strip the prefix from the absolute path + // as otherwise we will error out since WASI is not capable + // of dealing with absolute paths + let dir_path = get_file_path(&resolved.dirfd().as_os_handle())?; + let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); + let target_path = target_path + .strip_prefix(dir_path) + .map_err(|_| Errno::Notcapable) + .and_then(|path| path.to_str().map(String::from).ok_or(Errno::Ilseq))?; + + if buf.len() > 0 { + let mut chars = target_path.chars(); + let mut nread = 0usize; + + for i in 0..buf.len() { + match chars.next() { + Some(ch) => { + buf[i] = ch as u8; + nread += 1; + } + None => break, + } + } + + Ok(nread) + } else { + Ok(0) + } +} + +fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result> { + if resolved.path().ends_with('/') { + let suffix = resolved.path().trim_end_matches('/'); + concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some) + } else { + Ok(None) + } +} + +pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { + use std::fs; + + let old_path = resolved_old.concatenate()?; + let new_path = resolved_new.concatenate()?; + + // First sanity check: check we're not trying to rename dir to file or vice versa. + // NB on Windows, the former is actually permitted [std::fs::rename]. + // + // [std::fs::rename]: https://doc.rust-lang.org/std/fs/fn.rename.html + if old_path.is_dir() && new_path.is_file() { + return Err(Errno::Notdir); + } + // Second sanity check: check we're not trying to rename a file into a path + // ending in a trailing slash. + if old_path.is_file() && resolved_new.path().ends_with('/') { + return Err(Errno::Notdir); + } + + // TODO handle symlinks + let err = match fs::rename(&old_path, &new_path) { + Ok(()) => return Ok(()), + Err(e) => e, + }; + match err.raw_os_error() { + Some(code) => { + log::debug!("path_rename at rename error code={:?}", code); + match code as u32 { + winerror::ERROR_ACCESS_DENIED => { + // So most likely dealing with new_path == dir. + // Eliminate case old_path == file first. + if old_path.is_file() { + return Err(Errno::Isdir); + } else { + // Ok, let's try removing an empty dir at new_path if it exists + // and is a nonempty dir. + fs::remove_dir(&new_path)?; + fs::rename(old_path, new_path)?; + return Ok(()); + } + } + winerror::ERROR_INVALID_NAME => { + // If source contains trailing slashes, check if we are dealing with + // a file instead of a dir, and if so, throw ENOTDIR. + if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? { + if path.is_file() { + return Err(Errno::Notdir); + } + } + } + _ => {} + } + + Err(err.into()) + } + None => { + log::debug!("Inconvertible OS error: {}", err); + Err(Errno::Io) + } + } +} + +pub(crate) fn filestat_get( + resolved: PathGet, + _dirflags: types::Lookupflags, +) -> Result { + let path = resolved.concatenate()?; + let file = File::open(path)?; + let filestat = (&file).try_into()?; + Ok(filestat) +} + +pub(crate) fn filestat_set_times( + resolved: PathGet, + _dirflags: types::Lookupflags, + st_atim: types::Timestamp, + st_mtim: types::Timestamp, + fst_flags: types::Fstflags, +) -> Result<()> { + use winx::file::AccessMode; + let path = resolved.concatenate()?; + let file = OpenOptions::new() + .access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits()) + .open(path)?; + let modifiable_fd = Descriptor::OsHandle(OsHandle::from(file)); + fd::filestat_set_times_impl(&modifiable_fd, st_atim, st_mtim, fst_flags) +} + +pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { + use std::os::windows::fs::{symlink_dir, symlink_file}; + + let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?; + let new_path = resolved.concatenate()?; + + // try creating a file symlink + let err = match symlink_file(&old_path, &new_path) { + Ok(()) => return Ok(()), + Err(e) => e, + }; + match err.raw_os_error() { + Some(code) => { + log::debug!("path_symlink at symlink_file error code={:?}", code); + match code as u32 { + winerror::ERROR_NOT_A_REPARSE_POINT => { + // try creating a dir symlink instead + return symlink_dir(old_path, new_path).map_err(Into::into); + } + winerror::ERROR_ACCESS_DENIED => { + // does the target exist? + if new_path.exists() { + return Err(Errno::Exist); + } + } + winerror::ERROR_INVALID_NAME => { + // does the target without trailing slashes exist? + if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? { + if path.exists() { + return Err(Errno::Exist); + } + } + } + _ => {} + } + + Err(err.into()) + } + None => { + log::debug!("Inconvertible OS error: {}", err); + Err(Errno::Io) + } + } +} + +pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { + use std::fs; + + let path = resolved.concatenate()?; + let file_type = path + .symlink_metadata() + .map(|metadata| metadata.file_type())?; + + // check if we're unlinking a symlink + // NB this will get cleaned up a lot when [std::os::windows::fs::FileTypeExt] + // stabilises + // + // [std::os::windows::fs::FileTypeExt]: https://doc.rust-lang.org/std/os/windows/fs/trait.FileTypeExt.html + if file_type.is_symlink() { + let err = match fs::remove_file(&path) { + Ok(()) => return Ok(()), + Err(e) => e, + }; + match err.raw_os_error() { + Some(code) => { + log::debug!("path_unlink_file at symlink_file error code={:?}", code); + if code as u32 == winerror::ERROR_ACCESS_DENIED { + // try unlinking a dir symlink instead + return fs::remove_dir(path).map_err(Into::into); + } + + Err(err.into()) + } + None => { + log::debug!("Inconvertible OS error: {}", err); + Err(Errno::Io) + } + } + } else if file_type.is_dir() { + Err(Errno::Isdir) + } else if file_type.is_file() { + fs::remove_file(path).map_err(Into::into) + } else { + Err(Errno::Inval) + } +} + +pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> { + let path = resolved.concatenate()?; + std::fs::remove_dir(&path).map_err(Into::into) +} diff --git a/crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs b/crates/wasi-common/src/sys/windows/poll.rs similarity index 57% rename from crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs rename to crates/wasi-common/src/sys/windows/poll.rs index 51539bf7ae..71309d1875 100644 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs +++ b/crates/wasi-common/src/sys/windows/poll.rs @@ -1,22 +1,14 @@ -#![allow(non_camel_case_types)] -#![allow(unused_unsafe)] -#![allow(unused)] use crate::entry::Descriptor; -use crate::hostcalls_impl::{ClockEventData, FdEventData}; -use crate::memory::*; -use crate::sys::host_impl; -use crate::wasi::{self, WasiError, WasiResult}; -use crate::wasi32; -use cpu_time::{ProcessTime, ThreadTime}; +use crate::poll::{ClockEventData, FdEventData}; +use crate::wasi::{types, Errno, Result}; use lazy_static::lazy_static; use log::{debug, error, trace, warn}; use std::convert::TryInto; -use std::io; use std::os::windows::io::AsRawHandle; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; use std::sync::Mutex; use std::thread; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::time::Duration; struct StdinPoll { request_tx: Sender<()>, @@ -27,7 +19,7 @@ enum PollState { Ready, NotReady, // it's not ready, but we didn't wait TimedOut, // it's not ready and a timeout has occurred - Error(WasiError), + Error(Errno), } enum WaitMode { @@ -83,7 +75,7 @@ impl StdinPoll { // Linux returns `POLLIN` in both cases, and we imitate this behavior. let resp = match std::io::stdin().lock().fill_buf() { Ok(_) => PollState::Ready, - Err(e) => PollState::Error(WasiError::from(e)), + Err(e) => PollState::Error(Errno::from(e)), }; // Notify the requestor about data in stdin. They may have already timed out, @@ -94,8 +86,6 @@ impl StdinPoll { } lazy_static! { - static ref START_MONOTONIC: Instant = Instant::now(); - static ref PERF_COUNTER_RES: u64 = get_perf_counter_resolution_ns(); static ref STDIN_POLL: Mutex = { let (request_tx, request_rx) = mpsc::channel(); let (notify_tx, notify_rx) = mpsc::channel(); @@ -107,92 +97,30 @@ lazy_static! { }; } -// Timer resolution on Windows is really hard. We may consider exposing the resolution of the respective -// timers as an associated function in the future. -pub(crate) fn clock_res_get( - clock_id: wasi::__wasi_clockid_t, -) -> WasiResult { - Ok(match clock_id { - // This is the best that we can do with std::time::SystemTime. - // Rust uses GetSystemTimeAsFileTime, which is said to have the resolution of - // 10ms or 55ms, [1] but MSDN doesn't confirm this in any way. - // Even the MSDN article on high resolution timestamps doesn't even mention the precision - // for this method. [3] - // - // The timer resolution can be queried using one of the functions: [2, 5] - // * NtQueryTimerResolution, which is undocumented and thus not exposed by the winapi crate - // * timeGetDevCaps, which returns the upper and lower bound for the precision, in ms. - // While the upper bound seems like something we could use, it's typically too high to be meaningful. - // For instance, the intervals return by the syscall are: - // * [1, 65536] on Wine - // * [1, 1000000] on Windows 10, which is up to (sic) 1000 seconds. - // - // It's possible to manually set the timer resolution, but this sounds like something which should - // only be done temporarily. [5] - // - // Alternatively, we could possibly use GetSystemTimePreciseAsFileTime in clock_time_get, but - // this syscall is only available starting from Windows 8. - // (we could possibly emulate it on earlier versions of Windows, see [4]) - // The MSDN are not clear on the resolution of GetSystemTimePreciseAsFileTime either, but a - // Microsoft devblog entry [1] suggests that it kind of combines GetSystemTimeAsFileTime with - // QueryPeformanceCounter, which probably means that those two should have the same resolution. - // - // See also this discussion about the use of GetSystemTimePreciseAsFileTime in Python stdlib, - // which in particular contains some resolution benchmarks. - // - // [1] https://devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057 - // [2] http://www.windowstimestamp.com/description - // [3] https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps?redirectedfrom=MSDN - // [4] https://www.codeproject.com/Tips/1011902/High-Resolution-Time-For-Windows - // [5] https://stackoverflow.com/questions/7685762/windows-7-timing-functions-how-to-use-getsystemtimeadjustment-correctly - // [6] https://bugs.python.org/issue19007 - wasi::__WASI_CLOCKID_REALTIME => 55_000_000, - // std::time::Instant uses QueryPerformanceCounter & QueryPerformanceFrequency internally - wasi::__WASI_CLOCKID_MONOTONIC => *PERF_COUNTER_RES, - // The best we can do is to hardcode the value from the docs. - // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes - wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => 100, - // The best we can do is to hardcode the value from the docs. - // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes - wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => 100, - _ => return Err(WasiError::EINVAL), - }) -} - -pub(crate) fn clock_time_get( - clock_id: wasi::__wasi_clockid_t, -) -> WasiResult { - let duration = match clock_id { - wasi::__WASI_CLOCKID_REALTIME => get_monotonic_time(), - wasi::__WASI_CLOCKID_MONOTONIC => get_realtime_time()?, - wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => get_proc_cputime()?, - wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => get_thread_cputime()?, - _ => return Err(WasiError::EINVAL), - }; - duration.as_nanos().try_into().map_err(Into::into) -} - -fn make_rw_event(event: &FdEventData, nbytes: WasiResult) -> wasi::__wasi_event_t { +fn make_rw_event(event: &FdEventData, nbytes: Result) -> types::Event { let (nbytes, error) = match nbytes { - Ok(nbytes) => (nbytes, WasiError::ESUCCESS), + Ok(nbytes) => (nbytes, Errno::Success), Err(e) => (u64::default(), e), }; - wasi::__wasi_event_t { + types::Event { userdata: event.userdata, - r#type: event.r#type, - error: error.as_raw_errno(), - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { nbytes, flags: 0 }, + type_: event.r#type, + error, + fd_readwrite: types::EventFdReadwrite { + nbytes, + flags: types::Eventrwflags::empty(), + }, } } -fn make_timeout_event(timeout: &ClockEventData) -> wasi::__wasi_event_t { - wasi::__wasi_event_t { +fn make_timeout_event(timeout: &ClockEventData) -> types::Event { + types::Event { userdata: timeout.userdata, - r#type: wasi::__WASI_EVENTTYPE_CLOCK, - error: wasi::__WASI_ERRNO_SUCCESS, - fd_readwrite: wasi::__wasi_event_fd_readwrite_t { + type_: types::Eventtype::Clock, + error: Errno::Success, + fd_readwrite: types::EventFdReadwrite { nbytes: 0, - flags: 0, + flags: types::Eventrwflags::empty(), }, } } @@ -200,21 +128,22 @@ fn make_timeout_event(timeout: &ClockEventData) -> wasi::__wasi_event_t { fn handle_timeout( timeout_event: ClockEventData, timeout: Duration, - events: &mut Vec, + events: &mut Vec, ) { thread::sleep(timeout); handle_timeout_event(timeout_event, events); } -fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec) { +fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec) { let new_event = make_timeout_event(&timeout_event); events.push(new_event); } -fn handle_rw_event(event: FdEventData, out_events: &mut Vec) { - let size = match event.descriptor { +fn handle_rw_event(event: FdEventData, out_events: &mut Vec) { + let descriptor: &Descriptor = &event.descriptor; + let size = match descriptor { Descriptor::OsHandle(os_handle) => { - if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { + if event.r#type == types::Eventtype::FdRead { os_handle.metadata().map(|m| m.len()).map_err(Into::into) } else { // The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and @@ -237,23 +166,16 @@ fn handle_rw_event(event: FdEventData, out_events: &mut Vec, -) { +fn handle_error_event(event: FdEventData, error: Errno, out_events: &mut Vec) { let new_event = make_rw_event(&event, Err(error)); out_events.push(new_event); } -pub(crate) fn poll_oneoff( +pub(crate) fn oneoff( timeout: Option, fd_events: Vec, - events: &mut Vec, -) -> WasiResult<()> { - use std::fs::Metadata; - use std::thread; - + events: &mut Vec, +) -> Result<()> { let timeout = timeout .map(|event| { event @@ -279,8 +201,9 @@ pub(crate) fn poll_oneoff( let mut pipe_events = vec![]; for event in fd_events { - match event.descriptor { - Descriptor::Stdin if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ => { + let descriptor: &Descriptor = &event.descriptor; + match descriptor { + Descriptor::Stdin if event.r#type == types::Eventtype::FdRead => { stdin_events.push(event) } // stdout/stderr are always considered ready to write because there seems to @@ -295,7 +218,7 @@ pub(crate) fn poll_oneoff( let ftype = unsafe { winx::file::get_file_type(os_handle.as_raw_handle()) }?; if ftype.is_unknown() || ftype.is_char() { debug!("poll_oneoff: unsupported file type: {:?}", ftype); - handle_error_event(event, WasiError::ENOTSUP, events); + handle_error_event(event, Errno::Notsup, events); } else if ftype.is_disk() { immediate_events.push(event); } else if ftype.is_pipe() { @@ -314,12 +237,11 @@ pub(crate) fn poll_oneoff( // Process all the events that do not require waiting. if immediate { trace!(" | have immediate events, will return immediately"); - for mut event in immediate_events { + for event in immediate_events { handle_rw_event(event, events); } } if !stdin_events.is_empty() { - // During the firt request to poll stdin, we spin up a separate thread to // waiting for data to arrive on stdin. This thread will not terminate. // // We'd like to do the following: @@ -345,7 +267,7 @@ pub(crate) fn poll_oneoff( } else { trace!(" | passively waiting on stdin"); match timeout { - Some((event, dur)) => WaitMode::Timeout(dur), + Some((_event, dur)) => WaitMode::Timeout(dur), None => WaitMode::Infinite, } }; @@ -371,43 +293,10 @@ pub(crate) fn poll_oneoff( } None => { error!("Polling only pipes with no timeout not supported on Windows."); - return Err(WasiError::ENOTSUP); + return Err(Errno::Notsup); } } } Ok(()) } - -fn get_monotonic_time() -> Duration { - // We're circumventing the fact that we can't get a Duration from an Instant - // The epoch of __WASI_CLOCKID_MONOTONIC is undefined, so we fix a time point once - // and count relative to this time point. - // - // The alternative would be to copy over the implementation of std::time::Instant - // to our source tree and add a conversion to std::time::Duration - START_MONOTONIC.elapsed() -} - -fn get_realtime_time() -> WasiResult { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| WasiError::EFAULT) -} - -fn get_proc_cputime() -> WasiResult { - Ok(ProcessTime::try_now()?.as_duration()) -} - -fn get_thread_cputime() -> WasiResult { - Ok(ThreadTime::try_now()?.as_duration()) -} - -fn get_perf_counter_resolution_ns() -> u64 { - use winx::time::perf_counter_frequency; - const NANOS_PER_SEC: u64 = 1_000_000_000; - // This should always succeed starting from Windows XP, so it's fine to panic in case of an error. - let freq = perf_counter_frequency().expect("QueryPerformanceFrequency returned an error"); - let epsilon = NANOS_PER_SEC / freq; - epsilon -} diff --git a/crates/wasi-common/src/virtfs.rs b/crates/wasi-common/src/virtfs.rs index 76b943a9aa..c955c1663d 100644 --- a/crates/wasi-common/src/virtfs.rs +++ b/crates/wasi-common/src/virtfs.rs @@ -1,7 +1,4 @@ -use crate::host::Dirent; -use crate::host::FileType; -use crate::wasi::{self, WasiError, WasiResult}; -use crate::wasi32; +use crate::wasi::{self, types, Errno, Result, RightsExt}; use filetime::FileTime; use log::trace; use std::cell::RefCell; @@ -38,15 +35,17 @@ pub(crate) trait MovableFile { /// /// Default implementations of functions here fail in ways that are intended to mimic a file-like /// object with no permissions, no content, and that cannot be used in any way. +// TODO This trait should potentially be made unsafe since we need to assert that we don't +// reenter wasm or try to reborrow/read/etc. from wasm memory. pub(crate) trait VirtualFile: MovableFile { - fn fdstat_get(&self) -> wasi::__wasi_fdflags_t { - 0 + fn fdstat_get(&self) -> types::Fdflags { + types::Fdflags::empty() } fn try_clone(&self) -> io::Result>; - fn readlinkat(&self, _path: &Path) -> WasiResult { - Err(WasiError::EACCES) + fn readlinkat(&self, _path: &Path) -> Result { + Err(Errno::Acces) } fn openat( @@ -54,160 +53,140 @@ pub(crate) trait VirtualFile: MovableFile { _path: &Path, _read: bool, _write: bool, - _oflags: wasi::__wasi_oflags_t, - _fd_flags: wasi::__wasi_fdflags_t, - ) -> WasiResult> { - Err(WasiError::EACCES) + _oflags: types::Oflags, + _fd_flags: types::Fdflags, + ) -> Result> { + Err(Errno::Acces) } - fn remove_directory(&self, _path: &str) -> WasiResult<()> { - Err(WasiError::EACCES) + fn remove_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Acces) } - fn unlink_file(&self, _path: &str) -> WasiResult<()> { - Err(WasiError::EACCES) + fn unlink_file(&self, _path: &str) -> Result<()> { + Err(Errno::Acces) } - fn datasync(&self) -> WasiResult<()> { - Err(WasiError::EINVAL) + fn datasync(&self) -> Result<()> { + Err(Errno::Inval) } - fn sync(&self) -> WasiResult<()> { + fn sync(&self) -> Result<()> { Ok(()) } - fn create_directory(&self, _path: &Path) -> WasiResult<()> { - Err(WasiError::EACCES) + fn create_directory(&self, _path: &Path) -> Result<()> { + Err(Errno::Acces) } fn readdir( &self, - _cookie: wasi::__wasi_dircookie_t, - ) -> WasiResult>>> { - Err(WasiError::EBADF) + _cookie: types::Dircookie, + ) -> Result>>> { + Err(Errno::Badf) } - fn write_vectored(&mut self, _iovs: &[io::IoSlice]) -> WasiResult { - Err(WasiError::EBADF) + fn write_vectored(&mut self, _iovs: &[io::IoSlice]) -> Result { + Err(Errno::Badf) } - fn pread(&self, _buf: &mut [u8], _offset: u64) -> WasiResult { - Err(WasiError::EBADF) + fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result { + Err(Errno::Badf) } - fn pwrite(&self, _buf: &mut [u8], _offset: u64) -> WasiResult { - Err(WasiError::EBADF) + fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result { + Err(Errno::Badf) } - fn seek(&mut self, _offset: SeekFrom) -> WasiResult { - Err(WasiError::EBADF) + fn seek(&mut self, _offset: SeekFrom) -> Result { + Err(Errno::Badf) } fn advise( &self, - _advice: wasi::__wasi_advice_t, - _offset: wasi::__wasi_filesize_t, - _len: wasi::__wasi_filesize_t, - ) -> WasiResult<()> { - Err(WasiError::EBADF) + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<()> { + Err(Errno::Badf) } - fn allocate( - &self, - _offset: wasi::__wasi_filesize_t, - _len: wasi::__wasi_filesize_t, - ) -> WasiResult<()> { - Err(WasiError::EBADF) + fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> { + Err(Errno::Badf) } - fn filestat_get(&self) -> WasiResult { - Err(WasiError::EBADF) + fn filestat_get(&self) -> Result { + Err(Errno::Badf) } - fn filestat_set_times( - &self, - _atim: Option, - _mtim: Option, - ) -> WasiResult<()> { - Err(WasiError::EBADF) + fn filestat_set_times(&self, _atim: Option, _mtim: Option) -> Result<()> { + Err(Errno::Badf) } - fn filestat_set_size(&self, _st_size: wasi::__wasi_filesize_t) -> WasiResult<()> { - Err(WasiError::EBADF) + fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> { + Err(Errno::Badf) } - fn fdstat_set_flags(&mut self, _fdflags: wasi::__wasi_fdflags_t) -> WasiResult<()> { - Err(WasiError::EBADF) + fn fdstat_set_flags(&mut self, _fdflags: types::Fdflags) -> Result<()> { + Err(Errno::Badf) } - fn read_vectored(&mut self, _iovs: &mut [io::IoSliceMut]) -> WasiResult { - Err(WasiError::EBADF) + fn read_vectored(&mut self, _iovs: &mut [io::IoSliceMut]) -> Result { + Err(Errno::Badf) } - fn get_file_type(&self) -> wasi::__wasi_filetype_t; + fn get_file_type(&self) -> types::Filetype; - fn get_rights_base(&self) -> wasi::__wasi_rights_t { - 0 + fn get_rights_base(&self) -> types::Rights { + types::Rights::empty() } - fn get_rights_inheriting(&self) -> wasi::__wasi_rights_t { - 0 + fn get_rights_inheriting(&self) -> types::Rights { + types::Rights::empty() } } pub trait FileContents { /// The implementation-defined maximum size of the store corresponding to a `FileContents` /// implementation. - fn max_size(&self) -> wasi::__wasi_filesize_t; + fn max_size(&self) -> types::Filesize; /// The current number of bytes this `FileContents` describes. - fn size(&self) -> wasi::__wasi_filesize_t; + fn size(&self) -> types::Filesize; /// Resize to hold `new_size` number of bytes, or error if this is not possible. - fn resize(&mut self, new_size: wasi::__wasi_filesize_t) -> WasiResult<()>; + fn resize(&mut self, new_size: types::Filesize) -> Result<()>; /// Write a list of `IoSlice` starting at `offset`. `offset` plus the total size of all `iovs` /// is guaranteed to not exceed `max_size`. Implementations must not indicate more bytes have /// been written than can be held by `iovs`. - fn pwritev( - &mut self, - iovs: &[io::IoSlice], - offset: wasi::__wasi_filesize_t, - ) -> WasiResult; + fn pwritev(&mut self, iovs: &[io::IoSlice], offset: types::Filesize) -> Result; /// Read from the file from `offset`, filling a list of `IoSlice`. The returend size must not /// be more than the capactiy of `iovs`, and must not exceed the limit reported by /// `self.max_size()`. - fn preadv( - &self, - iovs: &mut [io::IoSliceMut], - offset: wasi::__wasi_filesize_t, - ) -> WasiResult; + fn preadv(&self, iovs: &mut [io::IoSliceMut], offset: types::Filesize) -> Result; /// Write contents from `buf` to this file starting at `offset`. `offset` plus the length of /// `buf` is guaranteed to not exceed `max_size`. Implementations must not indicate more bytes /// have been written than the size of `buf`. - fn pwrite(&mut self, buf: &[u8], offset: wasi::__wasi_filesize_t) -> WasiResult; + fn pwrite(&mut self, buf: &[u8], offset: types::Filesize) -> Result; /// Read from the file at `offset`, filling `buf`. The returned size must not be more than the /// capacity of `buf`, and `offset` plus the returned size must not exceed `self.max_size()`. - fn pread(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> WasiResult; + fn pread(&self, buf: &mut [u8], offset: types::Filesize) -> Result; } impl FileContents for VecFileContents { - fn max_size(&self) -> wasi::__wasi_filesize_t { - std::usize::MAX as wasi::__wasi_filesize_t + fn max_size(&self) -> types::Filesize { + std::usize::MAX as types::Filesize } - fn size(&self) -> wasi::__wasi_filesize_t { - self.content.len() as wasi::__wasi_filesize_t + fn size(&self) -> types::Filesize { + self.content.len() as types::Filesize } - fn resize(&mut self, new_size: wasi::__wasi_filesize_t) -> WasiResult<()> { - let new_size: usize = new_size.try_into().map_err(|_| WasiError::EINVAL)?; + fn resize(&mut self, new_size: types::Filesize) -> Result<()> { + let new_size: usize = new_size.try_into().map_err(|_| Errno::Inval)?; self.content.resize(new_size, 0); Ok(()) } - fn preadv( - &self, - iovs: &mut [io::IoSliceMut], - offset: wasi::__wasi_filesize_t, - ) -> WasiResult { + fn preadv(&self, iovs: &mut [io::IoSliceMut], offset: types::Filesize) -> Result { let mut read_total = 0usize; for iov in iovs.iter_mut() { let read = self.pread(iov, offset)?; @@ -216,11 +195,7 @@ impl FileContents for VecFileContents { Ok(read_total) } - fn pwritev( - &mut self, - iovs: &[io::IoSlice], - offset: wasi::__wasi_filesize_t, - ) -> WasiResult { + fn pwritev(&mut self, iovs: &[io::IoSlice], offset: types::Filesize) -> Result { let mut write_total = 0usize; for iov in iovs.iter() { let written = self.pwrite(iov, offset)?; @@ -229,9 +204,9 @@ impl FileContents for VecFileContents { Ok(write_total) } - fn pread(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> WasiResult { + fn pread(&self, buf: &mut [u8], offset: types::Filesize) -> Result { trace!(" | pread(buf.len={}, offset={})", buf.len(), offset); - let offset: usize = offset.try_into().map_err(|_| WasiError::EINVAL)?; + let offset: usize = offset.try_into().map_err(|_| Errno::Inval)?; let data_remaining = self.content.len().saturating_sub(offset); @@ -244,10 +219,10 @@ impl FileContents for VecFileContents { res } - fn pwrite(&mut self, buf: &[u8], offset: wasi::__wasi_filesize_t) -> WasiResult { - let offset: usize = offset.try_into().map_err(|_| WasiError::EINVAL)?; + fn pwrite(&mut self, buf: &[u8], offset: types::Filesize) -> Result { + let offset: usize = offset.try_into().map_err(|_| Errno::Inval)?; - let write_end = offset.checked_add(buf.len()).ok_or(WasiError::EFBIG)?; + let write_end = offset.checked_add(buf.len()).ok_or(Errno::Fbig)?; if write_end > self.content.len() { self.content.resize(write_end, 0); @@ -275,9 +250,9 @@ impl VecFileContents { /// a filesystem wherein a file descriptor is one view into a possibly-shared underlying collection /// of data and permissions on a filesystem. pub struct InMemoryFile { - cursor: wasi::__wasi_filesize_t, + cursor: types::Filesize, parent: Rc>>>, - fd_flags: wasi::__wasi_fdflags_t, + fd_flags: types::Fdflags, data: Rc>>, } @@ -286,7 +261,7 @@ impl InMemoryFile { Self { cursor: 0, parent: Rc::new(RefCell::new(None)), - fd_flags: 0, + fd_flags: types::Fdflags::empty(), data: Rc::new(RefCell::new(Box::new(VecFileContents::new()))), } } @@ -294,7 +269,7 @@ impl InMemoryFile { pub fn new(contents: Box) -> Self { Self { cursor: 0, - fd_flags: 0, + fd_flags: types::Fdflags::empty(), parent: Rc::new(RefCell::new(None)), data: Rc::new(RefCell::new(contents)), } @@ -308,7 +283,7 @@ impl MovableFile for InMemoryFile { } impl VirtualFile for InMemoryFile { - fn fdstat_get(&self) -> wasi::__wasi_fdflags_t { + fn fdstat_get(&self) -> types::Fdflags { self.fd_flags } @@ -321,9 +296,9 @@ impl VirtualFile for InMemoryFile { })) } - fn readlinkat(&self, _path: &Path) -> WasiResult { + fn readlinkat(&self, _path: &Path) -> Result { // no symlink support, so always say it's invalid. - Err(WasiError::ENOTDIR) + Err(Errno::Notdir) } fn openat( @@ -331,9 +306,9 @@ impl VirtualFile for InMemoryFile { path: &Path, read: bool, write: bool, - oflags: wasi::__wasi_oflags_t, - fd_flags: wasi::__wasi_fdflags_t, - ) -> WasiResult> { + oflags: types::Oflags, + fd_flags: types::Fdflags, + ) -> Result> { log::trace!( "InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}", path, @@ -343,13 +318,13 @@ impl VirtualFile for InMemoryFile { fd_flags ); - if oflags & wasi::__WASI_OFLAGS_DIRECTORY != 0 { + if oflags.contains(&types::Oflags::DIRECTORY) { log::trace!( "InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.", path ); - log::trace!(" return ENOTDIR"); - return Err(WasiError::ENOTDIR); + log::trace!(" return Notdir"); + return Err(Errno::Notdir); } if path == Path::new(".") { @@ -360,29 +335,29 @@ impl VirtualFile for InMemoryFile { None => self.try_clone().map_err(Into::into), } } else { - Err(WasiError::EACCES) + Err(Errno::Acces) } } - fn remove_directory(&self, _path: &str) -> WasiResult<()> { - Err(WasiError::ENOTDIR) + fn remove_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) } - fn unlink_file(&self, _path: &str) -> WasiResult<()> { - Err(WasiError::ENOTDIR) + fn unlink_file(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) } - fn fdstat_set_flags(&mut self, fdflags: wasi::__wasi_fdflags_t) -> WasiResult<()> { + fn fdstat_set_flags(&mut self, fdflags: types::Fdflags) -> Result<()> { self.fd_flags = fdflags; Ok(()) } - fn write_vectored(&mut self, iovs: &[io::IoSlice]) -> WasiResult { + fn write_vectored(&mut self, iovs: &[io::IoSlice]) -> Result { trace!("write_vectored(iovs={:?})", iovs); let mut data = self.data.borrow_mut(); - let append_mode = self.fd_flags & wasi::__WASI_FDFLAGS_APPEND != 0; - trace!(" | fd_flags={:o}", self.fd_flags); + let append_mode = self.fd_flags.contains(&types::Fdflags::APPEND); + trace!(" | fd_flags={}", self.fd_flags); // If this file is in append mode, we write to the end. let write_start = if append_mode { @@ -394,7 +369,7 @@ impl VirtualFile for InMemoryFile { let max_size = iovs .iter() .map(|iov| { - let cast_iovlen: wasi32::size_t = iov + let cast_iovlen: types::Size = iov .len() .try_into() .expect("iovec are bounded by wasi max sizes"); @@ -403,12 +378,12 @@ impl VirtualFile for InMemoryFile { .fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov))) .expect("write_vectored will not be called with invalid iovs"); - if let Some(end) = write_start.checked_add(max_size as wasi::__wasi_filesize_t) { + if let Some(end) = write_start.checked_add(max_size as types::Filesize) { if end > data.max_size() { - return Err(WasiError::EFBIG); + return Err(Errno::Fbig); } } else { - return Err(WasiError::EFBIG); + return Err(Errno::Fbig); } trace!(" | *write_start={:?}", write_start); @@ -423,43 +398,41 @@ impl VirtualFile for InMemoryFile { Ok(written) } - fn read_vectored(&mut self, iovs: &mut [io::IoSliceMut]) -> WasiResult { + fn read_vectored(&mut self, iovs: &mut [io::IoSliceMut]) -> Result { trace!("read_vectored(iovs={:?})", iovs); trace!(" | *read_start={:?}", self.cursor); self.data.borrow_mut().preadv(iovs, self.cursor) } - fn pread(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> WasiResult { - self.data.borrow_mut().pread(buf, offset) + fn preadv(&self, buf: &mut [io::IoSliceMut], offset: types::Filesize) -> Result { + self.data.borrow_mut().preadv(buf, offset) } - fn pwrite(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> WasiResult { - self.data.borrow_mut().pwrite(buf, offset) + fn pwritev(&self, buf: &[io::IoSlice], offset: types::Filesize) -> Result { + self.data.borrow_mut().pwritev(buf, offset) } - fn seek(&mut self, offset: SeekFrom) -> WasiResult { + fn seek(&mut self, offset: SeekFrom) -> Result { let content_len = self.data.borrow().size(); match offset { SeekFrom::Current(offset) => { let new_cursor = if offset < 0 { self.cursor .checked_sub(offset.wrapping_neg() as u64) - .ok_or(WasiError::EINVAL)? + .ok_or(Errno::Inval)? } else { - self.cursor - .checked_add(offset as u64) - .ok_or(WasiError::EINVAL)? + self.cursor.checked_add(offset as u64).ok_or(Errno::Inval)? }; self.cursor = std::cmp::min(content_len, new_cursor); } SeekFrom::End(offset) => { // A negative offset from the end would be past the end of the file, - let offset: u64 = offset.try_into().map_err(|_| WasiError::EINVAL)?; + let offset: u64 = offset.try_into().map_err(|_| Errno::Inval)?; self.cursor = content_len.saturating_sub(offset); } SeekFrom::Start(offset) => { // A negative offset from the end would be before the start of the file. - let offset: u64 = offset.try_into().map_err(|_| WasiError::EINVAL)?; + let offset: u64 = offset.try_into().map_err(|_| Errno::Inval)?; self.cursor = std::cmp::min(content_len, offset); } } @@ -469,32 +442,20 @@ impl VirtualFile for InMemoryFile { fn advise( &self, - advice: wasi::__wasi_advice_t, - _offset: wasi::__wasi_filesize_t, - _len: wasi::__wasi_filesize_t, - ) -> WasiResult<()> { + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<()> { // we'll just ignore advice for now, unless it's totally invalid - match advice { - wasi::__WASI_ADVICE_DONTNEED - | wasi::__WASI_ADVICE_SEQUENTIAL - | wasi::__WASI_ADVICE_WILLNEED - | wasi::__WASI_ADVICE_NOREUSE - | wasi::__WASI_ADVICE_RANDOM - | wasi::__WASI_ADVICE_NORMAL => Ok(()), - _ => Err(WasiError::EINVAL), - } + Ok(()) } - fn allocate( - &self, - offset: wasi::__wasi_filesize_t, - len: wasi::__wasi_filesize_t, - ) -> WasiResult<()> { - let new_limit = offset.checked_add(len).ok_or(WasiError::EFBIG)?; + fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> { + let new_limit = offset.checked_add(len).ok_or(Errno::Fbig)?; let mut data = self.data.borrow_mut(); if new_limit > data.max_size() { - return Err(WasiError::EFBIG); + return Err(Errno::Fbig); } if new_limit > data.size() { @@ -504,16 +465,16 @@ impl VirtualFile for InMemoryFile { Ok(()) } - fn filestat_set_size(&self, st_size: wasi::__wasi_filesize_t) -> WasiResult<()> { + fn filestat_set_size(&self, st_size: types::Filesize) -> Result<()> { let mut data = self.data.borrow_mut(); if st_size > data.max_size() { - return Err(WasiError::EFBIG); + return Err(Errno::Fbig); } data.resize(st_size) } - fn filestat_get(&self) -> WasiResult { - let stat = wasi::__wasi_filestat_t { + fn filestat_get(&self) -> Result { + let stat = types::Filestat { dev: 0, ino: 0, nlink: 0, @@ -526,16 +487,16 @@ impl VirtualFile for InMemoryFile { Ok(stat) } - fn get_file_type(&self) -> wasi::__wasi_filetype_t { - wasi::__WASI_FILETYPE_REGULAR_FILE + fn get_file_type(&self) -> types::Filetype { + types::Filetype::RegularFile } - fn get_rights_base(&self) -> wasi::__wasi_rights_t { - wasi::RIGHTS_REGULAR_FILE_BASE + fn get_rights_base(&self) -> types::Rights { + types::Rights::regular_file_base() } - fn get_rights_inheriting(&self) -> wasi::__wasi_rights_t { - wasi::RIGHTS_REGULAR_FILE_INHERITING + fn get_rights_inheriting(&self) -> types::Rights { + types::Rights::regular_file_inheriting() } } @@ -610,9 +571,9 @@ impl VirtualFile for VirtualDir { })) } - fn readlinkat(&self, _path: &Path) -> WasiResult { - // Files are not symbolic links or directories, faithfully report ENOTDIR. - Err(WasiError::ENOTDIR) + fn readlinkat(&self, _path: &Path) -> Result { + // Files are not symbolic links or directories, faithfully report Notdir. + Err(Errno::Notdir) } fn openat( @@ -620,9 +581,9 @@ impl VirtualFile for VirtualDir { path: &Path, read: bool, write: bool, - oflags: wasi::__wasi_oflags_t, - fd_flags: wasi::__wasi_fdflags_t, - ) -> WasiResult> { + oflags: types::Oflags, + fd_flags: types::Fdflags, + ) -> Result> { log::trace!( "VirtualDir::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}", path, @@ -647,27 +608,27 @@ impl VirtualFile for VirtualDir { // openat may have been passed a path with a trailing slash, but files are mapped to paths // with trailing slashes normalized out. - let file_name = path.file_name().ok_or(WasiError::EINVAL)?; + let file_name = path.file_name().ok_or(Errno::Inval)?; let mut entries = self.entries.borrow_mut(); let entry_count = entries.len(); match entries.entry(Path::new(file_name).to_path_buf()) { Entry::Occupied(e) => { - let creat_excl_mask = wasi::__WASI_OFLAGS_CREAT | wasi::__WASI_OFLAGS_EXCL; + let creat_excl_mask = types::Oflags::CREAT | types::Oflags::EXCL; if (oflags & creat_excl_mask) == creat_excl_mask { log::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name); - log::trace!(" return EEXIST"); - return Err(WasiError::EEXIST); + log::trace!(" return Exist"); + return Err(Errno::Exist); } - if (oflags & wasi::__WASI_OFLAGS_DIRECTORY) != 0 - && e.get().get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY + if oflags.contains(&types::Oflags::DIRECTORY) + && e.get().get_file_type() != types::Filetype::Directory { log::trace!( "VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.", file_name ); - log::trace!(" return ENOTDIR"); - return Err(WasiError::ENOTDIR); + log::trace!(" return Notdir"); + return Err(Errno::Notdir); } e.get().try_clone().map_err(Into::into) @@ -679,7 +640,7 @@ impl VirtualFile for VirtualDir { // would have with `usize`. The limit is the full `u32` range minus two so we // can reserve "self" and "parent" cookie values. if entry_count >= (std::u32::MAX - RESERVED_ENTRY_COUNT) as usize { - return Err(WasiError::ENOSPC); + return Err(Errno::Nospc); } log::trace!( @@ -692,26 +653,26 @@ impl VirtualFile for VirtualDir { file.set_parent(Some(self.try_clone().expect("can clone self"))); v.insert(file).try_clone().map_err(Into::into) } else { - Err(WasiError::EACCES) + Err(Errno::Acces) } } } } - fn remove_directory(&self, path: &str) -> WasiResult<()> { + fn remove_directory(&self, path: &str) -> Result<()> { let trimmed_path = path.trim_end_matches('/'); let mut entries = self.entries.borrow_mut(); match entries.entry(Path::new(trimmed_path).to_path_buf()) { Entry::Occupied(e) => { // first, does this name a directory? - if e.get().get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY { - return Err(WasiError::ENOTDIR); + if e.get().get_file_type() != types::Filetype::Directory { + return Err(Errno::Notdir); } // Okay, but is the directory empty? - let iter = e.get().readdir(wasi::__WASI_DIRCOOKIE_START)?; + let iter = e.get().readdir(wasi::DIRCOOKIE_START)?; if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() { - return Err(WasiError::ENOTEMPTY); + return Err(Errno::Notempty); } // Alright, it's an empty directory. We can remove it. @@ -727,27 +688,27 @@ impl VirtualFile for VirtualDir { "VirtualDir::remove_directory failed to remove {}, no such entry", trimmed_path ); - Err(WasiError::ENOENT) + Err(Errno::Noent) } } } - fn unlink_file(&self, path: &str) -> WasiResult<()> { + fn unlink_file(&self, path: &str) -> Result<()> { let trimmed_path = path.trim_end_matches('/'); // Special case: we may be unlinking this directory itself if path is `"."`. In that case, - // fail with EISDIR, since this is a directory. Alternatively, we may be unlinking `".."`, + // fail with Isdir, since this is a directory. Alternatively, we may be unlinking `".."`, // which is bound the same way, as this is by definition contained in a directory. if trimmed_path == "." || trimmed_path == ".." { - return Err(WasiError::EISDIR); + return Err(Errno::Isdir); } let mut entries = self.entries.borrow_mut(); match entries.entry(Path::new(trimmed_path).to_path_buf()) { Entry::Occupied(e) => { // Directories must be removed through `remove_directory`, not `unlink_file`. - if e.get().get_file_type() == wasi::__WASI_FILETYPE_DIRECTORY { - return Err(WasiError::EISDIR); + if e.get().get_file_type() == types::Filetype::Directory { + return Err(Errno::Isdir); } let removed = e.remove_entry(); @@ -762,15 +723,15 @@ impl VirtualFile for VirtualDir { "VirtualDir::unlink_file failed to remove {}, no such entry", trimmed_path ); - Err(WasiError::ENOENT) + Err(Errno::Noent) } } } - fn create_directory(&self, path: &Path) -> WasiResult<()> { + fn create_directory(&self, path: &Path) -> Result<()> { let mut entries = self.entries.borrow_mut(); match entries.entry(path.to_owned()) { - Entry::Occupied(_) => Err(WasiError::EEXIST), + Entry::Occupied(_) => Err(Errno::Exist), Entry::Vacant(v) => { if self.writable { let new_dir = Box::new(Self::new(true)); @@ -778,48 +739,50 @@ impl VirtualFile for VirtualDir { v.insert(new_dir); Ok(()) } else { - Err(WasiError::EACCES) + Err(Errno::Acces) } } } } - fn write_vectored(&mut self, _iovs: &[io::IoSlice]) -> WasiResult { - Err(WasiError::EBADF) + fn write_vectored(&mut self, _iovs: &[io::IoSlice]) -> Result { + Err(Errno::Badf) } fn readdir( &self, - cookie: wasi::__wasi_dircookie_t, - ) -> WasiResult>>> { + cookie: types::Dircookie, + ) -> Result>>> { struct VirtualDirIter { start: u32, entries: Rc>>>, } impl Iterator for VirtualDirIter { - type Item = WasiResult; + type Item = Result<(types::Dirent, String)>; fn next(&mut self) -> Option { log::trace!("VirtualDirIter::next continuing from {}", self.start); if self.start == SELF_DIR_COOKIE { self.start += 1; - return Some(Ok(Dirent { - name: ".".to_owned(), - ftype: FileType::from_wasi(wasi::__WASI_FILETYPE_DIRECTORY) - .expect("directories are valid file types"), - ino: 0, - cookie: self.start as u64, - })); + let name = ".".to_owned(); + let dirent = types::Dirent { + d_next: self.start as u64, + d_ino: 0, + d_namlen: name.len() as _, + d_type: types::Filetype::Directory, + }; + return Some(Ok((dirent, name))); } if self.start == PARENT_DIR_COOKIE { self.start += 1; - return Some(Ok(Dirent { - name: "..".to_owned(), - ftype: FileType::from_wasi(wasi::__WASI_FILETYPE_DIRECTORY) - .expect("directories are valid file types"), - ino: 0, - cookie: self.start as u64, - })); + let name = "..".to_owned(); + let dirent = types::Dirent { + d_next: self.start as u64, + d_ino: 0, + d_namlen: name.len() as _, + d_type: types::Filetype::Directory, + }; + return Some(Ok((dirent, name))); } let entries = self.entries.borrow(); @@ -838,18 +801,20 @@ impl VirtualFile for VirtualDir { .next() .expect("seeked less than the length of entries"); - let entry = Dirent { - name: path - .to_str() - .expect("wasi paths are valid utf8 strings") - .to_owned(), - ftype: FileType::from_wasi(file.get_file_type()) - .expect("virtfs reports valid wasi file types"), - ino: 0, - cookie: self.start as u64, + let name = path + .to_str() + .expect("wasi paths are valid utf8 strings") + .to_owned(); + let dirent = || -> Result { + let dirent = types::Dirent { + d_namlen: name.len().try_into()?, + d_type: file.get_file_type(), + d_ino: 0, + d_next: self.start as u64, + }; + Ok(dirent) }; - - Some(Ok(entry)) + Some(dirent().map(|dirent| (dirent, name))) } } let cookie = match cookie.try_into() { @@ -866,8 +831,8 @@ impl VirtualFile for VirtualDir { })) } - fn filestat_get(&self) -> WasiResult { - let stat = wasi::__wasi_filestat_t { + fn filestat_get(&self) -> Result { + let stat = types::Filestat { dev: 0, ino: 0, nlink: 0, @@ -880,15 +845,15 @@ impl VirtualFile for VirtualDir { Ok(stat) } - fn get_file_type(&self) -> wasi::__wasi_filetype_t { - wasi::__WASI_FILETYPE_DIRECTORY + fn get_file_type(&self) -> types::Filetype { + types::Filetype::Directory } - fn get_rights_base(&self) -> wasi::__wasi_rights_t { - wasi::RIGHTS_DIRECTORY_BASE + fn get_rights_base(&self) -> types::Rights { + types::Rights::directory_base() } - fn get_rights_inheriting(&self) -> wasi::__wasi_rights_t { - wasi::RIGHTS_DIRECTORY_INHERITING + fn get_rights_inheriting(&self) -> types::Rights { + types::Rights::directory_inheriting() } } diff --git a/crates/wasi-common/src/wasi.rs b/crates/wasi-common/src/wasi.rs index deb579e24d..89ad1fe954 100644 --- a/crates/wasi-common/src/wasi.rs +++ b/crates/wasi-common/src/wasi.rs @@ -1,237 +1,217 @@ //! Types and constants shared between 32-bit and 64-bit wasi. Types involving //! pointer or `usize`-sized data are excluded here, so this file only contains //! fixed-size types, so it's host/target independent. +use crate::WasiCtx; -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(dead_code)] +wiggle::from_witx!({ + witx: ["wig/WASI/phases/snapshot/witx/wasi_snapshot_preview1.witx"], + ctx: WasiCtx, +}); -use wig::witx_wasi_types; +pub use types::Errno; +pub type Result = std::result::Result; -witx_wasi_types!("snapshot" "wasi_snapshot_preview1"); +impl<'a> wiggle_runtime::GuestErrorType<'a> for Errno { + type Context = WasiCtx; -pub type WasiResult = Result; + fn success() -> Self { + Self::Success + } -#[derive(Clone, Copy, Debug, thiserror::Error, Eq, PartialEq)] -#[repr(u16)] -#[error("{:?} ({})", self, strerror(*self as __wasi_errno_t))] -pub enum WasiError { - ESUCCESS = __WASI_ERRNO_SUCCESS, - E2BIG = __WASI_ERRNO_2BIG, - EACCES = __WASI_ERRNO_ACCES, - EADDRINUSE = __WASI_ERRNO_ADDRINUSE, - EADDRNOTAVAIL = __WASI_ERRNO_ADDRNOTAVAIL, - EAFNOSUPPORT = __WASI_ERRNO_AFNOSUPPORT, - EAGAIN = __WASI_ERRNO_AGAIN, - EALREADY = __WASI_ERRNO_ALREADY, - EBADF = __WASI_ERRNO_BADF, - EBADMSG = __WASI_ERRNO_BADMSG, - EBUSY = __WASI_ERRNO_BUSY, - ECANCELED = __WASI_ERRNO_CANCELED, - ECHILD = __WASI_ERRNO_CHILD, - ECONNABORTED = __WASI_ERRNO_CONNABORTED, - ECONNREFUSED = __WASI_ERRNO_CONNREFUSED, - ECONNRESET = __WASI_ERRNO_CONNRESET, - EDEADLK = __WASI_ERRNO_DEADLK, - EDESTADDRREQ = __WASI_ERRNO_DESTADDRREQ, - EDOM = __WASI_ERRNO_DOM, - EDQUOT = __WASI_ERRNO_DQUOT, - EEXIST = __WASI_ERRNO_EXIST, - EFAULT = __WASI_ERRNO_FAULT, - EFBIG = __WASI_ERRNO_FBIG, - EHOSTUNREACH = __WASI_ERRNO_HOSTUNREACH, - EIDRM = __WASI_ERRNO_IDRM, - EILSEQ = __WASI_ERRNO_ILSEQ, - EINPROGRESS = __WASI_ERRNO_INPROGRESS, - EINTR = __WASI_ERRNO_INTR, - EINVAL = __WASI_ERRNO_INVAL, - EIO = __WASI_ERRNO_IO, - EISCONN = __WASI_ERRNO_ISCONN, - EISDIR = __WASI_ERRNO_ISDIR, - ELOOP = __WASI_ERRNO_LOOP, - EMFILE = __WASI_ERRNO_MFILE, - EMLINK = __WASI_ERRNO_MLINK, - EMSGSIZE = __WASI_ERRNO_MSGSIZE, - EMULTIHOP = __WASI_ERRNO_MULTIHOP, - ENAMETOOLONG = __WASI_ERRNO_NAMETOOLONG, - ENETDOWN = __WASI_ERRNO_NETDOWN, - ENETRESET = __WASI_ERRNO_NETRESET, - ENETUNREACH = __WASI_ERRNO_NETUNREACH, - ENFILE = __WASI_ERRNO_NFILE, - ENOBUFS = __WASI_ERRNO_NOBUFS, - ENODEV = __WASI_ERRNO_NODEV, - ENOENT = __WASI_ERRNO_NOENT, - ENOEXEC = __WASI_ERRNO_NOEXEC, - ENOLCK = __WASI_ERRNO_NOLCK, - ENOLINK = __WASI_ERRNO_NOLINK, - ENOMEM = __WASI_ERRNO_NOMEM, - ENOMSG = __WASI_ERRNO_NOMSG, - ENOPROTOOPT = __WASI_ERRNO_NOPROTOOPT, - ENOSPC = __WASI_ERRNO_NOSPC, - ENOSYS = __WASI_ERRNO_NOSYS, - ENOTCONN = __WASI_ERRNO_NOTCONN, - ENOTDIR = __WASI_ERRNO_NOTDIR, - ENOTEMPTY = __WASI_ERRNO_NOTEMPTY, - ENOTRECOVERABLE = __WASI_ERRNO_NOTRECOVERABLE, - ENOTSOCK = __WASI_ERRNO_NOTSOCK, - ENOTSUP = __WASI_ERRNO_NOTSUP, - ENOTTY = __WASI_ERRNO_NOTTY, - ENXIO = __WASI_ERRNO_NXIO, - EOVERFLOW = __WASI_ERRNO_OVERFLOW, - EOWNERDEAD = __WASI_ERRNO_OWNERDEAD, - EPERM = __WASI_ERRNO_PERM, - EPIPE = __WASI_ERRNO_PIPE, - EPROTO = __WASI_ERRNO_PROTO, - EPROTONOSUPPORT = __WASI_ERRNO_PROTONOSUPPORT, - EPROTOTYPE = __WASI_ERRNO_PROTOTYPE, - ERANGE = __WASI_ERRNO_RANGE, - EROFS = __WASI_ERRNO_ROFS, - ESPIPE = __WASI_ERRNO_SPIPE, - ESRCH = __WASI_ERRNO_SRCH, - ESTALE = __WASI_ERRNO_STALE, - ETIMEDOUT = __WASI_ERRNO_TIMEDOUT, - ETXTBSY = __WASI_ERRNO_TXTBSY, - EXDEV = __WASI_ERRNO_XDEV, - ENOTCAPABLE = __WASI_ERRNO_NOTCAPABLE, -} - -impl WasiError { - pub fn as_raw_errno(self) -> __wasi_errno_t { - self as __wasi_errno_t + fn from_error(e: wiggle_runtime::GuestError, _ctx: &Self::Context) -> Self { + eprintln!("Guest error: {:?}", e); + // TODO proper error mapping + Self::Inval } } -impl From for WasiError { +impl From for Errno { + fn from(err: wiggle_runtime::GuestError) -> Self { + use wiggle_runtime::GuestError::*; + match err { + InvalidFlagValue { .. } => Self::Inval, + InvalidEnumValue { .. } => Self::Inval, + PtrOverflow { .. } => Self::Fault, + PtrOutOfBounds { .. } => Self::Fault, + PtrNotAligned { .. } => Self::Inval, + PtrBorrowed { .. } => Self::Fault, + InvalidUtf8 { .. } => Self::Ilseq, + TryFromIntError { .. } => Self::Overflow, + InFunc { .. } => Self::Inval, + InDataField { .. } => Self::Inval, + SliceLengthsDiffer { .. } => Self::Fault, + } + } +} + +impl From for Errno { fn from(_err: std::convert::Infallible) -> Self { unreachable!() } } -impl From for WasiError { +impl From for Errno { fn from(_err: std::num::TryFromIntError) -> Self { - Self::EOVERFLOW + Self::Overflow } } -impl From for WasiError { +impl From for Errno { fn from(_err: std::str::Utf8Error) -> Self { - Self::EILSEQ + Self::Ilseq } } -pub(crate) const RIGHTS_ALL: __wasi_rights_t = __WASI_RIGHTS_FD_DATASYNC - | __WASI_RIGHTS_FD_READ - | __WASI_RIGHTS_FD_SEEK - | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHTS_FD_SYNC - | __WASI_RIGHTS_FD_TELL - | __WASI_RIGHTS_FD_WRITE - | __WASI_RIGHTS_FD_ADVISE - | __WASI_RIGHTS_FD_ALLOCATE - | __WASI_RIGHTS_PATH_CREATE_DIRECTORY - | __WASI_RIGHTS_PATH_CREATE_FILE - | __WASI_RIGHTS_PATH_LINK_SOURCE - | __WASI_RIGHTS_PATH_LINK_TARGET - | __WASI_RIGHTS_PATH_OPEN - | __WASI_RIGHTS_FD_READDIR - | __WASI_RIGHTS_PATH_READLINK - | __WASI_RIGHTS_PATH_RENAME_SOURCE - | __WASI_RIGHTS_PATH_RENAME_TARGET - | __WASI_RIGHTS_PATH_FILESTAT_GET - | __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE - | __WASI_RIGHTS_PATH_FILESTAT_SET_TIMES - | __WASI_RIGHTS_FD_FILESTAT_GET - | __WASI_RIGHTS_FD_FILESTAT_SET_SIZE - | __WASI_RIGHTS_FD_FILESTAT_SET_TIMES - | __WASI_RIGHTS_PATH_SYMLINK - | __WASI_RIGHTS_PATH_UNLINK_FILE - | __WASI_RIGHTS_PATH_REMOVE_DIRECTORY - | __WASI_RIGHTS_POLL_FD_READWRITE - | __WASI_RIGHTS_SOCK_SHUTDOWN; - -// Block and character device interaction is outside the scope of -// WASI. Simply allow everything. -pub(crate) const RIGHTS_BLOCK_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL; -pub(crate) const RIGHTS_BLOCK_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL; -pub(crate) const RIGHTS_CHARACTER_DEVICE_BASE: __wasi_rights_t = RIGHTS_ALL; -pub(crate) const RIGHTS_CHARACTER_DEVICE_INHERITING: __wasi_rights_t = RIGHTS_ALL; - -// Only allow directory operations on directories. Directories can only -// yield file descriptors to other directories and files. -pub(crate) const RIGHTS_DIRECTORY_BASE: __wasi_rights_t = __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHTS_FD_SYNC - | __WASI_RIGHTS_FD_ADVISE - | __WASI_RIGHTS_PATH_CREATE_DIRECTORY - | __WASI_RIGHTS_PATH_CREATE_FILE - | __WASI_RIGHTS_PATH_LINK_SOURCE - | __WASI_RIGHTS_PATH_LINK_TARGET - | __WASI_RIGHTS_PATH_OPEN - | __WASI_RIGHTS_FD_READDIR - | __WASI_RIGHTS_PATH_READLINK - | __WASI_RIGHTS_PATH_RENAME_SOURCE - | __WASI_RIGHTS_PATH_RENAME_TARGET - | __WASI_RIGHTS_PATH_FILESTAT_GET - | __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE - | __WASI_RIGHTS_PATH_FILESTAT_SET_TIMES - | __WASI_RIGHTS_FD_FILESTAT_GET - | __WASI_RIGHTS_FD_FILESTAT_SET_TIMES - | __WASI_RIGHTS_PATH_SYMLINK - | __WASI_RIGHTS_PATH_UNLINK_FILE - | __WASI_RIGHTS_PATH_REMOVE_DIRECTORY - | __WASI_RIGHTS_POLL_FD_READWRITE; -pub(crate) const RIGHTS_DIRECTORY_INHERITING: __wasi_rights_t = - RIGHTS_DIRECTORY_BASE | RIGHTS_REGULAR_FILE_BASE; - -// Operations that apply to regular files. -pub(crate) const RIGHTS_REGULAR_FILE_BASE: __wasi_rights_t = __WASI_RIGHTS_FD_DATASYNC - | __WASI_RIGHTS_FD_READ - | __WASI_RIGHTS_FD_SEEK - | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHTS_FD_SYNC - | __WASI_RIGHTS_FD_TELL - | __WASI_RIGHTS_FD_WRITE - | __WASI_RIGHTS_FD_ADVISE - | __WASI_RIGHTS_FD_ALLOCATE - | __WASI_RIGHTS_FD_FILESTAT_GET - | __WASI_RIGHTS_FD_FILESTAT_SET_SIZE - | __WASI_RIGHTS_FD_FILESTAT_SET_TIMES - | __WASI_RIGHTS_POLL_FD_READWRITE; -pub(crate) const RIGHTS_REGULAR_FILE_INHERITING: __wasi_rights_t = 0; - -// Operations that apply to sockets and socket pairs. -pub(crate) const RIGHTS_SOCKET_BASE: __wasi_rights_t = __WASI_RIGHTS_FD_READ - | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHTS_FD_WRITE - | __WASI_RIGHTS_FD_FILESTAT_GET - | __WASI_RIGHTS_POLL_FD_READWRITE - | __WASI_RIGHTS_SOCK_SHUTDOWN; -pub(crate) const RIGHTS_SOCKET_INHERITING: __wasi_rights_t = RIGHTS_ALL; - -// Operations that apply to TTYs. -pub(crate) const RIGHTS_TTY_BASE: __wasi_rights_t = __WASI_RIGHTS_FD_READ - | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS - | __WASI_RIGHTS_FD_WRITE - | __WASI_RIGHTS_FD_FILESTAT_GET - | __WASI_RIGHTS_POLL_FD_READWRITE; -#[allow(unused)] -pub(crate) const RIGHTS_TTY_INHERITING: __wasi_rights_t = 0; - -pub fn whence_to_str(whence: __wasi_whence_t) -> &'static str { - match whence { - __WASI_WHENCE_CUR => "__WASI_WHENCE_CUR", - __WASI_WHENCE_END => "__WASI_WHENCE_END", - __WASI_WHENCE_SET => "__WASI_WHENCE_SET", - other => panic!("Undefined whence value {:?}", other), +impl From for types::Filetype { + fn from(ftype: std::fs::FileType) -> Self { + if ftype.is_file() { + Self::RegularFile + } else if ftype.is_dir() { + Self::Directory + } else if ftype.is_symlink() { + Self::SymbolicLink + } else { + Self::Unknown + } } } -pub const __WASI_DIRCOOKIE_START: __wasi_dircookie_t = 0; +pub(crate) trait AsBytes { + fn as_bytes(&self) -> Result>; +} -impl crate::fdpool::Fd for __wasi_fd_t { +impl AsBytes for types::Dirent { + fn as_bytes(&self) -> Result> { + use std::convert::TryInto; + use wiggle_runtime::GuestType; + + assert_eq!( + Self::guest_size(), + std::mem::size_of::() as _, + "guest repr of types::Dirent and host repr should match" + ); + + let offset = Self::guest_size().try_into()?; + let mut bytes: Vec = Vec::with_capacity(offset); + bytes.resize(offset, 0); + let ptr = bytes.as_mut_ptr() as *mut Self; + unsafe { ptr.write_unaligned(*self) }; + Ok(bytes) + } +} + +pub(crate) trait RightsExt: Sized { + fn block_device_base() -> Self; + fn block_device_inheriting() -> Self; + fn character_device_base() -> Self; + fn character_device_inheriting() -> Self; + fn directory_base() -> Self; + fn directory_inheriting() -> Self; + fn regular_file_base() -> Self; + fn regular_file_inheriting() -> Self; + fn socket_base() -> Self; + fn socket_inheriting() -> Self; + fn tty_base() -> Self; + fn tty_inheriting() -> Self; +} + +impl RightsExt for types::Rights { + // Block and character device interaction is outside the scope of + // WASI. Simply allow everything. + fn block_device_base() -> Self { + Self::all() + } + fn block_device_inheriting() -> Self { + Self::all() + } + fn character_device_base() -> Self { + Self::all() + } + fn character_device_inheriting() -> Self { + Self::all() + } + + // Only allow directory operations on directories. Directories can only + // yield file descriptors to other directories and files. + fn directory_base() -> Self { + Self::FD_FDSTAT_SET_FLAGS + | Self::FD_SYNC + | Self::FD_ADVISE + | Self::PATH_CREATE_DIRECTORY + | Self::PATH_CREATE_FILE + | Self::PATH_LINK_SOURCE + | Self::PATH_LINK_TARGET + | Self::PATH_OPEN + | Self::FD_READDIR + | Self::PATH_READLINK + | Self::PATH_RENAME_SOURCE + | Self::PATH_RENAME_TARGET + | Self::PATH_FILESTAT_GET + | Self::PATH_FILESTAT_SET_SIZE + | Self::PATH_FILESTAT_SET_TIMES + | Self::FD_FILESTAT_GET + | Self::FD_FILESTAT_SET_TIMES + | Self::PATH_SYMLINK + | Self::PATH_UNLINK_FILE + | Self::PATH_REMOVE_DIRECTORY + | Self::POLL_FD_READWRITE + } + fn directory_inheriting() -> Self { + Self::all() ^ Self::SOCK_SHUTDOWN + } + + // Operations that apply to regular files. + fn regular_file_base() -> Self { + Self::FD_DATASYNC + | Self::FD_READ + | Self::FD_SEEK + | Self::FD_FDSTAT_SET_FLAGS + | Self::FD_SYNC + | Self::FD_TELL + | Self::FD_WRITE + | Self::FD_ADVISE + | Self::FD_ALLOCATE + | Self::FD_FILESTAT_GET + | Self::FD_FILESTAT_SET_SIZE + | Self::FD_FILESTAT_SET_TIMES + | Self::POLL_FD_READWRITE + } + fn regular_file_inheriting() -> Self { + Self::empty() + } + + // Operations that apply to sockets and socket pairs. + fn socket_base() -> Self { + Self::FD_READ + | Self::FD_FDSTAT_SET_FLAGS + | Self::FD_WRITE + | Self::FD_FILESTAT_GET + | Self::POLL_FD_READWRITE + | Self::SOCK_SHUTDOWN + } + fn socket_inheriting() -> Self { + Self::all() + } + + // Operations that apply to TTYs. + fn tty_base() -> Self { + Self::FD_READ + | Self::FD_FDSTAT_SET_FLAGS + | Self::FD_WRITE + | Self::FD_FILESTAT_GET + | Self::POLL_FD_READWRITE + } + fn tty_inheriting() -> Self { + Self::empty() + } +} +pub(crate) const DIRCOOKIE_START: types::Dircookie = 0; + +impl crate::fdpool::Fd for types::Fd { fn as_raw(&self) -> u32 { - *self + (*self).into() } fn from_raw(raw_fd: u32) -> Self { - raw_fd + Self::from(raw_fd) } } diff --git a/crates/wasi-common/src/wasi32.rs b/crates/wasi-common/src/wasi32.rs deleted file mode 100644 index 11460010c8..0000000000 --- a/crates/wasi-common/src/wasi32.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Types and constants specific to 32-bit wasi. These are similar to the types -//! in the `host` module, but pointers and `usize` values are replaced with -//! `u32`-sized types. - -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(dead_code)] - -use crate::wasi::*; -use wig::witx_wasi32_types; - -pub type uintptr_t = u32; -pub type size_t = u32; - -witx_wasi32_types!("snapshot" "wasi_snapshot_preview1"); diff --git a/crates/wasi-common/wig/src/lib.rs b/crates/wasi-common/wig/src/lib.rs index d0fd3f41e0..1a6688621b 100644 --- a/crates/wasi-common/wig/src/lib.rs +++ b/crates/wasi-common/wig/src/lib.rs @@ -38,6 +38,11 @@ pub fn define_wasi_struct(args: TokenStream) -> TokenStream { wasi::define_struct(args.into()).into() } +#[proc_macro] +pub fn define_wasi_struct_for_wiggle(args: TokenStream) -> TokenStream { + wasi::define_struct_for_wiggle(args.into()).into() +} + #[proc_macro] pub fn define_hostcalls(args: TokenStream) -> TokenStream { hostcalls::define(args.into()).into() diff --git a/crates/wasi-common/wig/src/wasi.rs b/crates/wasi-common/wig/src/wasi.rs index 14a38117d5..e49cf3cb23 100644 --- a/crates/wasi-common/wig/src/wasi.rs +++ b/crates/wasi-common/wig/src/wasi.rs @@ -195,7 +195,7 @@ pub fn define_struct(args: TokenStream) -> TokenStream { let memory = match caller.get_export("memory") { Some(wasmtime::Extern::Memory(m)) => m, _ => { - let e = wasi_common::wasi::__WASI_ERRNO_INVAL; + let e = wasi_common::old::snapshot_0::wasi::__WASI_ERRNO_INVAL; #handle_early_error } }; @@ -252,3 +252,255 @@ pub fn define_struct(args: TokenStream) -> TokenStream { } } } + +pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream { + let (path, _phase) = utils::witx_path_from_args(args); + let doc = match witx::load(&[&path]) { + Ok(doc) => doc, + Err(e) => { + panic!("error opening file {}: {}", path, e); + } + }; + + let mut fields = Vec::new(); + let mut get_exports = Vec::new(); + let mut ctor_externs = Vec::new(); + let mut ctor_fields = Vec::new(); + + for module in doc.modules() { + let module_id = Ident::new(module.name.as_str(), Span::call_site()); + for func in module.funcs() { + let name = func.name.as_str(); + let name_ident = Ident::new(func.name.as_str(), Span::call_site()); + fields.push(quote! { pub #name_ident: wasmtime::Func }); + get_exports.push(quote! { #name => Some(&self.#name_ident) }); + ctor_fields.push(name_ident.clone()); + + let mut shim_arg_decls = Vec::new(); + let mut params = Vec::new(); + let mut formats = Vec::new(); + let mut format_args = Vec::new(); + let mut hostcall_args = Vec::new(); + + for param in func.params.iter() { + let name = utils::param_name(param); + + // Registers a new parameter to the shim we're making with the + // given `name`, the `abi_ty` wasm type and `hex` defines + // whether it's debug-printed in a hex format or not. + // + // This will register a whole bunch of things: + // + // * The cranelift type for the parameter + // * Syntax to specify the actual function parameter + // * How to log the parameter value in a call to `trace!` + // * How to actually pass this argument to the host + // implementation, converting as necessary. + let mut add_param = |name: &Ident, abi_ty: Abi, hex: bool| { + match abi_ty { + Abi::I32 => { + params.push(quote! { types::I32 }); + shim_arg_decls.push(quote! { #name: i32 }); + } + Abi::I64 => { + params.push(quote! { types::I64 }); + shim_arg_decls.push(quote! { #name: i64 }); + } + Abi::F32 => { + params.push(quote! { types::F32 }); + shim_arg_decls.push(quote! { #name: f32 }); + } + Abi::F64 => { + params.push(quote! { types::F64 }); + shim_arg_decls.push(quote! { #name: f64 }); + } + } + formats.push(format!("{}={}", name, if hex { "{:#x}" } else { "{}" },)); + format_args.push(name.clone()); + hostcall_args.push(quote! { #name as _ }); + }; + + match &*param.tref.type_() { + witx::Type::Int(e) => match e.repr { + witx::IntRepr::U64 => add_param(&name, Abi::I64, false), + _ => add_param(&name, Abi::I32, false), + }, + + witx::Type::Enum(e) => match e.repr { + witx::IntRepr::U64 => add_param(&name, Abi::I64, false), + _ => add_param(&name, Abi::I32, false), + }, + + witx::Type::Flags(f) => match f.repr { + witx::IntRepr::U64 => add_param(&name, Abi::I64, true), + _ => add_param(&name, Abi::I32, true), + }, + + witx::Type::Builtin(witx::BuiltinType::Char8) + | witx::Type::Builtin(witx::BuiltinType::S8) + | witx::Type::Builtin(witx::BuiltinType::U8) + | witx::Type::Builtin(witx::BuiltinType::S16) + | witx::Type::Builtin(witx::BuiltinType::U16) + | witx::Type::Builtin(witx::BuiltinType::S32) + | witx::Type::Builtin(witx::BuiltinType::U32) + | witx::Type::Builtin(witx::BuiltinType::USize) => { + add_param(&name, Abi::I32, false); + } + + witx::Type::Builtin(witx::BuiltinType::S64) + | witx::Type::Builtin(witx::BuiltinType::U64) => { + add_param(&name, Abi::I64, false); + } + + witx::Type::Builtin(witx::BuiltinType::F32) => { + add_param(&name, Abi::F32, false); + } + + witx::Type::Builtin(witx::BuiltinType::F64) => { + add_param(&name, Abi::F64, false); + } + + // strings/arrays have an extra ABI parameter for the length + // of the array passed. + witx::Type::Builtin(witx::BuiltinType::String) | witx::Type::Array(_) => { + add_param(&name, Abi::I32, true); + let len = format_ident!("{}_len", name); + add_param(&len, Abi::I32, false); + } + + witx::Type::ConstPointer(_) + | witx::Type::Handle(_) + | witx::Type::Pointer(_) => { + add_param(&name, Abi::I32, true); + } + + witx::Type::Struct(_) | witx::Type::Union(_) => { + panic!("unsupported argument type") + } + } + } + + let mut results = func.results.iter(); + let mut ret_ty = quote! { () }; + let mut cvt_ret = quote! {}; + let mut returns = Vec::new(); + let mut handle_early_error = quote! { panic!("error: {:?}", e) }; + + // The first result is returned bare right now... + if let Some(ret) = results.next() { + handle_early_error = quote! { return e.into() }; + match &*ret.tref.type_() { + // Eventually we'll want to add support for more returned + // types, but for now let's just conform to what `*.witx` + // definitions currently use. + witx::Type::Enum(e) => match e.repr { + witx::IntRepr::U16 => { + returns.push(quote! { types::I32 }); + ret_ty = quote! { i32 }; + cvt_ret = quote! { .into() } + } + other => panic!("unsupported ret enum repr {:?}", other), + }, + other => panic!("unsupported first return {:?}", other), + } + } + + // ... and all remaining results are returned via out-poiners + for result in results { + let name = format_ident!("{}", result.name.as_str()); + params.push(quote! { types::I32 }); + shim_arg_decls.push(quote! { #name: i32 }); + formats.push(format!("{}={{:#x}}", name)); + format_args.push(name.clone()); + hostcall_args.push(quote! { #name }); + } + + let format_str = format!("{}({})", name, formats.join(", ")); + ctor_externs.push(quote! { + let my_cx = cx.clone(); + let #name_ident = wasmtime::Func::wrap( + store, + move |caller: wasmtime::Caller<'_> #(,#shim_arg_decls)*| -> #ret_ty { + log::trace!( + #format_str, + #(#format_args),* + ); + unsafe { + let mem = match caller.get_export("memory") { + Some(wasmtime::Extern::Memory(m)) => m, + _ => { + let e = wasi_common::wasi::Errno::Inval; + #handle_early_error + } + }; + let mem: WasiMemory = mem.into(); + wasi_common::wasi::#module_id::#name_ident( + &mut my_cx.borrow_mut(), + &mem, + #(#hostcall_args),* + ) #cvt_ret + } + } + ); + }); + } + } + + quote! { + /// Lightweight `wasmtime::Memory` wrapper so that we can + /// implement `wiggle_runtime::GuestMemory` trait on it which is + /// now required to interface with `wasi-common`. + struct WasiMemory(wasmtime::Memory); + + impl From for WasiMemory { + fn from(mem: wasmtime::Memory) -> Self { + Self(mem) + } + } + + unsafe impl wiggle_runtime::GuestMemory for WasiMemory { + fn base(&self) -> (*mut u8, u32) { + (self.0.data_ptr(), self.0.data_size() as _) + } + } + + /// An instantiated instance of the wasi exports. + /// + /// This represents a wasi module which can be used to instantiate other + /// wasm modules. This structure exports all that various fields of the + /// wasi instance as fields which can be used to implement your own + /// instantiation logic, if necessary. Additionally [`Wasi::get_export`] + /// can be used to do name-based resolution. + pub struct Wasi { + #(#fields,)* + } + + impl Wasi { + /// Creates a new [`Wasi`] instance. + /// + /// External values are allocated into the `store` provided and + /// configuration of the wasi instance itself should be all + /// contained in the `cx` parameter. + pub fn new(store: &wasmtime::Store, cx: WasiCtx) -> Wasi { + let cx = std::rc::Rc::new(std::cell::RefCell::new(cx)); + #(#ctor_externs)* + + Wasi { + #(#ctor_fields,)* + } + } + + /// Looks up a field called `name` in this structure, returning it + /// if found. + /// + /// This is often useful when instantiating a `wasmtime` instance + /// where name resolution often happens with strings. + pub fn get_export(&self, name: &str) -> Option<&wasmtime::Func> { + match name { + #(#get_exports,)* + _ => None, + } + } + } + } +} diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 3a84c96bcb..408d8d81f7 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -17,6 +17,7 @@ wasi-common = { path = "../wasi-common", version = "0.12.0" } wasmtime = { path = "../api", version = "0.12.0", default-features = false } wasmtime-runtime = { path = "../runtime", version = "0.12.0" } wig = { path = "../wasi-common/wig", version = "0.12.0" } +wiggle-runtime = { path = "../wiggle/crates/runtime" } [badges] maintenance = { status = "actively-developed" } diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index bfa75c248d..d58cdd4ac1 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -1,12 +1,10 @@ pub mod old; -use wasi_common::hostcalls; - pub use wasi_common::{WasiCtx, WasiCtxBuilder}; // Defines a `struct Wasi` with member fields and appropriate APIs for dealing // with all the various WASI exports. -wig::define_wasi_struct!( +wig::define_wasi_struct_for_wiggle!( "snapshot" "wasi_snapshot_preview1" ); diff --git a/crates/wiggle/crates/generate/src/types/handle.rs b/crates/wiggle/crates/generate/src/types/handle.rs index 4f922cbd7b..52e0d2501c 100644 --- a/crates/wiggle/crates/generate/src/types/handle.rs +++ b/crates/wiggle/crates/generate/src/types/handle.rs @@ -17,6 +17,12 @@ pub(super) fn define_handle( #[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)] pub struct #ident(u32); + impl #ident { + pub unsafe fn inner(&self) -> u32 { + self.0 + } + } + impl From<#ident> for u32 { fn from(e: #ident) -> u32 { e.0