diff --git a/Cargo.lock b/Cargo.lock index 7c4f4fb10640..df3f6926a5e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2339,6 +2339,8 @@ dependencies = [ "num", "thiserror", "wig", + "wiggle", + "wiggle-runtime", "winapi", "winx", "yanix", @@ -2610,6 +2612,7 @@ dependencies = [ "wasmtime", "wasmtime-runtime", "wig", + "wiggle-runtime", ] [[package]] diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index 073bb7d72939..a978e37ca5ca 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 000000000000..fdf9dd2792ba --- /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 18b9467cb1ca..25def582c25a 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; @@ -323,7 +325,7 @@ impl WasiCtxBuilder { .collect::>>()?; let mut fd_pool = FdPool::new(); - let mut entries: HashMap = HashMap::new(); + let mut entries: HashMap = HashMap::new(); // Populate the non-preopen entries. for pending in vec![ self.stdin.take().unwrap(), @@ -358,7 +360,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)); } } @@ -377,16 +379,16 @@ impl WasiCtxBuilder { Ok(WasiCtx { args, env, - entries, - fd_pool, + entries: RefCell::new(entries), + fd_pool: RefCell::new(fd_pool), }) } } #[derive(Debug)] pub struct WasiCtx { - fd_pool: FdPool, - entries: HashMap, + fd_pool: RefCell, + entries: RefCell>, pub(crate) args: Vec, pub(crate) env: Vec, } @@ -408,42 +410,52 @@ 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) unsafe fn contains_entry(&self, fd: types::Fd) -> bool { + self.entries.borrow().contains_key(&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) unsafe 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) + pub(crate) unsafe 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); + pub(crate) fn insert_entry(&self, fe: Entry) -> Result { + let fd = self.fd_pool.borrow_mut().allocate().ok_or(Errno::Mfile)?; + self.entries.borrow_mut().insert(fd, fe); Ok(fd) } /// 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, fe: Entry) -> Option { + self.entries.borrow_mut().insert(fd, fe) } /// 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 { + pub(crate) fn remove_entry(&self, fd: types::Fd) -> Result { // Remove the `fd` from valid entries. - let entry = self.entries.remove(&fd).ok_or(WasiError::EBADF)?; + let entry = self.entries.borrow_mut().remove(&fd).ok_or(Errno::Badf)?; // Next, deallocate the `fd`. - self.fd_pool.deallocate(fd); + self.fd_pool.borrow_mut().deallocate(fd); Ok(entry) } } diff --git a/crates/wasi-common/src/entry.rs b/crates/wasi-common/src/entry.rs index 5b07667d13ef..d86272b15547 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}; @@ -18,58 +19,56 @@ pub(crate) enum Descriptor { impl From for Descriptor { fn from(handle: OsHandle) -> Self { - Descriptor::OsHandle(handle) + Self::OsHandle(handle) } } impl From> for Descriptor { fn from(virt: Box) -> Self { - Descriptor::VirtualFile(virt) + Self::VirtualFile(virt) } } impl fmt::Debug for Descriptor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Descriptor::OsHandle(handle) => write!(f, "{:?}", handle), - Descriptor::VirtualFile(_) => write!(f, "VirtualFile"), - Descriptor::Stdin => write!(f, "Stdin"), - Descriptor::Stdout => write!(f, "Stdout"), - Descriptor::Stderr => write!(f, "Stderr"), + Self::OsHandle(handle) => write!(f, "{:?}", handle), + Self::VirtualFile(_) => write!(f, "VirtualFile"), + Self::Stdin => write!(f, "Stdin"), + Self::Stdout => write!(f, "Stdout"), + Self::Stderr => write!(f, "Stderr"), } } } impl Descriptor { - pub(crate) fn try_clone(&self) -> io::Result { + pub(crate) fn try_clone(&self) -> io::Result { match self { - Descriptor::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into()), - Descriptor::VirtualFile(virt) => virt.try_clone().map(Descriptor::VirtualFile), - Descriptor::Stdin => Ok(Descriptor::Stdin), - Descriptor::Stdout => Ok(Descriptor::Stdout), - Descriptor::Stderr => Ok(Descriptor::Stderr), + Self::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into()), + Self::VirtualFile(virt) => virt.try_clone().map(Self::VirtualFile), + Self::Stdin => Ok(Self::Stdin), + Self::Stdout => Ok(Self::Stdout), + Self::Stderr => Ok(Self::Stderr), } } /// 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 Descriptor> { + 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 Descriptor> { + 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), } } @@ -89,10 +88,10 @@ impl Descriptor { /// 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 } @@ -173,12 +172,12 @@ impl Entry { /// The `FdEntry` 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. + /// `FdEntry::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) } @@ -189,12 +188,12 @@ impl Entry { /// The `FdEntry` 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. + /// `FdEntry::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) } @@ -203,20 +202,20 @@ impl Entry { /// inheriting rights `rights_inheriting`; i.e., if rights attached to this `FdEntry` 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 000000000000..05d4706f42b4 --- /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 0a543b4a1511..5c2329669926 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 d8b0dbf71740..9382804052f8 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 4a5ce7196c1d..1e9ec229aa30 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 4fb365882b3e..000000000000 --- 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 56598b17a223..000000000000 --- 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 ecedc67b71f1..000000000000 --- a/crates/wasi-common/src/hostcalls_impl/fs.rs +++ /dev/null @@ -1,1279 +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, - )?; - - hostcalls_impl::path_link(resolved_old, resolved_new) -} - -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 9dc99db60925..000000000000 --- 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 924c8c1ba97b..000000000000 --- 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 6e133bb23531..000000000000 --- 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 3228dd8bf41e..2ba471d02b09 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 a45dc537ae4a..000000000000 --- 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/wasi.rs b/crates/wasi-common/src/old/snapshot_0/wasi.rs index 966fed9bbd5f..eb039d0013f5 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 9869f83a24ec..5c692b926424 100644 --- a/crates/wasi-common/src/hostcalls_impl/fs_helpers.rs +++ b/crates/wasi-common/src/path.rs @@ -1,10 +1,11 @@ -#![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; + +pub(crate) use sys::path::*; #[derive(Debug)] pub(crate) struct PathGet { @@ -21,11 +22,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 +36,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 +63,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 +73,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 +83,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 +99,24 @@ 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, + rights_base: types::Rights, + rights_inheriting: types::Rights, + dirflags: types::Lookupflags, path: &str, needs_final_component: bool, -) -> WasiResult { +) -> Result { const MAX_SYMLINK_EXPANSIONS: usize = 128; 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 +147,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 +165,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 +231,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 +257,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 +272,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 +282,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 000000000000..3842534daed5 --- /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 000000000000..cd093e101363 --- /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 000000000000..e3973de268a4 --- /dev/null +++ b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs @@ -0,0 +1,1448 @@ +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); + + let mut argv_buf_offset: types::Size = 0; + let mut bc = GuestBorrows::new(); + + for arg in &self.args { + let arg_bytes = arg.as_bytes_with_nul(); + let elems = arg_bytes.len().try_into()?; + let as_arr = argv_buf.as_array(elems); + let raw_ptr = as_arr.as_raw(&mut bc)?; + unsafe { &mut *raw_ptr }.copy_from_slice(arg_bytes); + + argv.write(argv_buf)?; + + argv = argv.add(1)?; + argv_buf_offset = argv_buf_offset.checked_add(elems).ok_or(Errno::Overflow)?; + argv_buf = argv_buf.add(argv_buf_offset)?; + } + + 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 + ); + + let mut environ_buf_offset: types::Size = 0; + let mut bc = GuestBorrows::new(); + + for e in &self.env { + let environ_bytes = e.as_bytes_with_nul(); + let elems = environ_bytes.len().try_into()?; + let as_arr = environ_buf.as_array(elems); + let raw_ptr = as_arr.as_raw(&mut bc)?; + unsafe { &mut *raw_ptr }.copy_from_slice(environ_bytes); + + environ.write(environ_buf)?; + + environ = environ.add(1)?; + environ_buf_offset = environ_buf_offset + .checked_add(elems) + .ok_or(Errno::Overflow)?; + environ_buf = environ_buf.add(environ_buf_offset)?; + } + + 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 = unsafe { 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 = unsafe { 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) = unsafe { 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 = unsafe { 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 = unsafe { 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 = unsafe { 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 = unsafe { 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 = unsafe { 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 = unsafe { 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 = unsafe { 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 slices = Vec::new(); + let mut total_len: types::Size = 0; + 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 buf = iov.buf; + let buf_len = iov.buf_len; + total_len = total_len.checked_add(buf_len).ok_or(Errno::Inval)?; + let as_arr = buf.as_array(buf_len); + let as_raw = as_arr.as_raw(&mut bc)?; + slices.push(unsafe { &mut *as_raw }); + } + + let entry = unsafe { self.get_entry(fd)? }; + let file = entry + .as_descriptor( + types::Rights::FD_READ | types::Rights::FD_SEEK, + types::Rights::empty(), + )? + .as_file()?; + + if offset > i64::max_value() as u64 { + return Err(Errno::Io); + } + + let mut buf = vec![0; total_len as usize]; + let host_nread = match file { + Descriptor::OsHandle(fd) => 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 slice in slices { + if left == 0 { + break; + } + let vec_len = std::cmp::min(slice.len(), left); + slice[..vec_len].copy_from_slice(&buf[buf_offset..buf_offset + vec_len]); + buf_offset += vec_len; + left -= vec_len; + } + 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 = unsafe { 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 = unsafe { 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); + + let mut bc = GuestBorrows::new(); + let path_arr = path.as_array(host_path_len); + let slice = path_arr.as_raw(&mut bc)?; + unsafe { &mut *slice }.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 slices = Vec::new(); + let mut total_len: types::Size = 0; + 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 buf = ciov.buf; + let buf_len = ciov.buf_len; + total_len = total_len.checked_add(buf_len).ok_or(Errno::Inval)?; + let as_arr = buf.as_array(buf_len); + let as_raw = as_arr.as_raw(&mut bc)?; + slices.push(unsafe { &*as_raw }); + } + + let entry = unsafe { self.get_entry(fd)? }; + let file = entry + .as_descriptor( + types::Rights::FD_WRITE | types::Rights::FD_SEEK, + types::Rights::empty(), + )? + .as_file()?; + + if offset > i64::max_value() as u64 { + return Err(Errno::Io); + } + + let mut buf = Vec::with_capacity(total_len as usize); + for slice in &slices { + buf.extend_from_slice(slice); + } + let host_nwritten = match file { + Descriptor::OsHandle(fd) => 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); + 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<&'_ mut [u8]> = 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 base: GuestPtr = iov.buf; + let len: u32 = iov.buf_len; + let buf: GuestPtr<[u8]> = base.as_array(len); + let slice = buf.as_raw(&mut bc)?; + slices.push(unsafe { &mut *slice }); + } + let mut slices: Vec<_> = slices.into_iter().map(io::IoSliceMut::new).collect(); + + let mut entry = unsafe { 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 bc = GuestBorrows::new(); + let as_arr = buf.as_array(buf_len); + let as_raw = as_arr.as_raw(&mut bc)?; + let host_buf = unsafe { &mut *as_raw }; + + trace!(" | (buf,buf_len)={:?}", host_buf); + + let mut entry = unsafe { 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 host_buf: &mut [u8], + ) -> Result { + let mut host_bufused = 0; + for pair in iter { + let (dirent, name) = pair?; + let dirent_raw = dirent.as_bytes()?; + let offset = dirent_raw + .len() + .checked_add(name.len()) + .ok_or(Errno::Overflow)?; + if host_buf.len() < offset { + break; + } else { + host_buf[0..dirent_raw.len()].copy_from_slice(&dirent_raw); + host_buf[dirent_raw.len()..offset].copy_from_slice(name.as_bytes()); + host_bufused += offset; + host_buf = &mut host_buf[offset..]; + } + } + Ok(host_bufused) + } + let host_bufused = match file { + Descriptor::OsHandle(file) => copy_entities(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" + ); + } + }; + let host_bufused = host_bufused.try_into()?; + + trace!(" | *buf_used={:?}", host_bufused); + + Ok(host_bufused) + } + + fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<()> { + trace!("fd_renumber(from={:?}, to={:?})", from, to); + + if unsafe { !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) = unsafe { self.get_entry(from) } { + if from_fe.preopen_path.is_some() { + return Err(Errno::Notsup); + } + } + if let Ok(to_fe) = unsafe { 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 = unsafe { 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 = unsafe { 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 = unsafe { 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<&'_ [u8]> = 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 base: GuestPtr = ciov.buf; + let len: u32 = ciov.buf_len; + let buf: GuestPtr<[u8]> = base.as_array(len); + let slice = buf.as_raw(&mut bc)?; + slices.push(unsafe { &*slice }); + } + let slices: Vec<_> = slices.into_iter().map(io::IoSlice::new).collect(); + + // perform unbuffered writes + let mut entry = unsafe { 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 mut bc = GuestBorrows::new(); + let as_str = path.as_raw(&mut bc)?; + let path = unsafe { &*as_str }; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let rights = types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY; + let entry = unsafe { 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 mut bc = GuestBorrows::new(); + let as_str = path.as_raw(&mut bc)?; + let path = unsafe { &*as_str }; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let entry = unsafe { 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 mut bc = GuestBorrows::new(); + let as_str = path.as_raw(&mut bc)?; + let path = unsafe { &*as_str }; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let entry = unsafe { 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, + ); + + // FIXME + // Much like in the case with `path_symlink`, we need to account for the + // fact where both `old_path` and `new_path` point the same memory location. + // Hence, separate scopes and `GuestBorrows` instances. + let resolved_old = { + let mut bc = GuestBorrows::new(); + let old_as_str = old_path.as_raw(&mut bc)?; + let old_path = unsafe { &*old_as_str }; + + trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); + + let old_entry = unsafe { self.get_entry(old_fd)? }; + path::get( + &old_entry, + types::Rights::PATH_LINK_SOURCE, + types::Rights::empty(), + types::Lookupflags::empty(), + old_path, + false, + )? + }; + let resolved_new = { + let mut bc = GuestBorrows::new(); + let new_as_str = new_path.as_raw(&mut bc)?; + let new_path = unsafe { &*new_as_str }; + + trace!(" | (new_path_ptr,new_path_len)='{}'", new_path); + + let new_entry = unsafe { self.get_entry(new_fd)? }; + path::get( + &new_entry, + types::Rights::PATH_LINK_TARGET, + types::Rights::empty(), + types::Lookupflags::empty(), + new_path, + false, + )? + }; + path::link(resolved_old, resolved_new) + } + + 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, + ); + + // Extract path as &str. + let mut bc = GuestBorrows::new(); + let path = unsafe { &*path.as_raw(&mut bc)? }; + + trace!(" | path='{}'", path); + + 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 = unsafe { 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 FdEntry::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 mut bc = GuestBorrows::new(); + let as_str = path.as_raw(&mut bc)?; + let path = unsafe { &*as_str }; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let entry = unsafe { self.get_entry(dirfd)? }; + let resolved = path::get( + &entry, + types::Rights::PATH_READLINK, + types::Rights::empty(), + types::Lookupflags::empty(), + path, + false, + )?; + + // This is to account for `path_readlink` call with zero-length + // buffer. This is a valid POSIX call hence we need to accommodate. + let slice = if buf_len > 0 { + let as_arr = buf.as_array(buf_len); + let as_raw = as_arr.as_raw(&mut bc)?; + unsafe { &mut *as_raw } + } else { + &mut [] + }; + + 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 mut bc = GuestBorrows::new(); + let as_str = path.as_raw(&mut bc)?; + let path = unsafe { &*as_str }; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let entry = unsafe { 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 mut bc = GuestBorrows::new(); + let old_as_str = old_path.as_raw(&mut bc)?; + let old_path = unsafe { &*old_as_str }; + let new_as_str = new_path.as_raw(&mut bc)?; + let new_path = unsafe { &*new_as_str }; + + trace!(" | (old_path_ptr,old_path_len)='{}'", old_path); + trace!(" | (new_path_ptr,new_path_len)='{}'", new_path); + + let resolved_old = { + let entry = unsafe { self.get_entry(old_fd)? }; + path::get( + &entry, + types::Rights::PATH_RENAME_SOURCE, + types::Rights::empty(), + types::Lookupflags::empty(), + old_path, + true, + )? + }; + let resolved_new = { + let entry = unsafe { self.get_entry(new_fd)? }; + 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 resolved_new = { + // FIXME + // We need to enclose ops on `new_path` in a separate scope with separate + // `GuestBorrows` instance to account for the case when `old_path` and `new_path` + // might point to the same memory location. This is possible when creating symlink + // loops for instance. If we don't do it this way, we will trigger a borrow error + // and we don't want that. + let mut bc = GuestBorrows::new(); + let new_as_str = new_path.as_raw(&mut bc)?; + let new_path = unsafe { &*new_as_str }; + + trace!(" | (new_path_ptr,new_path_len)='{}'", new_path); + + let entry = unsafe { self.get_entry(dirfd)? }; + path::get( + &entry, + types::Rights::PATH_SYMLINK, + types::Rights::empty(), + types::Lookupflags::empty(), + new_path, + true, + )? + }; + + let mut bc = GuestBorrows::new(); + let old_as_str = old_path.as_raw(&mut bc)?; + let old_path = unsafe { &*old_as_str }; + + 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 mut bc = GuestBorrows::new(); + let as_str = path.as_raw(&mut bc)?; + let path = unsafe { &*as_str }; + + trace!(" | (path_ptr,path_len)='{}'", path); + + let entry = unsafe { 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 unsafe { 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 unsafe { 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) + } + + 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 mut bc = GuestBorrows::new(); + let as_arr = buf.as_array(buf_len); + let slice = as_arr.as_raw(&mut bc)?; + + getrandom::getrandom(unsafe { &mut *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 000000000000..11dfd274f5c9 --- /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 d2ab148fc6ef..000000000000 --- 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 b47019d43d3c..153a19464116 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 74b02773cf71..702c0d1b42ff 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 000000000000..d04328346227 --- /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 04a3a8316dcf..9fca512635f1 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 d6bc582235a9..2592ef7cd2a7 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 000000000000..69b3cc46e2cb --- /dev/null +++ b/crates/wasi-common/src/sys/unix/fd.rs @@ -0,0 +1,93 @@ +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::fs::FileExt; +use std::os::unix::prelude::AsRawFd; + +pub(crate) fn pread(file: &File, buf: &mut [u8], offset: types::Filesize) -> Result { + log::debug!("fd_pread buf = {:#?}", buf); + let nread = file.read_at(buf, offset)?; + log::debug!("fd_pread buf = {:#?}", buf); + Ok(nread) +} + +pub(crate) fn pwrite(file: &File, buf: &[u8], offset: types::Filesize) -> Result { + log::debug!("fd_pwrite buf = {:#?}", buf); + let nwritten = file.write_at(buf, offset)?; + log::debug!("fd_pwrite buf = {:#?}", buf); + Ok(nwritten) +} + +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 3a3010b1f760..000000000000 --- 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 63eb64fdc066..000000000000 --- a/crates/wasi-common/src/sys/unix/hostcalls_impl/fs.rs +++ /dev/null @@ -1,320 +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) -> WasiResult<()> { - use yanix::file::{linkat, AtFlag}; - unsafe { - linkat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - resolved_new.dirfd().as_raw_fd(), - resolved_new.path(), - AtFlag::SYMLINK_FOLLOW, - ) - } - .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 f151feee1ad8..000000000000 --- 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 c710923a0288..000000000000 --- 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 69a0d25ae244..000000000000 --- 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 000000000000..6017edaef25b --- /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 b1850f9daa69..000000000000 --- 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 9d36e9993af1..000000000000 --- 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 b47019d43d3c..a0427fcdc383 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 000000000000..89a53b27f2f4 --- /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 84badddc25b1..cafc26f3aa3d 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 000000000000..f37d479470fe --- /dev/null +++ b/crates/wasi-common/src/sys/unix/path.rs @@ -0,0 +1,289 @@ +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) -> Result<()> { + use yanix::file::{linkat, AtFlag}; + unsafe { + linkat( + resolved_old.dirfd().as_raw_fd(), + resolved_old.path(), + resolved_new.dirfd().as_raw_fd(), + resolved_new.path(), + AtFlag::SYMLINK_FOLLOW, + )? + }; + 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 000000000000..93cac39a681b --- /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 000000000000..e72820ad6dd1 --- /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 182475fc382f..d2d0c0449781 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 000000000000..1eaec3439f1f --- /dev/null +++ b/crates/wasi-common/src/sys/windows/fd.rs @@ -0,0 +1,221 @@ +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::io::{self, Seek, SeekFrom}; +use std::os::windows::fs::{FileExt, OpenOptionsExt}; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; +use std::path::Path; +use winx::file::{AccessMode, 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 pread(file: &File, buf: &mut [u8], offset: types::Filesize) -> Result { + let nread = read_at(file, buf, offset)?; + Ok(nread) +} + +// TODO refactor common code with unix +pub(crate) fn pwrite(file: &File, buf: &[u8], offset: types::Filesize) -> Result { + let nwritten = write_at(file, buf, offset)?; + Ok(nwritten) +} + +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 cadb7205bffc..000000000000 --- 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 1f91e6f9a9d4..000000000000 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/fs.rs +++ /dev/null @@ -1,595 +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) -> 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 41ea9bbacf0c..000000000000 --- 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 c8c383831dbb..000000000000 --- 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 7bece1e0563c..bb559be5e2b8 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 000000000000..b1ed69ea9486 --- /dev/null +++ b/crates/wasi-common/src/sys/windows/path.rs @@ -0,0 +1,502 @@ +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) -> 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 51539bf7ae3b..71309d187564 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 50c34177d837..5215b79c1ae2 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; @@ -20,7 +17,7 @@ pub enum VirtualDirEntry { impl VirtualDirEntry { pub fn empty_directory() -> Self { - VirtualDirEntry::Directory(HashMap::new()) + Self::Directory(HashMap::new()) } } @@ -39,14 +36,14 @@ 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. 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 +51,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 pread(&self, _buf: &mut [u8], _offset: u64) -> Result { + Err(Errno::Badf) } - fn pwrite(&self, _buf: &mut [u8], _offset: u64) -> WasiResult { - Err(WasiError::EBADF) + fn pwrite(&self, _buf: &mut [u8], _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 +193,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 +202,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 +217,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 +248,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 +259,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 +267,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,12 +281,12 @@ 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 } fn try_clone(&self) -> io::Result> { - Ok(Box::new(InMemoryFile { + Ok(Box::new(Self { cursor: 0, fd_flags: self.fd_flags, parent: Rc::clone(&self.parent), @@ -321,9 +294,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 +304,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 +316,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 +333,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 +367,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 +376,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 +396,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 { + fn pread(&self, buf: &mut [u8], offset: types::Filesize) -> Result { self.data.borrow_mut().pread(buf, offset) } - fn pwrite(&self, buf: &mut [u8], offset: wasi::__wasi_filesize_t) -> WasiResult { + fn pwrite(&self, buf: &mut [u8], offset: types::Filesize) -> Result { self.data.borrow_mut().pwrite(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 +440,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 +463,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 +485,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() } } @@ -550,7 +509,7 @@ pub struct VirtualDir { impl VirtualDir { pub fn new(writable: bool) -> Self { - VirtualDir { + Self { writable, parent: Rc::new(RefCell::new(None)), entries: Rc::new(RefCell::new(HashMap::new())), @@ -558,13 +517,13 @@ impl VirtualDir { } #[allow(dead_code)] - pub fn with_dir>(mut self, dir: VirtualDir, path: P) -> Self { + pub fn with_dir>(mut self, dir: Self, path: P) -> Self { self.add_dir(dir, path); self } #[allow(dead_code)] - pub fn add_dir>(&mut self, dir: VirtualDir, path: P) { + pub fn add_dir>(&mut self, dir: Self, path: P) { let entry = Box::new(dir); entry.set_parent(Some(self.try_clone().expect("can clone self"))); self.entries @@ -603,16 +562,16 @@ const RESERVED_ENTRY_COUNT: u32 = 2; impl VirtualFile for VirtualDir { fn try_clone(&self) -> io::Result> { - Ok(Box::new(VirtualDir { + Ok(Box::new(Self { writable: self.writable, parent: Rc::clone(&self.parent), entries: Rc::clone(&self.entries), })) } - 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 +579,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 +606,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 +638,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 +651,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 +686,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,64 +721,66 @@ 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(VirtualDir::new(true)); + let new_dir = Box::new(Self::new(true)); new_dir.set_parent(Some(self.try_clone()?)); 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 +799,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 +829,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 +843,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 deb579e24dcc..61c140a9b656 100644 --- a/crates/wasi-common/src/wasi.rs +++ b/crates/wasi-common/src/wasi.rs @@ -1,237 +1,216 @@ //! 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)] - -use wig::witx_wasi_types; - -witx_wasi_types!("snapshot" "wasi_snapshot_preview1"); - -pub type WasiResult = Result; - -#[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, +wiggle::from_witx!({ + witx: ["wig/WASI/phases/snapshot/witx/wasi_snapshot_preview1.witx"], + ctx: WasiCtx, +}); + +pub use types::Errno; +pub type Result = std::result::Result; + +impl<'a> wiggle_runtime::GuestErrorType<'a> for Errno { + type Context = WasiCtx; + + fn success() -> Self { + Self::Success + } + + fn from_error(e: wiggle_runtime::GuestError, _ctx: &Self::Context) -> Self { + eprintln!("Guest error: {:?}", e); + // TODO proper error mapping + Self::Inval + } } -impl WasiError { - pub fn as_raw_errno(self) -> __wasi_errno_t { - self as __wasi_errno_t +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, + } } } -impl From for WasiError { +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 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 __wasi_fd_t { +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 11460010c871..000000000000 --- 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 d0fd3f41e078..1a6688621b25 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 4e681bfc4733..1c4ac2df55de 100644 --- a/crates/wasi-common/wig/src/wasi.rs +++ b/crates/wasi-common/wig/src/wasi.rs @@ -250,3 +250,229 @@ 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(); + + // The first result is returned bare right now... + if let Some(ret) = results.next() { + 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(", ")); + let wrap = format_ident!("wrap{}", shim_arg_decls.len() + 1); + ctor_externs.push(quote! { + let my_cx = cx.clone(); + let #name_ident = wasmtime::Func::#wrap( + store, + move |mem: crate::WasiCallerMemory #(,#shim_arg_decls)*| -> #ret_ty { + log::trace!( + #format_str, + #(#format_args),* + ); + unsafe { + wasi_common::wasi::#module_id::#name_ident( + &mut my_cx.borrow_mut(), + &mem, + #(#hostcall_args),* + ) #cvt_ret + } + } + ); + }); + } + } + + quote! { + /// 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 47fae58778df..4ae1166a195f 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" } 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 3b0f65c38a7a..1c1729febc54 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" ); @@ -33,6 +31,12 @@ struct WasiCallerMemory { len: usize, } +unsafe impl wiggle_runtime::GuestMemory for WasiCallerMemory { + fn base(&self) -> (*mut u8, u32) { + (self.base, self.len as _) + } +} + impl wasmtime::WasmTy for WasiCallerMemory { type Abi = (); @@ -63,9 +67,9 @@ impl wasmtime::WasmTy for WasiCallerMemory { } impl WasiCallerMemory { - unsafe fn get(&self) -> Result<&mut [u8], wasi_common::wasi::__wasi_errno_t> { + unsafe fn get(&self) -> wasi_common::wasi::Result<&mut [u8]> { if self.base.is_null() { - Err(wasi_common::wasi::__WASI_ERRNO_INVAL) + Err(wasi_common::wasi::Errno::Inval) } else { Ok(std::slice::from_raw_parts_mut(self.base, self.len)) } diff --git a/crates/wiggle/crates/generate/src/types/handle.rs b/crates/wiggle/crates/generate/src/types/handle.rs index 4f922cbd7b3d..52e0d2501c29 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