From cbf7cbfa39f3090fd10f6660863980dfbfad72c3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 8 May 2020 01:00:14 +0200 Subject: [PATCH] Introduce strongly-typed system primitives (#1561) * Introduce strongly-typed system primitives This commit does a lot of reshuffling and even some more. It introduces strongly-typed system primitives which are: `OsFile`, `OsDir`, `Stdio`, and `OsOther`. Those primitives are separate structs now, each implementing a subset of `Handle` methods, rather than all being an enumeration of some supertype such as `OsHandle`. To summarise the structs: * `OsFile` represents a regular file, and implements fd-ops of `Handle` trait * `OsDir` represents a directory, and primarily implements path-ops, plus `readdir` and some common fd-ops such as `fdstat`, etc. * `Stdio` represents a stdio handle, and implements a subset of fd-ops such as `fdstat` _and_ `read_` and `write_vectored` calls * `OsOther` currently represents anything else and implements a set similar to that implemented by `Stdio` This commit is effectively an experiment and an excercise into better understanding what's going on for each OS resource/type under-the-hood. It's meant to give us some intuition in order to move on with the idea of having strongly-typed handles in WASI both in the syscall impl as well as at the libc level. Some more minor changes include making `OsHandle` represent an OS-specific wrapper for a raw OS handle (Unix fd or Windows handle). Also, since `OsDir` is tricky across OSes, we also have a supertype of `OsHandle` called `OsDirHandle` which may store a `DIR*` stream pointer (mainly BSD). Last but not least, the `Filetype` and `Rights` are now computed when the resource is created, rather than every time we call `Handle::get_file_type` and `Handle::get_rights`. Finally, in order to facilitate the latter, I've converted `EntryRights` into `HandleRights` and pushed them into each `Handle` implementor. * Do not adjust rights on Stdio * Clean up testing for TTY and escaping writes * Implement AsFile for dyn Handle This cleans up a lot of repeating boilerplate code todo with dynamic dispatch. * Delegate definition of OsDir to OS-specific modules Delegates defining `OsDir` struct to OS-specific modules (BSD, Linux, Emscripten, Windows). This way, `OsDir` can safely re-use `OsHandle` for raw OS handle storage, and can store some aux data such as an initialized stream ptr in case of BSD. As a result, we can safely get rid of `OsDirHandle` which IMHO was causing unnecessary noise and overcomplicating the design. On the other hand, delegating definition of `OsDir` to OS-specific modules isn't super clean in and of itself either. Perhaps there's a better way of handling this? * Check if filetype of OS handle matches WASI filetype when creating It seems prudent to check if the passed in `File` instance is of type matching that of the requested WASI filetype. In other words, we'd like to avoid situations where `OsFile` is created from a pipe. * Make AsFile fallible Return `EBADF` in `AsFile` in case a `Handle` cannot be made into a `std::fs::File`. * Remove unnecessary as_file conversion * Remove unnecessary check for TTY for Stdio handle type * Fix incorrect stdio ctors on Unix * Split Stdio into three separate types: Stdin, Stdout, Stderr * Rename PendingEntry::File to PendingEntry::OsHandle to avoid confusion * Rename OsHandle to RawOsHandle Also, since `RawOsHandle` on *nix doesn't need interior mutability wrt the inner raw file descriptor, we can safely swap the `RawFd` for `File` instance. * Add docs explaining what OsOther is * Allow for stdio to be non-character-device (e.g., piped) * Return error on bad preopen rather than panic --- crates/wasi-common/src/ctx.rs | 117 +++++--- crates/wasi-common/src/entry.rs | 117 ++------ crates/wasi-common/src/handle.rs | 74 ++++- crates/wasi-common/src/path.rs | 8 +- .../src/snapshots/wasi_snapshot_preview1.rs | 99 +++---- crates/wasi-common/src/sys/mod.rs | 67 ++++- crates/wasi-common/src/sys/osdir.rs | 129 ++++++++ crates/wasi-common/src/sys/osfile.rs | 133 +++++++++ crates/wasi-common/src/sys/oshandle.rs | 277 ------------------ crates/wasi-common/src/sys/osother.rs | 95 ++++++ crates/wasi-common/src/sys/stdio.rs | 176 +++++++++++ crates/wasi-common/src/sys/unix/bsd/mod.rs | 2 +- crates/wasi-common/src/sys/unix/bsd/osdir.rs | 46 +++ crates/wasi-common/src/sys/unix/bsd/osfile.rs | 109 ------- crates/wasi-common/src/sys/unix/bsd/path.rs | 10 +- .../src/sys/unix/emscripten/mod.rs | 4 +- crates/wasi-common/src/sys/unix/fd.rs | 10 +- crates/wasi-common/src/sys/unix/linux/mod.rs | 2 +- .../wasi-common/src/sys/unix/linux/osdir.rs | 35 +++ .../wasi-common/src/sys/unix/linux/osfile.rs | 82 ------ crates/wasi-common/src/sys/unix/linux/path.rs | 10 +- crates/wasi-common/src/sys/unix/mod.rs | 95 +++++- crates/wasi-common/src/sys/unix/osdir.rs | 39 +++ crates/wasi-common/src/sys/unix/osfile.rs | 38 +++ crates/wasi-common/src/sys/unix/oshandle.rs | 139 ++------- crates/wasi-common/src/sys/unix/osother.rs | 34 +++ crates/wasi-common/src/sys/unix/path.rs | 33 ++- crates/wasi-common/src/sys/unix/poll.rs | 43 ++- crates/wasi-common/src/sys/unix/stdio.rs | 59 ++++ crates/wasi-common/src/sys/windows/fd.rs | 17 +- crates/wasi-common/src/sys/windows/mod.rs | 71 ++++- crates/wasi-common/src/sys/windows/osdir.rs | 51 ++++ crates/wasi-common/src/sys/windows/osfile.rs | 38 +++ .../wasi-common/src/sys/windows/oshandle.rs | 139 +-------- crates/wasi-common/src/sys/windows/osother.rs | 31 ++ crates/wasi-common/src/sys/windows/path.rs | 49 ++-- crates/wasi-common/src/sys/windows/poll.rs | 107 ++++--- crates/wasi-common/src/sys/windows/stdio.rs | 59 ++++ crates/wasi-common/src/virtfs.rs | 72 ++--- 39 files changed, 1643 insertions(+), 1073 deletions(-) create mode 100644 crates/wasi-common/src/sys/osdir.rs create mode 100644 crates/wasi-common/src/sys/osfile.rs delete mode 100644 crates/wasi-common/src/sys/oshandle.rs create mode 100644 crates/wasi-common/src/sys/osother.rs create mode 100644 crates/wasi-common/src/sys/stdio.rs create mode 100644 crates/wasi-common/src/sys/unix/bsd/osdir.rs delete mode 100644 crates/wasi-common/src/sys/unix/bsd/osfile.rs create mode 100644 crates/wasi-common/src/sys/unix/linux/osdir.rs delete mode 100644 crates/wasi-common/src/sys/unix/linux/osfile.rs create mode 100644 crates/wasi-common/src/sys/unix/osdir.rs create mode 100644 crates/wasi-common/src/sys/unix/osfile.rs create mode 100644 crates/wasi-common/src/sys/unix/osother.rs create mode 100644 crates/wasi-common/src/sys/unix/stdio.rs create mode 100644 crates/wasi-common/src/sys/windows/osdir.rs create mode 100644 crates/wasi-common/src/sys/windows/osfile.rs create mode 100644 crates/wasi-common/src/sys/windows/osother.rs create mode 100644 crates/wasi-common/src/sys/windows/stdio.rs diff --git a/crates/wasi-common/src/ctx.rs b/crates/wasi-common/src/ctx.rs index de66d81814..895c96ad6f 100644 --- a/crates/wasi-common/src/ctx.rs +++ b/crates/wasi-common/src/ctx.rs @@ -1,13 +1,16 @@ use crate::entry::{Entry, EntryHandle}; use crate::fdpool::FdPool; use crate::handle::Handle; -use crate::sys::oshandle::{OsHandle, OsHandleExt}; +use crate::sys::osdir::OsDir; +use crate::sys::osother::{OsOther, OsOtherExt}; +use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt}; use crate::virtfs::{VirtualDir, VirtualDirEntry}; use crate::wasi::types; use crate::wasi::{Errno, Result}; use std::borrow::Borrow; use std::cell::RefCell; use std::collections::HashMap; +use std::convert::TryFrom; use std::ffi::{self, CString, OsString}; use std::fs::File; use std::path::{Path, PathBuf}; @@ -32,9 +35,9 @@ pub enum WasiCtxBuilderError { /// Provided sequence of bytes contained an unexpected NUL byte. #[error("provided sequence contained an unexpected NUL byte")] UnexpectedNul(#[from] ffi::NulError), - /// Provided `File` is not a directory. - #[error("preopened directory path {} is not a directory", .0.display())] - NotADirectory(PathBuf), + /// The root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory. + #[error("the root of a VirtualDirEntry tree at {} must be a VirtualDirEntry::Directory", .0.display())] + VirtualDirEntryRootNotADirectory(PathBuf), /// `WasiCtx` has too many opened files. #[error("context object has too many opened files")] TooManyFilesOpen, @@ -43,8 +46,8 @@ pub enum WasiCtxBuilderError { type WasiCtxBuilderResult = std::result::Result; enum PendingEntry { - Thunk(fn() -> io::Result), - File(File), + Thunk(fn() -> io::Result>), + OsHandle(File), } impl std::fmt::Debug for PendingEntry { @@ -53,9 +56,9 @@ impl std::fmt::Debug for PendingEntry { Self::Thunk(f) => write!( fmt, "PendingEntry::Thunk({:p})", - f as *const fn() -> io::Result + f as *const fn() -> io::Result> ), - Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f), + Self::OsHandle(f) => write!(fmt, "PendingEntry::OsHandle({:?})", f), } } } @@ -105,12 +108,27 @@ impl PendingCString { } } +struct PendingPreopen(Box WasiCtxBuilderResult>>); + +impl PendingPreopen { + fn new(f: F) -> Self + where + F: FnOnce() -> WasiCtxBuilderResult> + 'static, + { + Self(Box::new(f)) + } + + fn into(self) -> WasiCtxBuilderResult> { + self.0() + } +} + /// A builder allowing customizable construction of `WasiCtx` instances. pub struct WasiCtxBuilder { stdin: Option, stdout: Option, stderr: Option, - preopens: Option)>>, + preopens: Option>, args: Option>, env: Option>, } @@ -118,9 +136,9 @@ pub struct WasiCtxBuilder { impl WasiCtxBuilder { /// Builder for a new `WasiCtx`. pub fn new() -> Self { - let stdin = Some(PendingEntry::Thunk(OsHandle::from_null)); - let stdout = Some(PendingEntry::Thunk(OsHandle::from_null)); - let stderr = Some(PendingEntry::Thunk(OsHandle::from_null)); + let stdin = Some(PendingEntry::Thunk(OsOther::from_null)); + let stdout = Some(PendingEntry::Thunk(OsOther::from_null)); + let stderr = Some(PendingEntry::Thunk(OsOther::from_null)); Self { stdin, @@ -167,27 +185,27 @@ impl WasiCtxBuilder { /// Inherit stdin from the host process. pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin()))); + self.stdin = Some(PendingEntry::Thunk(Stdin::stdin)); self } /// Inherit stdout from the host process. pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout()))); + self.stdout = Some(PendingEntry::Thunk(Stdout::stdout)); self } - /// Inherit stdout from the host process. + /// Inherit stderr from the host process. pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr()))); + self.stderr = Some(PendingEntry::Thunk(Stderr::stderr)); self } /// Inherit the stdin, stdout, and stderr streams from the host process. pub fn inherit_stdio(&mut self) -> &mut Self { - self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin()))); - self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout()))); - self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr()))); + self.stdin = Some(PendingEntry::Thunk(Stdin::stdin)); + self.stdout = Some(PendingEntry::Thunk(Stdout::stdout)); + self.stderr = Some(PendingEntry::Thunk(Stderr::stderr)); self } @@ -231,28 +249,32 @@ impl WasiCtxBuilder { /// Provide a File to use as stdin pub fn stdin(&mut self, file: File) -> &mut Self { - self.stdin = Some(PendingEntry::File(file)); + self.stdin = Some(PendingEntry::OsHandle(file)); self } /// Provide a File to use as stdout pub fn stdout(&mut self, file: File) -> &mut Self { - self.stdout = Some(PendingEntry::File(file)); + self.stdout = Some(PendingEntry::OsHandle(file)); self } /// Provide a File to use as stderr pub fn stderr(&mut self, file: File) -> &mut Self { - self.stderr = Some(PendingEntry::File(file)); + self.stderr = Some(PendingEntry::OsHandle(file)); self } /// Add a preopened directory. pub fn preopened_dir>(&mut self, dir: File, guest_path: P) -> &mut Self { - self.preopens.as_mut().unwrap().push(( - guest_path.as_ref().to_owned(), - Box::new(OsHandle::from(dir)), - )); + let preopen = PendingPreopen::new(move || { + let dir = OsDir::try_from(dir).map_err(WasiCtxBuilderError::from)?; + Ok(Box::new(dir)) + }); + self.preopens + .as_mut() + .unwrap() + .push((guest_path.as_ref().to_owned(), preopen)); self } @@ -277,18 +299,22 @@ impl WasiCtxBuilder { } } - let dir = if let VirtualDirEntry::Directory(entries) = dir { - let mut dir = VirtualDir::new(true); - populate_directory(entries, &mut dir); - Box::new(dir) - } else { - panic!("the root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory"); - }; - + let guest_path_owned = guest_path.as_ref().to_owned(); + let preopen = PendingPreopen::new(move || { + if let VirtualDirEntry::Directory(entries) = dir { + let mut dir = VirtualDir::new(true); + populate_directory(entries, &mut dir); + Ok(Box::new(dir)) + } else { + Err(WasiCtxBuilderError::VirtualDirEntryRootNotADirectory( + guest_path_owned, + )) + } + }); self.preopens .as_mut() .unwrap() - .push((guest_path.as_ref().to_owned(), dir)); + .push((guest_path.as_ref().to_owned(), preopen)); self } @@ -336,15 +362,16 @@ impl WasiCtxBuilder { log::debug!("WasiCtx inserting entry {:?}", pending); let fd = match pending { PendingEntry::Thunk(f) => { - let handle = EntryHandle::new(f()?); - let entry = Entry::from(handle)?; + let handle = EntryHandle::from(f()?); + let entry = Entry::new(handle); entries .insert(entry) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? } - PendingEntry::File(f) => { - let handle = EntryHandle::new(OsHandle::from(f)); - let entry = Entry::from(handle)?; + PendingEntry::OsHandle(f) => { + let handle = OsOther::try_from(f)?; + let handle = EntryHandle::new(handle); + let entry = Entry::new(handle); entries .insert(entry) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? @@ -353,13 +380,9 @@ impl WasiCtxBuilder { log::debug!("WasiCtx inserted at {:?}", fd); } // Then add the preopen entries. - for (guest_path, dir) in self.preopens.take().unwrap() { - if !dir.is_directory() { - return Err(WasiCtxBuilderError::NotADirectory(guest_path)); - } - - let handle = EntryHandle::from(dir); - let mut entry = Entry::from(handle)?; + for (guest_path, preopen) in self.preopens.take().unwrap() { + let handle = EntryHandle::from(preopen.into()?); + let mut entry = Entry::new(handle); entry.preopen_path = Some(guest_path); let fd = entries .insert(entry) diff --git a/crates/wasi-common/src/entry.rs b/crates/wasi-common/src/entry.rs index debd904617..eb311a7b10 100644 --- a/crates/wasi-common/src/entry.rs +++ b/crates/wasi-common/src/entry.rs @@ -1,11 +1,9 @@ -use crate::handle::Handle; -use crate::wasi::types::{Filetype, Rights}; +use crate::handle::{Handle, HandleRights}; +use crate::wasi::types::Filetype; use crate::wasi::{Errno, Result}; -use std::cell::Cell; use std::ops::Deref; use std::path::PathBuf; use std::rc::Rc; -use std::{fmt, io}; pub(crate) struct EntryHandle(Rc); @@ -33,118 +31,67 @@ impl Deref for EntryHandle { } } -/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires -/// certain rights `rights` in order to be accessed correctly. +/// An abstraction struct serving as a wrapper for a `Handle` object. /// -/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or -/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` method +/// Here, the `handle` field stores an instance of `Handle` type (such as a file descriptor, or +/// stdin handle), and accessing it can only be done via the provided `Entry::as_handle` method /// which require a set of base and inheriting rights to be specified, verifying whether the stored -/// `Descriptor` object is valid for the rights specified. +/// `Handle` object is valid for the rights specified. pub(crate) struct Entry { - pub(crate) file_type: Filetype, handle: EntryHandle, - pub(crate) rights: Cell, pub(crate) preopen_path: Option, // TODO: directories } -/// Represents rights of an `Entry` entity, either already held or -/// required. -#[derive(Debug, Copy, Clone)] -pub(crate) struct EntryRights { - pub(crate) base: Rights, - pub(crate) inheriting: Rights, -} - -impl EntryRights { - pub(crate) fn new(base: Rights, inheriting: Rights) -> Self { - Self { base, inheriting } - } - - /// Create new `EntryRights` instance from `base` rights only, keeping - /// `inheriting` set to none. - pub(crate) fn from_base(base: Rights) -> Self { - Self { - base, - inheriting: Rights::empty(), - } - } - - /// Create new `EntryRights` instance with both `base` and `inheriting` - /// rights set to none. - pub(crate) fn empty() -> Self { - Self { - base: Rights::empty(), - inheriting: Rights::empty(), - } - } - - /// Check if `other` is a subset of those rights. - pub(crate) fn contains(&self, other: &Self) -> bool { - self.base.contains(&other.base) && self.inheriting.contains(&other.inheriting) - } -} - -impl fmt::Display for EntryRights { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "EntryRights {{ base: {}, inheriting: {} }}", - self.base, self.inheriting - ) - } -} - impl Entry { - pub(crate) fn from(handle: EntryHandle) -> io::Result { - let file_type = handle.get_file_type()?; - let rights = handle.get_rights()?; - Ok(Self { - file_type, + pub(crate) fn new(handle: EntryHandle) -> Self { + let preopen_path = None; + Self { handle, - rights: Cell::new(rights), - preopen_path: None, - }) + preopen_path, + } } - /// Convert this `Entry` into a host `Descriptor` object provided the specified + pub(crate) fn get_file_type(&self) -> Filetype { + self.handle.get_file_type() + } + + pub(crate) fn get_rights(&self) -> HandleRights { + self.handle.get_rights() + } + + pub(crate) fn set_rights(&self, rights: HandleRights) { + self.handle.set_rights(rights) + } + + /// Convert this `Entry` into a `Handle` object provided the specified /// `rights` rights are set on this `Entry` object. /// - /// The `Entry` can only be converted into a valid `Descriptor` object if + /// The `Entry` can only be converted into a valid `Handle` object if /// the specified set of base rights, and inheriting rights encapsulated within `rights` - /// `EntryRights` structure is a subset of rights attached to this `Entry`. The check is + /// `HandleRights` structure is a subset of rights attached to this `Entry`. The check is /// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable` /// is returned. - pub(crate) fn as_handle(&self, rights: &EntryRights) -> Result { + pub(crate) fn as_handle(&self, rights: &HandleRights) -> Result { self.validate_rights(rights)?; Ok(self.handle.get()) } - /// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if + /// Check if this `Entry` object satisfies the specified `HandleRights`; i.e., if /// rights attached to this `Entry` object are a superset. /// /// Upon unsuccessful check, `Errno::Notcapable` is returned. - pub(crate) fn validate_rights(&self, rights: &EntryRights) -> Result<()> { - if self.rights.get().contains(rights) { + pub(crate) fn validate_rights(&self, rights: &HandleRights) -> Result<()> { + let this_rights = self.handle.get_rights(); + if this_rights.contains(rights) { Ok(()) } else { log::trace!( " | validate_rights failed: required rights = {}; actual rights = {}", rights, - self.rights.get(), + this_rights, ); Err(Errno::Notcapable) } } - - /// Test whether this descriptor is considered a tty within WASI. - /// 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 == Filetype::CharacterDevice - && self - .rights - .get() - .contains(&EntryRights::from_base(Rights::FD_SEEK | Rights::FD_TELL)) - } } diff --git a/crates/wasi-common/src/handle.rs b/crates/wasi-common/src/handle.rs index 671ae2d2a1..21a0ab4f2c 100644 --- a/crates/wasi-common/src/handle.rs +++ b/crates/wasi-common/src/handle.rs @@ -1,20 +1,74 @@ -use crate::entry::EntryRights; -use crate::wasi::{types, Errno, Result}; +use crate::wasi::types::{self, Rights}; +use crate::wasi::{Errno, Result}; use std::any::Any; +use std::fmt; use std::io::{self, SeekFrom}; +/// Represents rights of a `Handle`, either already held or required. +#[derive(Debug, Copy, Clone)] +pub(crate) struct HandleRights { + pub(crate) base: Rights, + pub(crate) inheriting: Rights, +} + +impl HandleRights { + pub(crate) fn new(base: Rights, inheriting: Rights) -> Self { + Self { base, inheriting } + } + + /// Create new `HandleRights` instance from `base` rights only, keeping + /// `inheriting` set to none. + pub(crate) fn from_base(base: Rights) -> Self { + Self { + base, + inheriting: Rights::empty(), + } + } + + /// Create new `HandleRights` instance with both `base` and `inheriting` + /// rights set to none. + pub(crate) fn empty() -> Self { + Self { + base: Rights::empty(), + inheriting: Rights::empty(), + } + } + + /// Check if `other` is a subset of those rights. + pub(crate) fn contains(&self, other: &Self) -> bool { + self.base.contains(&other.base) && self.inheriting.contains(&other.inheriting) + } +} + +impl fmt::Display for HandleRights { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "HandleRights {{ base: {}, inheriting: {} }}", + self.base, self.inheriting + ) + } +} + pub(crate) trait Handle { fn as_any(&self) -> &dyn Any; fn try_clone(&self) -> io::Result>; - fn get_file_type(&self) -> io::Result; - fn get_rights(&self) -> io::Result { - Ok(EntryRights::empty()) + fn get_file_type(&self) -> types::Filetype; + fn get_rights(&self) -> HandleRights { + HandleRights::empty() } + fn set_rights(&self, rights: HandleRights); fn is_directory(&self) -> bool { - if let Ok(ft) = self.get_file_type() { - return ft == types::Filetype::Directory; - } - false + self.get_file_type() == types::Filetype::Directory + } + /// Test whether this descriptor is considered a tty within WASI. + /// Note that since WASI itself lacks an `isatty` syscall and relies + /// on a conservative approximation, we use the same approximation here. + fn is_tty(&self) -> bool { + let file_type = self.get_file_type(); + let rights = self.get_rights(); + let required_rights = HandleRights::from_base(Rights::FD_SEEK | Rights::FD_TELL); + file_type == types::Filetype::CharacterDevice && rights.contains(&required_rights) } // TODO perhaps should be a separate trait? // FdOps @@ -73,7 +127,7 @@ pub(crate) trait Handle { fn sync(&self) -> Result<()> { Ok(()) } - fn write_vectored(&self, _iovs: &[io::IoSlice], _isatty: bool) -> Result { + fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result { Err(Errno::Badf) } // TODO perhaps should be a separate trait? diff --git a/crates/wasi-common/src/path.rs b/crates/wasi-common/src/path.rs index 515ed18019..f5399a7b7e 100644 --- a/crates/wasi-common/src/path.rs +++ b/crates/wasi-common/src/path.rs @@ -1,5 +1,5 @@ -use crate::entry::{Entry, EntryRights}; -use crate::handle::Handle; +use crate::entry::Entry; +use crate::handle::{Handle, HandleRights}; use crate::wasi::{types, Errno, Result}; use std::path::{Component, Path}; use std::str; @@ -12,7 +12,7 @@ pub(crate) use crate::sys::path::{from_host, open_rights}; /// This is a workaround for not having Capsicum support in the OS. pub(crate) fn get( entry: &Entry, - required_rights: &EntryRights, + required_rights: &HandleRights, dirflags: types::Lookupflags, path: &GuestPtr<'_, str>, needs_final_component: bool, @@ -33,7 +33,7 @@ pub(crate) fn get( return Err(Errno::Ilseq); } - if entry.file_type != types::Filetype::Directory { + if entry.get_file_type() != types::Filetype::Directory { // if `dirfd` doesn't refer to a directory, return `Notdir`. return Err(Errno::Notdir); } diff --git a/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs index 23649b20b4..4ab53f5d6e 100644 --- a/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs +++ b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs @@ -1,4 +1,5 @@ -use crate::entry::{Entry, EntryHandle, EntryRights}; +use crate::entry::{Entry, EntryHandle}; +use crate::handle::HandleRights; use crate::sys::clock; use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1; use crate::wasi::{types, AsBytes, Errno, Result}; @@ -91,7 +92,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { len: types::Filesize, advice: types::Advice, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_ADVISE); + let required_rights = HandleRights::from_base(types::Rights::FD_ADVISE); let entry = self.get_entry(fd)?; entry .as_handle(&required_rights)? @@ -104,7 +105,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { offset: types::Filesize, len: types::Filesize, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_ALLOCATE); + let required_rights = HandleRights::from_base(types::Rights::FD_ALLOCATE); let entry = self.get_entry(fd)?; entry.as_handle(&required_rights)?.allocate(offset, len) } @@ -121,18 +122,18 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } fn fd_datasync(&self, fd: types::Fd) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_DATASYNC); + let required_rights = HandleRights::from_base(types::Rights::FD_DATASYNC); let entry = self.get_entry(fd)?; entry.as_handle(&required_rights)?.datasync() } fn fd_fdstat_get(&self, fd: types::Fd) -> Result { let entry = self.get_entry(fd)?; - let file = entry.as_handle(&EntryRights::empty())?; + let file = entry.as_handle(&HandleRights::empty())?; let fs_flags = file.fdstat_get()?; - let rights = entry.rights.get(); + let rights = entry.get_rights(); let fdstat = types::Fdstat { - fs_filetype: entry.file_type, + fs_filetype: entry.get_file_type(), fs_rights_base: rights.base, fs_rights_inheriting: rights.inheriting, fs_flags, @@ -141,7 +142,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS); + let required_rights = HandleRights::from_base(types::Rights::FD_FDSTAT_SET_FLAGS); let entry = self.get_entry(fd)?; entry.as_handle(&required_rights)?.fdstat_set_flags(flags) } @@ -152,24 +153,24 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, ) -> Result<()> { - let rights = EntryRights::new(fs_rights_base, fs_rights_inheriting); + let rights = HandleRights::new(fs_rights_base, fs_rights_inheriting); let entry = self.get_entry(fd)?; - if !entry.rights.get().contains(&rights) { + if !entry.get_rights().contains(&rights) { return Err(Errno::Notcapable); } - entry.rights.set(rights); + entry.set_rights(rights); Ok(()) } fn fd_filestat_get(&self, fd: types::Fd) -> Result { - let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_GET); + let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_GET); let entry = self.get_entry(fd)?; let host_filestat = entry.as_handle(&required_rights)?.filestat_get()?; Ok(host_filestat) } fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE); + let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_SIZE); let entry = self.get_entry(fd)?; // This check will be unnecessary when rust-lang/rust#63326 is fixed if size > i64::max_value() as u64 { @@ -185,7 +186,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES); + let required_rights = HandleRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES); let entry = self.get_entry(fd)?; entry .as_handle(&required_rights)? @@ -213,7 +214,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } let required_rights = - EntryRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK); + HandleRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK); let entry = self.get_entry(fd)?; if offset > i64::max_value() as u64 { return Err(Errno::Io); @@ -227,9 +228,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { fn fd_prestat_get(&self, fd: types::Fd) -> Result { // TODO: should we validate any rights here? - let fe = self.get_entry(fd)?; - let po_path = fe.preopen_path.as_ref().ok_or(Errno::Notsup)?; - if fe.file_type != types::Filetype::Directory { + let entry = self.get_entry(fd)?; + let po_path = entry.preopen_path.as_ref().ok_or(Errno::Notsup)?; + if entry.get_file_type() != types::Filetype::Directory { return Err(Errno::Notdir); } @@ -247,9 +248,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { path_len: types::Size, ) -> Result<()> { // TODO: should we validate any rights here? - let fe = self.get_entry(fd)?; - let po_path = fe.preopen_path.as_ref().ok_or(Errno::Notsup)?; - if fe.file_type != types::Filetype::Directory { + let entry = self.get_entry(fd)?; + let po_path = entry.preopen_path.as_ref().ok_or(Errno::Notsup)?; + if entry.get_file_type() != types::Filetype::Directory { return Err(Errno::Notdir); } @@ -289,7 +290,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } let required_rights = - EntryRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK); + HandleRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK); let entry = self.get_entry(fd)?; if offset > i64::max_value() as u64 { @@ -318,7 +319,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { slices.push(io::IoSliceMut::new(slice)); } - let required_rights = EntryRights::from_base(types::Rights::FD_READ); + let required_rights = HandleRights::from_base(types::Rights::FD_READ); let entry = self.get_entry(fd)?; let host_nread = entry .as_handle(&required_rights)? @@ -335,7 +336,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { buf_len: types::Size, cookie: types::Dircookie, ) -> Result { - let required_rights = EntryRights::from_base(types::Rights::FD_READDIR); + let required_rights = HandleRights::from_base(types::Rights::FD_READDIR); let entry = self.get_entry(fd)?; let mut bufused = 0; @@ -395,7 +396,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } else { types::Rights::FD_SEEK | types::Rights::FD_TELL }; - let required_rights = EntryRights::from_base(base); + let required_rights = HandleRights::from_base(base); let entry = self.get_entry(fd)?; let pos = match whence { types::Whence::Cur => SeekFrom::Current(offset), @@ -407,13 +408,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } fn fd_sync(&self, fd: types::Fd) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::FD_SYNC); + let required_rights = HandleRights::from_base(types::Rights::FD_SYNC); let entry = self.get_entry(fd)?; entry.as_handle(&required_rights)?.sync() } fn fd_tell(&self, fd: types::Fd) -> Result { - let required_rights = EntryRights::from_base(types::Rights::FD_TELL); + let required_rights = HandleRights::from_base(types::Rights::FD_TELL); let entry = self.get_entry(fd)?; let host_offset = entry .as_handle(&required_rights)? @@ -435,19 +436,19 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { }; slices.push(io::IoSlice::new(slice)); } - let required_rights = EntryRights::from_base(types::Rights::FD_WRITE); + let required_rights = HandleRights::from_base(types::Rights::FD_WRITE); let entry = self.get_entry(fd)?; - let isatty = entry.isatty(); let host_nwritten = entry .as_handle(&required_rights)? - .write_vectored(&slices, isatty)? + .write_vectored(&slices)? .try_into()?; Ok(host_nwritten) } fn path_create_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { - let required_rights = - EntryRights::from_base(types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY); + let required_rights = HandleRights::from_base( + types::Rights::PATH_OPEN | types::Rights::PATH_CREATE_DIRECTORY, + ); let entry = self.get_entry(dirfd)?; let (dirfd, path) = path::get( &entry, @@ -465,7 +466,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { flags: types::Lookupflags, path: &GuestPtr<'_, str>, ) -> Result { - let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_GET); + let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_GET); let entry = self.get_entry(dirfd)?; let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?; let host_filestat = dirfd @@ -489,7 +490,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES); + let required_rights = HandleRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES); let entry = self.get_entry(dirfd)?; let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?; dirfd @@ -512,7 +513,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { new_fd: types::Fd, new_path: &GuestPtr<'_, str>, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_SOURCE); + let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_SOURCE); let old_entry = self.get_entry(old_fd)?; let (old_dirfd, old_path) = path::get( &old_entry, @@ -521,7 +522,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { old_path, false, )?; - let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_TARGET); + let required_rights = HandleRights::from_base(types::Rights::PATH_LINK_TARGET); let new_entry = self.get_entry(new_fd)?; let (new_dirfd, new_path) = path::get( &new_entry, @@ -549,7 +550,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { fdflags: types::Fdflags, ) -> Result { let needed_rights = path::open_rights( - &EntryRights::new(fs_rights_base, fs_rights_inheriting), + &HandleRights::new(fs_rights_base, fs_rights_inheriting), oflags, fdflags, ); @@ -577,14 +578,14 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { write ); let fd = dirfd.openat(&path, read, write, oflags, fdflags)?; - let fe = Entry::from(EntryHandle::from(fd))?; + let entry = Entry::new(EntryHandle::from(fd)); // We need to manually deny the rights which are not explicitly requested // because Entry::from will assign maximal consistent rights. - let mut rights = fe.rights.get(); + let mut rights = entry.get_rights(); rights.base &= fs_rights_base; rights.inheriting &= fs_rights_inheriting; - fe.rights.set(rights); - let guest_fd = self.insert_entry(fe)?; + entry.set_rights(rights); + let guest_fd = self.insert_entry(entry)?; Ok(guest_fd) } @@ -595,7 +596,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { buf: &GuestPtr, buf_len: types::Size, ) -> Result { - let required_rights = EntryRights::from_base(types::Rights::PATH_READLINK); + let required_rights = HandleRights::from_base(types::Rights::PATH_READLINK); let entry = self.get_entry(dirfd)?; let (dirfd, path) = path::get( &entry, @@ -615,7 +616,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY); + let required_rights = HandleRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY); let entry = self.get_entry(dirfd)?; let (dirfd, path) = path::get( &entry, @@ -634,7 +635,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { new_fd: types::Fd, new_path: &GuestPtr<'_, str>, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_SOURCE); + let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_SOURCE); let entry = self.get_entry(old_fd)?; let (old_dirfd, old_path) = path::get( &entry, @@ -643,7 +644,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { old_path, true, )?; - let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_TARGET); + let required_rights = HandleRights::from_base(types::Rights::PATH_RENAME_TARGET); let entry = self.get_entry(new_fd)?; let (new_dirfd, new_path) = path::get( &entry, @@ -661,7 +662,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { dirfd: types::Fd, new_path: &GuestPtr<'_, str>, ) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::PATH_SYMLINK); + let required_rights = HandleRights::from_base(types::Rights::PATH_SYMLINK); let entry = self.get_entry(dirfd)?; let (new_fd, new_path) = path::get( &entry, @@ -680,7 +681,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { - let required_rights = EntryRights::from_base(types::Rights::PATH_UNLINK_FILE); + let required_rights = HandleRights::from_base(types::Rights::PATH_UNLINK_FILE); let entry = self.get_entry(dirfd)?; let (dirfd, path) = path::get( &entry, @@ -740,7 +741,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } types::SubscriptionU::FdRead(fd_read) => { let fd = fd_read.file_descriptor; - let required_rights = EntryRights::from_base( + let required_rights = HandleRights::from_base( types::Rights::FD_READ | types::Rights::POLL_FD_READWRITE, ); let entry = match self.get_entry(fd) { @@ -766,7 +767,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } types::SubscriptionU::FdWrite(fd_write) => { let fd = fd_write.file_descriptor; - let required_rights = EntryRights::from_base( + let required_rights = HandleRights::from_base( types::Rights::FD_WRITE | types::Rights::POLL_FD_READWRITE, ); let entry = match self.get_entry(fd) { diff --git a/crates/wasi-common/src/sys/mod.rs b/crates/wasi-common/src/sys/mod.rs index 61ed66700f..bca99f146d 100644 --- a/crates/wasi-common/src/sys/mod.rs +++ b/crates/wasi-common/src/sys/mod.rs @@ -1,6 +1,9 @@ pub(crate) mod clock; pub(crate) mod fd; -pub(crate) mod oshandle; +pub(crate) mod osdir; +pub(crate) mod osfile; +pub(crate) mod osother; +pub(crate) mod stdio; use cfg_if::cfg_if; @@ -20,3 +23,65 @@ cfg_if! { pub(crate) use sys_impl::path; pub(crate) use sys_impl::poll; + +use super::handle::Handle; +use crate::wasi::types; +use osdir::OsDir; +use osfile::OsFile; +use osother::OsOther; +use std::convert::TryFrom; +use std::fs::File; +use std::io; +use std::mem::ManuallyDrop; +use stdio::{Stderr, Stdin, Stdout}; +use sys_impl::get_file_type; + +pub(crate) trait AsFile { + fn as_file(&self) -> io::Result>; +} + +impl AsFile for dyn Handle + 'static { + fn as_file(&self) -> io::Result> { + if let Some(file) = self.as_any().downcast_ref::() { + file.as_file() + } else if let Some(dir) = self.as_any().downcast_ref::() { + dir.as_file() + } else if let Some(stdin) = self.as_any().downcast_ref::() { + stdin.as_file() + } else if let Some(stdout) = self.as_any().downcast_ref::() { + stdout.as_file() + } else if let Some(stderr) = self.as_any().downcast_ref::() { + stderr.as_file() + } else if let Some(other) = self.as_any().downcast_ref::() { + other.as_file() + } else { + log::error!("tried to make std::fs::File from non-OS handle"); + Err(io::Error::from_raw_os_error(libc::EBADF)) + } + } +} + +impl TryFrom for Box { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let file_type = get_file_type(&file)?; + match file_type { + types::Filetype::RegularFile => { + let handle = OsFile::try_from(file)?; + log::debug!("Created new instance of OsFile: {:?}", handle); + Ok(Box::new(handle)) + } + types::Filetype::Directory => { + let handle = OsDir::try_from(file)?; + log::debug!("Created new instance of OsDir: {:?}", handle); + Ok(Box::new(handle)) + } + _ => { + let handle = OsOther::try_from(file)?; + log::debug!("Created new instance of OsOther: {:?}", handle); + Ok(Box::new(handle)) + } + } + } +} diff --git a/crates/wasi-common/src/sys/osdir.rs b/crates/wasi-common/src/sys/osdir.rs new file mode 100644 index 0000000000..29a69e0c10 --- /dev/null +++ b/crates/wasi-common/src/sys/osdir.rs @@ -0,0 +1,129 @@ +use super::sys_impl::oshandle::RawOsHandle; +use super::{fd, path, AsFile}; +use crate::handle::{Handle, HandleRights}; +use crate::wasi::{types, Errno, Result}; +use log::{debug, error}; +use std::any::Any; +use std::io; +use std::ops::Deref; + +// TODO could this be cleaned up? +// The actual `OsDir` struct is OS-dependent, therefore we delegate +// its definition to OS-specific modules. +pub(crate) use super::sys_impl::osdir::OsDir; + +impl Deref for OsDir { + type Target = RawOsHandle; + + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl Handle for OsDir { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { + let handle = self.handle.try_clone()?; + let new = Self::new(self.rights.get(), handle)?; + Ok(Box::new(new)) + } + fn get_file_type(&self) -> types::Filetype { + types::Filetype::Directory + } + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, rights: HandleRights) { + self.rights.set(rights) + } + // FdOps + fn fdstat_get(&self) -> Result { + fd::fdstat_get(&*self.as_file()?) + } + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { + if let Some(new_file) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? { + self.handle.update_from(new_file); + } + Ok(()) + } + fn filestat_get(&self) -> Result { + fd::filestat_get(&*self.as_file()?) + } + fn filestat_set_times( + &self, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<()> { + fd::filestat_set_times(&*self.as_file()?, atim, mtim, fst_flags) + } + fn readdir<'a>( + &'a self, + cookie: types::Dircookie, + ) -> Result> + 'a>> { + fd::readdir(self, cookie) + } + // PathOps + fn create_directory(&self, path: &str) -> Result<()> { + path::create_directory(self, path) + } + fn openat( + &self, + path: &str, + read: bool, + write: bool, + oflags: types::Oflags, + fd_flags: types::Fdflags, + ) -> Result> { + path::open(self, path, read, write, oflags, fd_flags) + } + fn link( + &self, + old_path: &str, + new_handle: Box, + new_path: &str, + follow: bool, + ) -> Result<()> { + let new_handle = match new_handle.as_any().downcast_ref::() { + None => { + error!("Tried to link with handle that's not an OsDir"); + return Err(Errno::Badf); + } + Some(handle) => handle, + }; + path::link(self, old_path, new_handle, new_path, follow) + } + fn symlink(&self, old_path: &str, new_path: &str) -> Result<()> { + path::symlink(old_path, self, new_path) + } + fn readlink(&self, path: &str, buf: &mut [u8]) -> Result { + path::readlink(self, path, buf) + } + fn readlinkat(&self, path: &str) -> Result { + path::readlinkat(self, path) + } + fn rename(&self, old_path: &str, new_handle: Box, new_path: &str) -> Result<()> { + let new_handle = match new_handle.as_any().downcast_ref::() { + None => { + error!("Tried to rename with handle that's not an OsDir"); + return Err(Errno::Badf); + } + Some(handle) => handle, + }; + debug!("rename (old_dirfd, old_path)=({:?}, {:?})", self, old_path); + debug!( + "rename (new_dirfd, new_path)=({:?}, {:?})", + new_handle, new_path + ); + path::rename(self, old_path, new_handle, new_path) + } + fn remove_directory(&self, path: &str) -> Result<()> { + debug!("remove_directory (dirfd, path)=({:?}, {:?})", self, path); + path::remove_directory(self, path) + } + fn unlink_file(&self, path: &str) -> Result<()> { + path::unlink_file(self, path) + } +} diff --git a/crates/wasi-common/src/sys/osfile.rs b/crates/wasi-common/src/sys/osfile.rs new file mode 100644 index 0000000000..a3491f5432 --- /dev/null +++ b/crates/wasi-common/src/sys/osfile.rs @@ -0,0 +1,133 @@ +use super::sys_impl::oshandle::RawOsHandle; +use super::{fd, AsFile}; +use crate::handle::{Handle, HandleRights}; +use crate::wasi::{types, Errno, Result}; +use std::any::Any; +use std::cell::Cell; +use std::fs::File; +use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::ops::Deref; + +#[derive(Debug)] +pub(crate) struct OsFile { + rights: Cell, + handle: RawOsHandle, +} + +impl OsFile { + pub(super) fn new(rights: HandleRights, handle: RawOsHandle) -> Self { + let rights = Cell::new(rights); + Self { rights, handle } + } +} + +impl Deref for OsFile { + type Target = RawOsHandle; + + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl Handle for OsFile { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { + let handle = self.handle.try_clone()?; + let rights = self.rights.clone(); + Ok(Box::new(Self { rights, handle })) + } + fn get_file_type(&self) -> types::Filetype { + types::Filetype::RegularFile + } + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, rights: HandleRights) { + self.rights.set(rights) + } + // FdOps + fn advise( + &self, + advice: types::Advice, + offset: types::Filesize, + len: types::Filesize, + ) -> Result<()> { + fd::advise(self, advice, offset, len) + } + fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> { + let fd = self.as_file()?; + 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(()) + } + fn datasync(&self) -> Result<()> { + self.as_file()?.sync_data()?; + Ok(()) + } + fn fdstat_get(&self) -> Result { + fd::fdstat_get(&*self.as_file()?) + } + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { + if let Some(new_handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? { + self.handle.update_from(new_handle); + } + Ok(()) + } + fn filestat_get(&self) -> Result { + fd::filestat_get(&*self.as_file()?) + } + fn filestat_set_size(&self, size: types::Filesize) -> Result<()> { + self.as_file()?.set_len(size)?; + Ok(()) + } + fn filestat_set_times( + &self, + atim: types::Timestamp, + mtim: types::Timestamp, + fst_flags: types::Fstflags, + ) -> Result<()> { + fd::filestat_set_times(&*self.as_file()?, atim, mtim, fst_flags) + } + fn preadv(&self, buf: &mut [io::IoSliceMut], offset: u64) -> Result { + let mut fd: &File = &*self.as_file()?; + let cur_pos = fd.seek(SeekFrom::Current(0))?; + fd.seek(SeekFrom::Start(offset))?; + let nread = self.read_vectored(buf)?; + fd.seek(SeekFrom::Start(cur_pos))?; + Ok(nread) + } + fn pwritev(&self, buf: &[io::IoSlice], offset: u64) -> Result { + let mut fd: &File = &*self.as_file()?; + let cur_pos = fd.seek(SeekFrom::Current(0))?; + fd.seek(SeekFrom::Start(offset))?; + let nwritten = self.write_vectored(&buf)?; + fd.seek(SeekFrom::Start(cur_pos))?; + Ok(nwritten) + } + fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { + let nread = self.as_file()?.read_vectored(iovs)?; + Ok(nread) + } + fn seek(&self, offset: SeekFrom) -> Result { + let pos = self.as_file()?.seek(offset)?; + Ok(pos) + } + fn sync(&self) -> Result<()> { + self.as_file()?.sync_all()?; + Ok(()) + } + fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { + let nwritten = self.as_file()?.write_vectored(&iovs)?; + Ok(nwritten) + } +} diff --git a/crates/wasi-common/src/sys/oshandle.rs b/crates/wasi-common/src/sys/oshandle.rs deleted file mode 100644 index eeff049cce..0000000000 --- a/crates/wasi-common/src/sys/oshandle.rs +++ /dev/null @@ -1,277 +0,0 @@ -use super::{fd, path}; -use crate::entry::EntryRights; -use crate::handle::Handle; -use crate::sandboxed_tty_writer::SandboxedTTYWriter; -use crate::wasi::{types, Errno, Result}; -use log::{debug, error}; -use std::any::Any; -use std::fs::File; -use std::io::{self, Read, Seek, SeekFrom, Write}; -use std::mem::ManuallyDrop; - -pub(crate) use super::sys_impl::oshandle::*; - -#[derive(Debug)] -pub(crate) enum OsHandle { - OsFile(OsFile), - Stdin, - Stdout, - Stderr, -} - -impl OsHandle { - pub(crate) fn as_os_file(&self) -> Result<&OsFile> { - match self { - Self::OsFile(fd) => Ok(fd), - _ => Err(Errno::Badf), - } - } - - pub(crate) fn stdin() -> Self { - Self::Stdin - } - - pub(crate) fn stdout() -> Self { - Self::Stdout - } - - pub(crate) fn stderr() -> Self { - Self::Stderr - } -} - -pub(crate) trait AsFile { - fn as_file(&self) -> ManuallyDrop; -} - -pub(crate) trait OsHandleExt: Sized { - /// Returns the file type. - fn get_file_type(&self) -> io::Result; - /// Returns the set of all possible rights that are both relevant for the file - /// type and consistent with the open mode. - fn get_rights(&self, filetype: types::Filetype) -> io::Result; - fn from_null() -> io::Result; -} - -impl From for OsHandle { - fn from(file: OsFile) -> Self { - Self::OsFile(file) - } -} - -impl Handle for OsHandle { - fn as_any(&self) -> &dyn Any { - self - } - fn try_clone(&self) -> io::Result> { - let new_handle = match self { - Self::OsFile(file) => Self::OsFile(file.try_clone()?), - Self::Stdin => Self::Stdin, - Self::Stdout => Self::Stdout, - Self::Stderr => Self::Stderr, - }; - Ok(Box::new(new_handle)) - } - fn get_file_type(&self) -> io::Result { - ::get_file_type(self) - } - - fn get_rights(&self) -> io::Result { - ::get_rights(self, ::get_file_type(self)?) - } - // FdOps - fn advise( - &self, - advice: types::Advice, - offset: types::Filesize, - len: types::Filesize, - ) -> Result<()> { - fd::advise(self.as_os_file()?, advice, offset, len) - } - fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> { - let fd = self.as_file(); - 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(()) - } - fn datasync(&self) -> Result<()> { - self.as_file().sync_data()?; - Ok(()) - } - fn fdstat_get(&self) -> Result { - fd::fdstat_get(&self.as_file()) - } - fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { - if let Some(new_file) = fd::fdstat_set_flags(&self.as_file(), fdflags)? { - // If we don't deal with OsFile, then something went wrong, and we - // should fail. On the other hand, is that even possible? - self.as_os_file()?.update_from(new_file); - } - Ok(()) - } - fn filestat_get(&self) -> Result { - fd::filestat_get(&self.as_file()) - } - fn filestat_set_size(&self, size: types::Filesize) -> Result<()> { - self.as_os_file()?.as_file().set_len(size)?; - Ok(()) - } - fn filestat_set_times( - &self, - atim: types::Timestamp, - mtim: types::Timestamp, - fst_flags: types::Fstflags, - ) -> Result<()> { - fd::filestat_set_times(&self.as_file(), atim, mtim, fst_flags) - } - fn preadv(&self, buf: &mut [io::IoSliceMut], offset: u64) -> Result { - let mut fd: &File = &self.as_os_file()?.as_file(); - let cur_pos = fd.seek(SeekFrom::Current(0))?; - fd.seek(SeekFrom::Start(offset))?; - let nread = self.read_vectored(buf)?; - fd.seek(SeekFrom::Start(cur_pos))?; - Ok(nread) - } - fn pwritev(&self, buf: &[io::IoSlice], offset: u64) -> Result { - let mut fd: &File = &self.as_os_file()?.as_file(); - let cur_pos = fd.seek(SeekFrom::Current(0))?; - fd.seek(SeekFrom::Start(offset))?; - let nwritten = self.write_vectored(&buf, false)?; - fd.seek(SeekFrom::Start(cur_pos))?; - Ok(nwritten) - } - fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { - let nread = match self { - Self::OsFile(file) => file.as_file().read_vectored(iovs)?, - Self::Stdin => io::stdin().read_vectored(iovs)?, - _ => return Err(Errno::Badf), - }; - Ok(nread) - } - fn readdir<'a>( - &'a self, - cookie: types::Dircookie, - ) -> Result> + 'a>> { - fd::readdir(self.as_os_file()?, cookie) - } - fn seek(&self, offset: SeekFrom) -> Result { - let pos = self.as_os_file()?.as_file().seek(offset)?; - Ok(pos) - } - fn sync(&self) -> Result<()> { - self.as_os_file()?.as_file().sync_all()?; - Ok(()) - } - fn write_vectored(&self, iovs: &[io::IoSlice], isatty: bool) -> Result { - let nwritten = match self { - Self::OsFile(file) => { - let mut file: &File = &file.as_file(); - if isatty { - SandboxedTTYWriter::new(&mut file).write_vectored(&iovs)? - } else { - file.write_vectored(&iovs)? - } - } - Self::Stdin => return Err(Errno::Badf), - Self::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. - Self::Stderr => SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?, - }; - Ok(nwritten) - } - // PathOps - fn create_directory(&self, path: &str) -> Result<()> { - path::create_directory(self.as_os_file()?, path) - } - fn openat( - &self, - path: &str, - read: bool, - write: bool, - oflags: types::Oflags, - fd_flags: types::Fdflags, - ) -> Result> { - let handle = path::open(self.as_os_file()?, path, read, write, oflags, fd_flags)?; - Ok(Box::new(handle)) - } - fn link( - &self, - old_path: &str, - new_handle: Box, - new_path: &str, - follow: bool, - ) -> Result<()> { - let new_handle = match new_handle.as_any().downcast_ref::() { - None => { - error!("Tried to link OS resource with Virtual"); - return Err(Errno::Badf); - } - Some(handle) => handle, - }; - path::link( - self.as_os_file()?, - old_path, - new_handle.as_os_file()?, - new_path, - follow, - ) - } - fn symlink(&self, old_path: &str, new_path: &str) -> Result<()> { - path::symlink(old_path, self.as_os_file()?, new_path) - } - fn readlink(&self, path: &str, buf: &mut [u8]) -> Result { - path::readlink(self.as_os_file()?, path, buf) - } - fn readlinkat(&self, path: &str) -> Result { - path::readlinkat(self.as_os_file()?, path) - } - fn rename(&self, old_path: &str, new_handle: Box, new_path: &str) -> Result<()> { - let new_handle = match new_handle.as_any().downcast_ref::() { - None => { - error!("Tried to link OS resource with Virtual"); - return Err(Errno::Badf); - } - Some(handle) => handle, - }; - debug!("rename (old_dirfd, old_path)=({:?}, {:?})", self, old_path); - debug!( - "rename (new_dirfd, new_path)=({:?}, {:?})", - new_handle, new_path - ); - path::rename( - self.as_os_file()?, - old_path, - new_handle.as_os_file()?, - new_path, - ) - } - fn remove_directory(&self, path: &str) -> Result<()> { - debug!("remove_directory (dirfd, path)=({:?}, {:?})", self, path); - path::remove_directory(self.as_os_file()?, path) - } - fn unlink_file(&self, path: &str) -> Result<()> { - path::unlink_file(self.as_os_file()?, path) - } -} diff --git a/crates/wasi-common/src/sys/osother.rs b/crates/wasi-common/src/sys/osother.rs new file mode 100644 index 0000000000..de30a4f2dd --- /dev/null +++ b/crates/wasi-common/src/sys/osother.rs @@ -0,0 +1,95 @@ +use super::sys_impl::oshandle::RawOsHandle; +use super::{fd, AsFile}; +use crate::handle::{Handle, HandleRights}; +use crate::sandboxed_tty_writer::SandboxedTTYWriter; +use crate::wasi::types::{self, Filetype}; +use crate::wasi::Result; +use std::any::Any; +use std::cell::Cell; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::ops::Deref; + +pub(crate) trait OsOtherExt { + /// Create `OsOther` as `dyn Handle` from null device. + fn from_null() -> io::Result>; +} + +/// `OsOther` is something of a catch-all for everything not covered with the specific handle +/// types (`OsFile`, `OsDir`, `Stdio`). It currently encapsulates handles such as OS pipes, +/// sockets, streams, etc. As such, when redirecting stdio within `WasiCtxBuilder`, the redirected +/// pipe should be encapsulated within this instance _and not_ `OsFile` which represents a regular +/// OS file. +#[derive(Debug)] +pub(crate) struct OsOther { + file_type: Filetype, + rights: Cell, + handle: RawOsHandle, +} + +impl OsOther { + pub(super) fn new(file_type: Filetype, rights: HandleRights, handle: RawOsHandle) -> Self { + let rights = Cell::new(rights); + Self { + file_type, + rights, + handle, + } + } +} + +impl Deref for OsOther { + type Target = RawOsHandle; + + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl Handle for OsOther { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { + let file_type = self.file_type; + let handle = self.handle.try_clone()?; + let rights = self.rights.clone(); + Ok(Box::new(Self { + file_type, + rights, + handle, + })) + } + fn get_file_type(&self) -> Filetype { + self.file_type + } + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, new_rights: HandleRights) { + self.rights.set(new_rights) + } + // FdOps + fn fdstat_get(&self) -> Result { + fd::fdstat_get(&*self.as_file()?) + } + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { + if let Some(handle) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? { + self.handle.update_from(handle); + } + Ok(()) + } + fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { + let nread = self.as_file()?.read_vectored(iovs)?; + Ok(nread) + } + fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { + let mut fd: &File = &*self.as_file()?; + let nwritten = if self.is_tty() { + SandboxedTTYWriter::new(&mut fd).write_vectored(&iovs)? + } else { + fd.write_vectored(iovs)? + }; + Ok(nwritten) + } +} diff --git a/crates/wasi-common/src/sys/stdio.rs b/crates/wasi-common/src/sys/stdio.rs new file mode 100644 index 0000000000..bbd2339ed0 --- /dev/null +++ b/crates/wasi-common/src/sys/stdio.rs @@ -0,0 +1,176 @@ +// The reason we have a separate Stdio wrappers is to correctly facilitate redirects on Windows. +// To elaborate further, in POSIX, we can get a stdio handle by opening a specific fd {0,1,2}. +// On Windows however, we need to issue a syscall that's separate from standard Windows "open" +// to get a console handle, and this is GetStdHandle. This is exactly what Rust does and what +// is wrapped inside their Stdio object in the libstd. We wrap it here as well because of this +// nuance on Windows: +// +// The standard handles of a process may be redirected by a call to SetStdHandle, in which +// case GetStdHandle returns the redirected handle. +// +// The MSDN also says this however: +// +// If the standard handles have been redirected, you can specify the CONIN$ value in a call +// to the CreateFile function to get a handle to a console's input buffer. Similarly, you +// can specify the CONOUT$ value to get a handle to a console's active screen buffer. +// +// TODO it might worth re-investigating the suitability of this type on Windows. + +use super::{fd, AsFile}; +use crate::handle::{Handle, HandleRights}; +use crate::sandboxed_tty_writer::SandboxedTTYWriter; +use crate::wasi::types::{self, Filetype}; +use crate::wasi::Result; +use std::any::Any; +use std::cell::Cell; +use std::io::{self, Read, Write}; + +pub(crate) trait StdinExt: Sized { + /// Create `Stdin` from `io::stdin`. + fn stdin() -> io::Result>; +} + +#[derive(Debug, Clone)] +pub(crate) struct Stdin { + pub(crate) file_type: Filetype, + pub(crate) rights: Cell, +} + +impl Handle for Stdin { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { + Ok(Box::new(self.clone())) + } + fn get_file_type(&self) -> Filetype { + self.file_type + } + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, new_rights: HandleRights) { + self.rights.set(new_rights) + } + // FdOps + fn fdstat_get(&self) -> Result { + fd::fdstat_get(&*self.as_file()?) + } + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { + if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? { + // OK, this means we should somehow update the underlying os handle, + // and we can't do that with `std::io::std{in, out, err}`, so we'll + // panic for now. + panic!("Tried updating Fdflags on Stdio handle by re-opening as file!"); + } + Ok(()) + } + fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { + let nread = io::stdin().read_vectored(iovs)?; + Ok(nread) + } +} + +pub(crate) trait StdoutExt: Sized { + /// Create `Stdout` from `io::stdout`. + fn stdout() -> io::Result>; +} + +#[derive(Debug, Clone)] +pub(crate) struct Stdout { + pub(crate) file_type: Filetype, + pub(crate) rights: Cell, +} + +impl Handle for Stdout { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { + Ok(Box::new(self.clone())) + } + fn get_file_type(&self) -> Filetype { + self.file_type + } + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, new_rights: HandleRights) { + self.rights.set(new_rights) + } + // FdOps + fn fdstat_get(&self) -> Result { + fd::fdstat_get(&*self.as_file()?) + } + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { + if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? { + // OK, this means we should somehow update the underlying os handle, + // and we can't do that with `std::io::std{in, out, err}`, so we'll + // panic for now. + panic!("Tried updating Fdflags on Stdio handle by re-opening as file!"); + } + Ok(()) + } + fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { + // lock for the duration of the scope + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + let nwritten = if self.is_tty() { + SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)? + } else { + stdout.write_vectored(iovs)? + }; + stdout.flush()?; + Ok(nwritten) + } +} + +pub(crate) trait StderrExt: Sized { + /// Create `Stderr` from `io::stderr`. + fn stderr() -> io::Result>; +} + +#[derive(Debug, Clone)] +pub(crate) struct Stderr { + pub(crate) file_type: Filetype, + pub(crate) rights: Cell, +} + +impl Handle for Stderr { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { + Ok(Box::new(self.clone())) + } + fn get_file_type(&self) -> Filetype { + self.file_type + } + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, new_rights: HandleRights) { + self.rights.set(new_rights) + } + // FdOps + fn fdstat_get(&self) -> Result { + fd::fdstat_get(&*self.as_file()?) + } + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { + if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? { + // OK, this means we should somehow update the underlying os handle, + // and we can't do that with `std::io::std{in, out, err}`, so we'll + // panic for now. + panic!("Tried updating Fdflags on Stdio handle by re-opening as file!"); + } + Ok(()) + } + fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { + // 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. + let nwritten = SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?; + Ok(nwritten) + } +} diff --git a/crates/wasi-common/src/sys/unix/bsd/mod.rs b/crates/wasi-common/src/sys/unix/bsd/mod.rs index a4a1882453..a521318cdf 100644 --- a/crates/wasi-common/src/sys/unix/bsd/mod.rs +++ b/crates/wasi-common/src/sys/unix/bsd/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod osfile; +pub(crate) mod osdir; 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/osdir.rs b/crates/wasi-common/src/sys/unix/bsd/osdir.rs new file mode 100644 index 0000000000..0726ad2ef6 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/bsd/osdir.rs @@ -0,0 +1,46 @@ +use crate::handle::HandleRights; +use crate::sys::sys_impl::oshandle::RawOsHandle; +use crate::wasi::Result; +use std::cell::{Cell, RefCell, RefMut}; +use std::io; +use yanix::dir::Dir; + +#[derive(Debug)] +pub(crate) struct OsDir { + pub(crate) rights: Cell, + pub(crate) handle: RawOsHandle, + // When the client makes a `fd_readdir` syscall on this descriptor, + // we will need to cache the `libc::DIR` pointer manually in order + // to be able to seek on it later. While on Linux, this is handled + // by the OS, BSD Unixes require the client to do this caching. + // + // This comes directly from the BSD man pages on `readdir`: + // > Values returned by telldir() are good only for the lifetime + // > of the DIR pointer, dirp, from which they are derived. + // > If the directory is closed and then reopened, prior values + // > returned by telldir() will no longer be valid. + stream_ptr: RefCell, +} + +impl OsDir { + pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result { + let rights = Cell::new(rights); + // We need to duplicate the handle, 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 stream_ptr = Dir::from(handle.try_clone()?)?; + let stream_ptr = RefCell::new(stream_ptr); + Ok(Self { + rights, + handle, + stream_ptr, + }) + } + /// Returns the `Dir` stream pointer associated with this `OsDir`. + pub(crate) fn stream_ptr(&self) -> Result> { + Ok(self.stream_ptr.borrow_mut()) + } +} diff --git a/crates/wasi-common/src/sys/unix/bsd/osfile.rs b/crates/wasi-common/src/sys/unix/bsd/osfile.rs deleted file mode 100644 index 0ead556838..0000000000 --- a/crates/wasi-common/src/sys/unix/bsd/osfile.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::sys::oshandle::AsFile; -use crate::wasi::Result; -use std::cell::{Cell, RefCell, RefMut}; -use std::fs::File; -use std::io; -use std::mem::ManuallyDrop; -use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use yanix::dir::Dir; - -#[derive(Debug)] -pub(crate) struct OsFile { - fd: Cell, - // In case that this `OsHandle` actually refers to a directory, - // when the client makes a `fd_readdir` syscall on this descriptor, - // we will need to cache the `libc::DIR` pointer manually in order - // to be able to seek on it later. While on Linux, this is handled - // by the OS, BSD Unixes require the client to do this caching. - // - // This comes directly from the BSD man pages on `readdir`: - // > Values returned by telldir() are good only for the lifetime - // > of the DIR pointer, dirp, from which they are derived. - // > If the directory is closed and then reopened, prior values - // > returned by telldir() will no longer be valid. - dir: RefCell>, -} - -impl OsFile { - /// Consumes `other` taking the ownership of the underlying - /// `RawFd` file descriptor. - /// - /// Note that the state of `Dir` stream pointer *will* not be carried - /// across from `other` to `self`. - pub(crate) fn update_from(&self, other: Self) { - let new_fd = other.into_raw_fd(); - let old_fd = self.fd.get(); - self.fd.set(new_fd); - // We need to remember to close the old_fd. - unsafe { - File::from_raw_fd(old_fd); - } - } - /// Clones `self` uninitializing the `Dir` stream pointer - /// (if any). - pub(crate) fn try_clone(&self) -> io::Result { - let fd = self.as_file().try_clone()?; - Ok(Self { - fd: Cell::new(fd.into_raw_fd()), - dir: RefCell::new(None), - }) - } - /// Returns the `Dir` stream pointer associated with - /// this instance. - /// - /// Initializes the `Dir` stream pointer if `None`. - pub(crate) fn dir_stream(&self) -> Result> { - if self.dir.borrow().is_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 file = self.try_clone()?; - let d = Dir::from(file)?; - *self.dir.borrow_mut() = Some(d); - } - Ok(RefMut::map(self.dir.borrow_mut(), |dir| { - dir.as_mut().unwrap() - })) - } -} - -impl Drop for OsFile { - fn drop(&mut self) { - unsafe { - File::from_raw_fd(self.as_raw_fd()); - } - } -} - -impl AsRawFd for OsFile { - fn as_raw_fd(&self) -> RawFd { - self.fd.get() - } -} - -impl FromRawFd for OsFile { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self { - fd: Cell::new(fd), - dir: RefCell::new(None), - } - } -} - -impl IntoRawFd for OsFile { - fn into_raw_fd(self) -> RawFd { - // We need to prevent dropping of the OsFile - let wrapped = ManuallyDrop::new(self); - wrapped.fd.get() - } -} - -impl AsFile for OsFile { - fn as_file(&self) -> ManuallyDrop { - let file = unsafe { File::from_raw_fd(self.fd.get()) }; - ManuallyDrop::new(file) - } -} diff --git a/crates/wasi-common/src/sys/unix/bsd/path.rs b/crates/wasi-common/src/sys/unix/bsd/path.rs index 872d3f25cd..8dff951cb9 100644 --- a/crates/wasi-common/src/sys/unix/bsd/path.rs +++ b/crates/wasi-common/src/sys/unix/bsd/path.rs @@ -1,8 +1,8 @@ -use super::osfile::OsFile; +use crate::sys::osdir::OsDir; use crate::wasi::{Errno, Result}; use std::os::unix::prelude::AsRawFd; -pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { +pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> { use yanix::file::{unlinkat, AtFlag}; match unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty()) } { Err(err) => { @@ -35,7 +35,7 @@ pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { } } -pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Result<()> { +pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path: &str) -> Result<()> { use yanix::file::{fstatat, symlinkat, AtFlag}; log::debug!("path_symlink old_path = {:?}", old_path); @@ -69,9 +69,9 @@ pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Res } pub(crate) fn rename( - old_dirfd: &OsFile, + old_dirfd: &OsDir, old_path: &str, - new_dirfd: &OsFile, + new_dirfd: &OsDir, new_path: &str, ) -> Result<()> { use yanix::file::{fstatat, renameat, AtFlag}; diff --git a/crates/wasi-common/src/sys/unix/emscripten/mod.rs b/crates/wasi-common/src/sys/unix/emscripten/mod.rs index 151d735025..ed0472e45d 100644 --- a/crates/wasi-common/src/sys/unix/emscripten/mod.rs +++ b/crates/wasi-common/src/sys/unix/emscripten/mod.rs @@ -1,5 +1,5 @@ -#[path = "../linux/osfile.rs"] -pub(crate) mod osfile; +#[path = "../linux/osdir.rs"] +pub(crate) mod osdir; #[path = "../linux/path.rs"] pub(crate) mod path; diff --git a/crates/wasi-common/src/sys/unix/fd.rs b/crates/wasi-common/src/sys/unix/fd.rs index e192bd44d8..83d717711c 100644 --- a/crates/wasi-common/src/sys/unix/fd.rs +++ b/crates/wasi-common/src/sys/unix/fd.rs @@ -1,4 +1,6 @@ -use super::oshandle::OsFile; +use super::oshandle::RawOsHandle; +use crate::sys::osdir::OsDir; +use crate::sys::osfile::OsFile; use crate::wasi::{self, types, Result}; use std::convert::TryInto; use std::fs::File; @@ -9,7 +11,7 @@ pub(crate) fn fdstat_get(fd: &File) -> Result { Ok(fdflags.into()) } -pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result> { +pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result> { unsafe { yanix::fcntl::set_status_flags(fd.as_raw_fd(), fdflags.into())? }; // We return None here to signal that the operation succeeded on the original // file descriptor and mutating the original WASI Descriptor is thus unnecessary. @@ -45,14 +47,14 @@ pub(crate) fn filestat_get(file: &File) -> Result { } pub(crate) fn readdir<'a>( - file: &'a OsFile, + dirfd: &'a OsDir, 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 = file.dir_stream()?; + let mut dir = dirfd.stream_ptr()?; // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, // new items may not be returned to the caller. diff --git a/crates/wasi-common/src/sys/unix/linux/mod.rs b/crates/wasi-common/src/sys/unix/linux/mod.rs index b587ed6b6f..d8bd3e7501 100644 --- a/crates/wasi-common/src/sys/unix/linux/mod.rs +++ b/crates/wasi-common/src/sys/unix/linux/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod osfile; +pub(crate) mod osdir; 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/osdir.rs b/crates/wasi-common/src/sys/unix/linux/osdir.rs new file mode 100644 index 0000000000..2fff99ec7b --- /dev/null +++ b/crates/wasi-common/src/sys/unix/linux/osdir.rs @@ -0,0 +1,35 @@ +use crate::handle::HandleRights; +use crate::sys::sys_impl::oshandle::RawOsHandle; +use crate::wasi::Result; +use std::cell::Cell; +use std::io; +use yanix::dir::Dir; + +#[derive(Debug)] +pub(crate) struct OsDir { + pub(crate) rights: Cell, + pub(crate) handle: RawOsHandle, +} + +impl OsDir { + pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result { + let rights = Cell::new(rights); + Ok(Self { rights, handle }) + } + /// Returns the `Dir` stream pointer associated with this `OsDir`. + pub(crate) fn stream_ptr(&self) -> Result> { + // We need to duplicate the handle, 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 file = self.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(file)?)) + } +} diff --git a/crates/wasi-common/src/sys/unix/linux/osfile.rs b/crates/wasi-common/src/sys/unix/linux/osfile.rs deleted file mode 100644 index 388f245dfa..0000000000 --- a/crates/wasi-common/src/sys/unix/linux/osfile.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::sys::oshandle::AsFile; -use crate::wasi::Result; -use std::cell::Cell; -use std::fs::File; -use std::io; -use std::mem::ManuallyDrop; -use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use yanix::dir::Dir; - -#[derive(Debug)] -pub(crate) struct OsFile(Cell); - -impl OsFile { - /// Consumes `other` taking the ownership of the underlying - /// `RawFd` file descriptor. - pub(crate) fn update_from(&self, other: Self) { - let new_fd = other.into_raw_fd(); - let old_fd = self.0.get(); - self.0.set(new_fd); - // We need to remember to close the old_fd. - unsafe { - File::from_raw_fd(old_fd); - } - } - /// Clones `self`. - pub(crate) fn try_clone(&self) -> io::Result { - let fd = self.as_file().try_clone()?; - Ok(Self(Cell::new(fd.into_raw_fd()))) - } - /// Returns the `Dir` stream pointer associated with - /// this instance. - pub(crate) fn dir_stream(&self) -> 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 file = self.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(file)?)) - } -} - -impl Drop for OsFile { - fn drop(&mut self) { - unsafe { - File::from_raw_fd(self.as_raw_fd()); - } - } -} - -impl AsRawFd for OsFile { - fn as_raw_fd(&self) -> RawFd { - self.0.get() - } -} - -impl FromRawFd for OsFile { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self(Cell::new(fd)) - } -} - -impl IntoRawFd for OsFile { - fn into_raw_fd(self) -> RawFd { - // We need to prevent dropping of the OsFile - let wrapped = ManuallyDrop::new(self); - wrapped.0.get() - } -} - -impl AsFile for OsFile { - fn as_file(&self) -> ManuallyDrop { - let file = unsafe { File::from_raw_fd(self.0.get()) }; - ManuallyDrop::new(file) - } -} diff --git a/crates/wasi-common/src/sys/unix/linux/path.rs b/crates/wasi-common/src/sys/unix/linux/path.rs index 02ee2b3fdd..611c57d22e 100644 --- a/crates/wasi-common/src/sys/unix/linux/path.rs +++ b/crates/wasi-common/src/sys/unix/linux/path.rs @@ -1,14 +1,14 @@ -use super::osfile::OsFile; +use crate::sys::osdir::OsDir; use crate::wasi::Result; use std::os::unix::prelude::AsRawFd; -pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { +pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> { use yanix::file::{unlinkat, AtFlag}; unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty())? }; Ok(()) } -pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Result<()> { +pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path: &str) -> Result<()> { use yanix::file::symlinkat; log::debug!("path_symlink old_path = {:?}", old_path); @@ -23,9 +23,9 @@ pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Res } pub(crate) fn rename( - old_dirfd: &OsFile, + old_dirfd: &OsDir, old_path: &str, - new_dirfd: &OsFile, + new_dirfd: &OsDir, new_path: &str, ) -> Result<()> { use yanix::file::renameat; diff --git a/crates/wasi-common/src/sys/unix/mod.rs b/crates/wasi-common/src/sys/unix/mod.rs index 8bad50fc79..e71635db04 100644 --- a/crates/wasi-common/src/sys/unix/mod.rs +++ b/crates/wasi-common/src/sys/unix/mod.rs @@ -1,8 +1,12 @@ pub(crate) mod clock; pub(crate) mod fd; +pub(crate) mod osdir; +pub(crate) mod osfile; pub(crate) mod oshandle; +pub(crate) mod osother; pub(crate) mod path; pub(crate) mod poll; +pub(crate) mod stdio; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { @@ -22,16 +26,105 @@ cfg_if::cfg_if! { } } -use crate::wasi::{types, Errno, Result}; +use crate::handle::HandleRights; +use crate::sys::AsFile; +use crate::wasi::{types, Errno, Result, RightsExt}; use std::convert::{TryFrom, TryInto}; use std::fs::File; use std::io; +use std::mem::ManuallyDrop; +use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd}; use std::path::Path; use yanix::clock::ClockId; use yanix::file::{AtFlag, OFlag}; pub(crate) use sys_impl::*; +impl AsFile for T { + fn as_file(&self) -> io::Result> { + let file = unsafe { File::from_raw_fd(self.as_raw_fd()) }; + Ok(ManuallyDrop::new(file)) + } +} + +pub(super) fn get_file_type(file: &File) -> io::Result { + let ft = file.metadata()?.file_type(); + let file_type = if ft.is_block_device() { + log::debug!("Host fd {:?} is a block device", file.as_raw_fd()); + types::Filetype::BlockDevice + } else if ft.is_char_device() { + log::debug!("Host fd {:?} is a char device", file.as_raw_fd()); + types::Filetype::CharacterDevice + } else if ft.is_dir() { + log::debug!("Host fd {:?} is a directory", file.as_raw_fd()); + types::Filetype::Directory + } else if ft.is_file() { + log::debug!("Host fd {:?} is a file", file.as_raw_fd()); + types::Filetype::RegularFile + } else if ft.is_socket() { + log::debug!("Host fd {:?} is a socket", file.as_raw_fd()); + use yanix::socket::{get_socket_type, SockType}; + match unsafe { get_socket_type(file.as_raw_fd())? } { + SockType::Datagram => types::Filetype::SocketDgram, + SockType::Stream => types::Filetype::SocketStream, + _ => return Err(io::Error::from_raw_os_error(libc::EINVAL)), + } + } else if ft.is_fifo() { + log::debug!("Host fd {:?} is a fifo", file.as_raw_fd()); + types::Filetype::Unknown + } else { + log::debug!("Host fd {:?} is unknown", file.as_raw_fd()); + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + }; + Ok(file_type) +} + +pub(super) fn get_rights(file: &File, file_type: &types::Filetype) -> io::Result { + use yanix::{fcntl, file::OFlag}; + let (base, inheriting) = match file_type { + types::Filetype::BlockDevice => ( + types::Rights::block_device_base(), + types::Rights::block_device_inheriting(), + ), + types::Filetype::CharacterDevice => { + use yanix::file::isatty; + if unsafe { isatty(file.as_raw_fd())? } { + (types::Rights::tty_base(), types::Rights::tty_base()) + } else { + ( + types::Rights::character_device_base(), + types::Rights::character_device_inheriting(), + ) + } + } + types::Filetype::SocketDgram | types::Filetype::SocketStream => ( + types::Rights::socket_base(), + types::Rights::socket_inheriting(), + ), + types::Filetype::SymbolicLink | types::Filetype::Unknown => ( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ), + types::Filetype::Directory => ( + types::Rights::directory_base(), + types::Rights::directory_inheriting(), + ), + types::Filetype::RegularFile => ( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ), + }; + let mut rights = HandleRights::new(base, inheriting); + let flags = unsafe { fcntl::get_status_flags(file.as_raw_fd())? }; + let accmode = flags & OFlag::ACCMODE; + if accmode == OFlag::RDONLY { + rights.base &= !types::Rights::FD_WRITE; + } else if accmode == OFlag::WRONLY { + rights.base &= !types::Rights::FD_READ; + } + Ok(rights) +} + pub fn preopen_dir>(path: P) -> io::Result { File::open(path) } diff --git a/crates/wasi-common/src/sys/unix/osdir.rs b/crates/wasi-common/src/sys/unix/osdir.rs new file mode 100644 index 0000000000..47b264208f --- /dev/null +++ b/crates/wasi-common/src/sys/unix/osdir.rs @@ -0,0 +1,39 @@ +use super::oshandle::RawOsHandle; +use crate::handle::HandleRights; +use crate::wasi::{types, RightsExt}; +use std::convert::TryFrom; +use std::fs::File; +use std::io; +use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd}; + +pub(crate) use super::sys_impl::osdir::OsDir; + +impl TryFrom for OsDir { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let ft = file.metadata()?.file_type(); + if !ft.is_dir() { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let rights = get_rights(&file)?; + let handle = unsafe { RawOsHandle::from_raw_fd(file.into_raw_fd()) }; + Self::new(rights, handle) + } +} + +fn get_rights(file: &File) -> io::Result { + use yanix::{fcntl, file::OFlag}; + let mut rights = HandleRights::new( + types::Rights::directory_base(), + types::Rights::directory_inheriting(), + ); + let flags = unsafe { fcntl::get_status_flags(file.as_raw_fd())? }; + let accmode = flags & OFlag::ACCMODE; + if accmode == OFlag::RDONLY { + rights.base &= !types::Rights::FD_WRITE; + } else if accmode == OFlag::WRONLY { + rights.base &= !types::Rights::FD_READ; + } + Ok(rights) +} diff --git a/crates/wasi-common/src/sys/unix/osfile.rs b/crates/wasi-common/src/sys/unix/osfile.rs new file mode 100644 index 0000000000..f4d3b6e854 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/osfile.rs @@ -0,0 +1,38 @@ +use super::oshandle::RawOsHandle; +use crate::handle::HandleRights; +use crate::sys::osfile::OsFile; +use crate::wasi::{types, RightsExt}; +use std::convert::TryFrom; +use std::fs::File; +use std::io; +use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd}; + +impl TryFrom for OsFile { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let ft = file.metadata()?.file_type(); + if !ft.is_file() { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let rights = get_rights(&file)?; + let handle = unsafe { RawOsHandle::from_raw_fd(file.into_raw_fd()) }; + Ok(Self::new(rights, handle)) + } +} + +fn get_rights(file: &File) -> io::Result { + use yanix::{fcntl, file::OFlag}; + let mut rights = HandleRights::new( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ); + let flags = unsafe { fcntl::get_status_flags(file.as_raw_fd())? }; + let accmode = flags & OFlag::ACCMODE; + if accmode == OFlag::RDONLY { + rights.base &= !types::Rights::FD_WRITE; + } else if accmode == OFlag::WRONLY { + rights.base &= !types::Rights::FD_READ; + } + Ok(rights) +} diff --git a/crates/wasi-common/src/sys/unix/oshandle.rs b/crates/wasi-common/src/sys/unix/oshandle.rs index 236ba07884..513e4b7787 100644 --- a/crates/wasi-common/src/sys/unix/oshandle.rs +++ b/crates/wasi-common/src/sys/unix/oshandle.rs @@ -1,123 +1,40 @@ -use crate::entry::EntryRights; -use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt}; -use crate::wasi::{types, RightsExt}; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::io; -use std::mem::ManuallyDrop; -use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, IntoRawFd, RawFd}; +use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -pub(crate) use super::sys_impl::osfile::*; +#[derive(Debug)] +pub(crate) struct RawOsHandle(File); -impl AsRawFd for OsHandle { +impl RawOsHandle { + /// Tries clone `self`. + pub(crate) fn try_clone(&self) -> io::Result { + let fd = self.0.try_clone()?; + Ok(unsafe { Self::from_raw_fd(fd.into_raw_fd()) }) + } + /// Consumes `other` taking the ownership of the underlying + /// `RawFd` file descriptor. + /// + /// Note that the state of `Dir` stream pointer *will* not be carried + /// across from `other` to `self`. + pub(crate) fn update_from(&self, _other: Self) { + panic!("RawOsHandle::update_from should never be issued on Unix!") + } +} + +impl AsRawFd for RawOsHandle { fn as_raw_fd(&self) -> RawFd { - match self { - Self::OsFile(file) => file.as_raw_fd(), - Self::Stdin => io::stdin().as_raw_fd(), - Self::Stdout => io::stdout().as_raw_fd(), - Self::Stderr => io::stderr().as_raw_fd(), - } + self.0.as_raw_fd() } } -impl AsFile for OsHandle { - fn as_file(&self) -> ManuallyDrop { - let file = unsafe { File::from_raw_fd(self.as_raw_fd()) }; - ManuallyDrop::new(file) +impl IntoRawFd for RawOsHandle { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() } } -impl From for OsHandle { - fn from(file: File) -> Self { - Self::from(unsafe { OsFile::from_raw_fd(file.into_raw_fd()) }) - } -} - -impl OsHandleExt for OsHandle { - fn get_file_type(&self) -> io::Result { - let file = self.as_file(); - let ft = file.metadata()?.file_type(); - let file_type = if ft.is_block_device() { - log::debug!("Host fd {:?} is a block device", self.as_raw_fd()); - types::Filetype::BlockDevice - } else if ft.is_char_device() { - log::debug!("Host fd {:?} is a char device", self.as_raw_fd()); - types::Filetype::CharacterDevice - } else if ft.is_dir() { - log::debug!("Host fd {:?} is a directory", self.as_raw_fd()); - types::Filetype::Directory - } else if ft.is_file() { - log::debug!("Host fd {:?} is a file", self.as_raw_fd()); - types::Filetype::RegularFile - } else if ft.is_socket() { - log::debug!("Host fd {:?} is a socket", self.as_raw_fd()); - use yanix::socket::{get_socket_type, SockType}; - match unsafe { get_socket_type(self.as_raw_fd())? } { - SockType::Datagram => types::Filetype::SocketDgram, - SockType::Stream => types::Filetype::SocketStream, - _ => return Err(io::Error::from_raw_os_error(libc::EINVAL)), - } - } else if ft.is_fifo() { - log::debug!("Host fd {:?} is a fifo", self.as_raw_fd()); - types::Filetype::Unknown - } else { - log::debug!("Host fd {:?} is unknown", self.as_raw_fd()); - return Err(io::Error::from_raw_os_error(libc::EINVAL)); - }; - - Ok(file_type) - } - - fn get_rights(&self, file_type: types::Filetype) -> io::Result { - use yanix::{fcntl, file::OFlag}; - let (base, inheriting) = match file_type { - types::Filetype::BlockDevice => ( - types::Rights::block_device_base(), - types::Rights::block_device_inheriting(), - ), - types::Filetype::CharacterDevice => { - use yanix::file::isatty; - if unsafe { isatty(self.as_raw_fd())? } { - (types::Rights::tty_base(), types::Rights::tty_base()) - } else { - ( - types::Rights::character_device_base(), - types::Rights::character_device_inheriting(), - ) - } - } - types::Filetype::Directory => ( - types::Rights::directory_base(), - types::Rights::directory_inheriting(), - ), - types::Filetype::RegularFile => ( - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ), - types::Filetype::SocketDgram | types::Filetype::SocketStream => ( - types::Rights::socket_base(), - types::Rights::socket_inheriting(), - ), - types::Filetype::SymbolicLink | types::Filetype::Unknown => ( - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ), - }; - let mut rights = EntryRights::new(base, inheriting); - let flags = unsafe { fcntl::get_status_flags(self.as_raw_fd())? }; - let accmode = flags & OFlag::ACCMODE; - if accmode == OFlag::RDONLY { - rights.base &= !types::Rights::FD_WRITE; - } else if accmode == OFlag::WRONLY { - rights.base &= !types::Rights::FD_READ; - } - Ok(rights) - } - - fn from_null() -> io::Result { - let file = OpenOptions::new() - .read(true) - .write(true) - .open("/dev/null")?; - Ok(Self::from(file)) +impl FromRawFd for RawOsHandle { + unsafe fn from_raw_fd(raw: RawFd) -> Self { + Self(File::from_raw_fd(raw)) } } diff --git a/crates/wasi-common/src/sys/unix/osother.rs b/crates/wasi-common/src/sys/unix/osother.rs new file mode 100644 index 0000000000..260c47db8f --- /dev/null +++ b/crates/wasi-common/src/sys/unix/osother.rs @@ -0,0 +1,34 @@ +use super::oshandle::RawOsHandle; +use super::{get_file_type, get_rights}; +use crate::handle::Handle; +use crate::sys::osother::{OsOther, OsOtherExt}; +use crate::wasi::types; +use std::convert::TryFrom; +use std::fs::{File, OpenOptions}; +use std::io; +use std::os::unix::prelude::{FromRawFd, IntoRawFd}; + +impl TryFrom for OsOther { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let file_type = get_file_type(&file)?; + if file_type == types::Filetype::RegularFile || file_type == types::Filetype::Directory { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let rights = get_rights(&file, &file_type)?; + let handle = unsafe { RawOsHandle::from_raw_fd(file.into_raw_fd()) }; + Ok(Self::new(file_type, rights, handle)) + } +} + +impl OsOtherExt for OsOther { + fn from_null() -> io::Result> { + let file = OpenOptions::new() + .read(true) + .write(true) + .open("/dev/null")?; + let file = Self::try_from(file)?; + Ok(Box::new(file)) + } +} diff --git a/crates/wasi-common/src/sys/unix/path.rs b/crates/wasi-common/src/sys/unix/path.rs index e52aa0f67f..9ffa5f7b60 100644 --- a/crates/wasi-common/src/sys/unix/path.rs +++ b/crates/wasi-common/src/sys/unix/path.rs @@ -1,8 +1,9 @@ -use super::oshandle::OsFile; -use crate::entry::EntryRights; -use crate::sys::oshandle::OsHandle; +use crate::handle::{Handle, HandleRights}; +use crate::sys::osdir::OsDir; use crate::wasi::{types, Errno, Result}; +use std::convert::TryFrom; use std::ffi::OsStr; +use std::fs::File; use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt}; use std::str; use yanix::file::OFlag; @@ -19,10 +20,10 @@ pub(crate) fn from_host>(s: S) -> Result { } pub(crate) fn open_rights( - input_rights: &EntryRights, + input_rights: &HandleRights, oflags: types::Oflags, fs_flags: types::Fdflags, -) -> EntryRights { +) -> HandleRights { // which rights are needed on the dirfd? let mut needed_base = types::Rights::PATH_OPEN; let mut needed_inheriting = input_rights.base | input_rights.inheriting; @@ -45,10 +46,10 @@ pub(crate) fn open_rights( needed_inheriting |= types::Rights::FD_SYNC; } - EntryRights::new(needed_base, needed_inheriting) + HandleRights::new(needed_base, needed_inheriting) } -pub(crate) fn readlinkat(dirfd: &OsFile, path: &str) -> Result { +pub(crate) fn readlinkat(dirfd: &OsDir, path: &str) -> Result { use std::os::unix::prelude::AsRawFd; use yanix::file::readlinkat; @@ -59,16 +60,16 @@ pub(crate) fn readlinkat(dirfd: &OsFile, path: &str) -> Result { Ok(path) } -pub(crate) fn create_directory(base: &OsFile, path: &str) -> Result<()> { +pub(crate) fn create_directory(base: &OsDir, 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( - old_dirfd: &OsFile, + old_dirfd: &OsDir, old_path: &str, - new_dirfd: &OsFile, + new_dirfd: &OsDir, new_path: &str, follow_symlinks: bool, ) -> Result<()> { @@ -91,13 +92,13 @@ pub(crate) fn link( } pub(crate) fn open( - dirfd: &OsFile, + dirfd: &OsDir, path: &str, read: bool, write: bool, oflags: types::Oflags, fs_flags: types::Fdflags, -) -> Result { +) -> Result> { use yanix::file::{fstatat, openat, AtFlag, FileType, Mode, OFlag}; let mut nix_all_oflags = if read && write { @@ -181,10 +182,12 @@ pub(crate) fn open( 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 { OsFile::from_raw_fd(new_fd) })) + let file = unsafe { File::from_raw_fd(new_fd) }; + let handle = >::try_from(file)?; + Ok(handle) } -pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result { +pub(crate) fn readlink(dirfd: &OsDir, path: &str, buf: &mut [u8]) -> Result { use std::cmp::min; use yanix::file::readlinkat; let read_link = unsafe { readlinkat(dirfd.as_raw_fd(), path)? }; @@ -196,7 +199,7 @@ pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result Result<()> { +pub(crate) fn remove_directory(dirfd: &OsDir, path: &str) -> Result<()> { use yanix::file::{unlinkat, AtFlag}; unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::REMOVEDIR)? }; Ok(()) diff --git a/crates/wasi-common/src/sys/unix/poll.rs b/crates/wasi-common/src/sys/unix/poll.rs index 7b5ffddded..b185bdb93c 100644 --- a/crates/wasi-common/src/sys/unix/poll.rs +++ b/crates/wasi-common/src/sys/unix/poll.rs @@ -1,6 +1,6 @@ -use super::super::oshandle::OsHandle; +use crate::entry::EntryHandle; use crate::poll::{ClockEventData, FdEventData}; -use crate::sys::oshandle::AsFile; +use crate::sys::AsFile; use crate::wasi::{types, Errno, Result}; use std::io; use std::{convert::TryInto, os::unix::prelude::AsRawFd}; @@ -16,7 +16,7 @@ pub(crate) fn oneoff( return Ok(()); } - let mut poll_fds: Vec<_> = fd_events + let poll_fds: Result> = fd_events .iter() .map(|event| { let mut flags = PollFlags::empty(); @@ -28,14 +28,11 @@ pub(crate) fn oneoff( // events we filtered before. If we get something else here, the code has a serious bug. _ => unreachable!(), }; - let handle = event - .handle - .as_any() - .downcast_ref::() - .expect("can poll FdEvent for OS resources only"); - unsafe { PollFd::new(handle.as_raw_fd(), flags) } + let file = event.handle.as_file()?; + unsafe { Ok(PollFd::new(file.as_raw_fd(), flags)) } }) .collect(); + let mut poll_fds = poll_fds?; let poll_timeout = timeout.map_or(-1, |timeout| { let delay = timeout.delay / 1_000_000; // poll syscall requires delay to expressed in milliseconds @@ -80,18 +77,17 @@ fn handle_fd_event( ready_events: impl Iterator, events: &mut Vec, ) -> Result<()> { - fn query_nbytes(handle: &OsHandle) -> Result { - // fionread may overflow for large files, so use another way for regular files. - if let OsHandle::OsFile(file) = handle { - let meta = file.as_file().metadata()?; - if meta.file_type().is_file() { - use yanix::file::tell; - let len = meta.len(); - let host_offset = unsafe { tell(file.as_raw_fd())? }; - return Ok(len - host_offset); - } + fn query_nbytes(handle: EntryHandle) -> Result { + let file = handle.as_file()?; + if handle.get_file_type() == types::Filetype::RegularFile { + // fionread may overflow for large files, so use another way for regular files. + use yanix::file::tell; + let meta = file.metadata()?; + let len = meta.len(); + let host_offset = unsafe { tell(file.as_raw_fd())? }; + return Ok(len - host_offset); } - unsafe { Ok(fionread(handle.as_raw_fd())?.into()) } + Ok(unsafe { fionread(file.as_raw_fd())?.into() }) } for (fd_event, poll_fd) in ready_events { @@ -106,12 +102,7 @@ fn handle_fd_event( log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents); let nbytes = if fd_event.r#type == types::Eventtype::FdRead { - let handle = fd_event - .handle - .as_any() - .downcast_ref::() - .expect("can poll FdEvent for OS resources only"); - query_nbytes(handle)? + query_nbytes(fd_event.handle)? } else { 0 }; diff --git a/crates/wasi-common/src/sys/unix/stdio.rs b/crates/wasi-common/src/sys/unix/stdio.rs new file mode 100644 index 0000000000..5a38a2992b --- /dev/null +++ b/crates/wasi-common/src/sys/unix/stdio.rs @@ -0,0 +1,59 @@ +use super::{get_file_type, get_rights}; +use crate::handle::Handle; +use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt}; +use std::cell::Cell; +use std::fs::File; +use std::io; +use std::mem::ManuallyDrop; +use std::os::unix::prelude::{AsRawFd, FromRawFd, RawFd}; + +impl AsRawFd for Stdin { + fn as_raw_fd(&self) -> RawFd { + io::stdin().as_raw_fd() + } +} + +impl AsRawFd for Stdout { + fn as_raw_fd(&self) -> RawFd { + io::stdout().as_raw_fd() + } +} + +impl AsRawFd for Stderr { + fn as_raw_fd(&self) -> RawFd { + io::stderr().as_raw_fd() + } +} + +impl StdinExt for Stdin { + fn stdin() -> io::Result> { + let file = unsafe { File::from_raw_fd(io::stdin().as_raw_fd()) }; + let file = ManuallyDrop::new(file); + let file_type = get_file_type(&file)?; + let rights = get_rights(&file, &file_type)?; + let rights = Cell::new(rights); + Ok(Box::new(Self { file_type, rights })) + } +} + +impl StdoutExt for Stdout { + fn stdout() -> io::Result> { + let file = unsafe { File::from_raw_fd(io::stdout().as_raw_fd()) }; + let file = ManuallyDrop::new(file); + let file_type = get_file_type(&file)?; + let rights = get_rights(&file, &file_type)?; + let rights = Cell::new(rights); + Ok(Box::new(Self { file_type, rights })) + } +} + +impl StderrExt for Stderr { + fn stderr() -> io::Result> { + let file = unsafe { File::from_raw_fd(io::stderr().as_raw_fd()) }; + let file = ManuallyDrop::new(file); + let file_type = get_file_type(&file)?; + let rights = get_rights(&file, &file_type)?; + let rights = Cell::new(rights); + Ok(Box::new(Self { file_type, rights })) + } +} diff --git a/crates/wasi-common/src/sys/windows/fd.rs b/crates/wasi-common/src/sys/windows/fd.rs index 0cc20a79bb..78604a4719 100644 --- a/crates/wasi-common/src/sys/windows/fd.rs +++ b/crates/wasi-common/src/sys/windows/fd.rs @@ -1,7 +1,9 @@ use super::file_serial_no; -use super::oshandle::OsFile; +use super::oshandle::RawOsHandle; use crate::path; -use crate::sys::oshandle::AsFile; +use crate::sys::osdir::OsDir; +use crate::sys::osfile::OsFile; +use crate::sys::AsFile; use crate::wasi::{types, Result}; use log::trace; use std::convert::TryInto; @@ -39,7 +41,10 @@ pub(crate) fn fdstat_get(file: &File) -> Result { // handle came from `CreateFile`, but the Rust's libstd will use `GetStdHandle` // rather than `CreateFile`. Relevant discussion can be found in: // https://github.com/rust-lang/rust/issues/40490 -pub(crate) fn fdstat_set_flags(file: &File, fdflags: types::Fdflags) -> Result> { +pub(crate) fn fdstat_set_flags( + file: &File, + fdflags: types::Fdflags, +) -> Result> { let handle = file.as_raw_handle(); let access_mode = winx::file::query_access_information(handle)?; let new_access_mode = file_access_mode_from_fdflags( @@ -49,7 +54,7 @@ pub(crate) fn fdstat_set_flags(file: &File, fdflags: types::Fdflags) -> Result Result>>> { use winx::file::get_file_path; let cookie = cookie.try_into()?; - let path = get_file_path(&file.as_file())?; + let path = get_file_path(&*dirfd.as_file()?)?; // 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 diff --git a/crates/wasi-common/src/sys/windows/mod.rs b/crates/wasi-common/src/sys/windows/mod.rs index c493115ebe..109385f15a 100644 --- a/crates/wasi-common/src/sys/windows/mod.rs +++ b/crates/wasi-common/src/sys/windows/mod.rs @@ -1,18 +1,87 @@ pub(crate) mod clock; pub(crate) mod fd; +pub(crate) mod osdir; +pub(crate) mod osfile; pub(crate) mod oshandle; +pub(crate) mod osother; pub(crate) mod path; pub(crate) mod poll; +pub(crate) mod stdio; -use crate::wasi::{types, Errno, Result}; +use crate::handle::HandleRights; +use crate::sys::AsFile; +use crate::wasi::{types, Errno, Result, RightsExt}; use std::convert::{TryFrom, TryInto}; use std::fs::File; +use std::mem::ManuallyDrop; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; use std::{io, string}; use winapi::shared::winerror; use winx::file::{CreationDisposition, Flags}; +impl AsFile for T { + fn as_file(&self) -> io::Result> { + let file = unsafe { File::from_raw_handle(self.as_raw_handle()) }; + Ok(ManuallyDrop::new(file)) + } +} + +pub(super) fn get_file_type(file: &File) -> io::Result { + let file_type = unsafe { winx::file::get_file_type(file.as_raw_handle())? }; + let file_type = if file_type.is_char() { + // character file: LPT device or console + // TODO: rule out LPT device + types::Filetype::CharacterDevice + } else if file_type.is_disk() { + // disk file: file, dir or disk device + let meta = file.metadata()?; + if meta.is_dir() { + types::Filetype::Directory + } else if meta.is_file() { + types::Filetype::RegularFile + } else { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + } else if file_type.is_pipe() { + // pipe object: socket, named pipe or anonymous pipe + // TODO: what about pipes, etc? + types::Filetype::SocketStream + } else { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + }; + Ok(file_type) +} + +pub(super) fn get_rights(file_type: &types::Filetype) -> io::Result { + let (base, inheriting) = match file_type { + types::Filetype::BlockDevice => ( + types::Rights::block_device_base(), + types::Rights::block_device_inheriting(), + ), + types::Filetype::CharacterDevice => (types::Rights::tty_base(), types::Rights::tty_base()), + types::Filetype::SocketDgram | types::Filetype::SocketStream => ( + types::Rights::socket_base(), + types::Rights::socket_inheriting(), + ), + types::Filetype::SymbolicLink | types::Filetype::Unknown => ( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ), + types::Filetype::Directory => ( + types::Rights::directory_base(), + types::Rights::directory_inheriting(), + ), + types::Filetype::RegularFile => ( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ), + }; + let rights = HandleRights::new(base, inheriting); + Ok(rights) +} + pub fn preopen_dir>(path: P) -> io::Result { use std::fs::OpenOptions; use std::os::windows::fs::OpenOptionsExt; diff --git a/crates/wasi-common/src/sys/windows/osdir.rs b/crates/wasi-common/src/sys/windows/osdir.rs new file mode 100644 index 0000000000..80cee50bce --- /dev/null +++ b/crates/wasi-common/src/sys/windows/osdir.rs @@ -0,0 +1,51 @@ +use super::oshandle::RawOsHandle; +use crate::handle::HandleRights; +use crate::wasi::{types, RightsExt}; +use std::cell::Cell; +use std::convert::TryFrom; +use std::fs::File; +use std::io; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle}; + +#[derive(Debug)] +pub(crate) struct OsDir { + pub(crate) rights: Cell, + pub(crate) handle: RawOsHandle, +} + +impl OsDir { + pub(crate) fn new(rights: HandleRights, handle: RawOsHandle) -> io::Result { + let rights = Cell::new(rights); + Ok(Self { rights, handle }) + } +} + +impl TryFrom for OsDir { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let ft = file.metadata()?.file_type(); + if !ft.is_dir() { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let rights = get_rights(&file)?; + let handle = unsafe { RawOsHandle::from_raw_handle(file.into_raw_handle()) }; + Self::new(rights, handle) + } +} + +fn get_rights(file: &File) -> io::Result { + use winx::file::{query_access_information, AccessMode}; + let mut rights = HandleRights::new( + types::Rights::directory_base(), + types::Rights::directory_inheriting(), + ); + let mode = query_access_information(file.as_raw_handle())?; + if mode.contains(AccessMode::FILE_GENERIC_READ) { + rights.base |= types::Rights::FD_READ; + } + if mode.contains(AccessMode::FILE_GENERIC_WRITE) { + rights.base |= types::Rights::FD_WRITE; + } + Ok(rights) +} diff --git a/crates/wasi-common/src/sys/windows/osfile.rs b/crates/wasi-common/src/sys/windows/osfile.rs new file mode 100644 index 0000000000..0ef24c0b30 --- /dev/null +++ b/crates/wasi-common/src/sys/windows/osfile.rs @@ -0,0 +1,38 @@ +use super::oshandle::RawOsHandle; +use crate::handle::HandleRights; +use crate::sys::osfile::OsFile; +use crate::wasi::{types, RightsExt}; +use std::convert::TryFrom; +use std::fs::File; +use std::io; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle}; + +impl TryFrom for OsFile { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let ft = file.metadata()?.file_type(); + if !ft.is_file() { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let rights = get_rights(&file)?; + let handle = unsafe { RawOsHandle::from_raw_handle(file.into_raw_handle()) }; + Ok(Self::new(rights, handle)) + } +} + +fn get_rights(file: &File) -> io::Result { + use winx::file::{query_access_information, AccessMode}; + let mut rights = HandleRights::new( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ); + let mode = query_access_information(file.as_raw_handle())?; + if mode.contains(AccessMode::FILE_GENERIC_READ) { + rights.base |= types::Rights::FD_READ; + } + if mode.contains(AccessMode::FILE_GENERIC_WRITE) { + rights.base |= types::Rights::FD_WRITE; + } + Ok(rights) +} diff --git a/crates/wasi-common/src/sys/windows/oshandle.rs b/crates/wasi-common/src/sys/windows/oshandle.rs index a313416a2a..89884d03a8 100644 --- a/crates/wasi-common/src/sys/windows/oshandle.rs +++ b/crates/wasi-common/src/sys/windows/oshandle.rs @@ -1,16 +1,19 @@ -use crate::entry::EntryRights; -use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt}; -use crate::wasi::{types, RightsExt}; +use crate::sys::AsFile; use std::cell::Cell; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::io; use std::mem::ManuallyDrop; use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; #[derive(Debug)] -pub(crate) struct OsFile(Cell); +pub(crate) struct RawOsHandle(Cell); -impl OsFile { +impl RawOsHandle { + /// Tries cloning `self`. + pub(crate) fn try_clone(&self) -> io::Result { + let handle = self.as_file()?.try_clone()?; + Ok(Self(Cell::new(handle.into_raw_handle()))) + } /// Consumes `other` taking the ownership of the underlying /// `RawHandle` file handle. pub(crate) fn update_from(&self, other: Self) { @@ -22,14 +25,9 @@ impl OsFile { File::from_raw_handle(old_handle); } } - /// Clones `self`. - pub(crate) fn try_clone(&self) -> io::Result { - let handle = self.as_file().try_clone()?; - Ok(Self(Cell::new(handle.into_raw_handle()))) - } } -impl Drop for OsFile { +impl Drop for RawOsHandle { fn drop(&mut self) { unsafe { File::from_raw_handle(self.as_raw_handle()); @@ -37,133 +35,22 @@ impl Drop for OsFile { } } -impl AsRawHandle for OsFile { +impl AsRawHandle for RawOsHandle { fn as_raw_handle(&self) -> RawHandle { self.0.get() } } -impl FromRawHandle for OsFile { +impl FromRawHandle for RawOsHandle { unsafe fn from_raw_handle(handle: RawHandle) -> Self { Self(Cell::new(handle)) } } -impl IntoRawHandle for OsFile { +impl IntoRawHandle for RawOsHandle { fn into_raw_handle(self) -> RawHandle { // We need to prevent dropping of the OsFile let wrapped = ManuallyDrop::new(self); wrapped.0.get() } } - -impl AsFile for OsFile { - fn as_file(&self) -> ManuallyDrop { - let file = unsafe { File::from_raw_handle(self.0.get()) }; - ManuallyDrop::new(file) - } -} - -impl AsRawHandle for OsHandle { - fn as_raw_handle(&self) -> RawHandle { - match self { - Self::OsFile(file) => file.as_raw_handle(), - Self::Stdin => io::stdin().as_raw_handle(), - Self::Stdout => io::stdout().as_raw_handle(), - Self::Stderr => io::stderr().as_raw_handle(), - } - } -} - -impl AsFile for OsHandle { - fn as_file(&self) -> ManuallyDrop { - let file = unsafe { File::from_raw_handle(self.as_raw_handle()) }; - ManuallyDrop::new(file) - } -} - -impl From for OsHandle { - fn from(file: File) -> Self { - Self::from(unsafe { OsFile::from_raw_handle(file.into_raw_handle()) }) - } -} - -impl OsHandleExt for OsHandle { - fn get_file_type(&self) -> io::Result { - let file_type = unsafe { winx::file::get_file_type(self.as_raw_handle())? }; - let file_type = if file_type.is_char() { - // character file: LPT device or console - // TODO: rule out LPT device - types::Filetype::CharacterDevice - } else if file_type.is_disk() { - // disk file: file, dir or disk device - let file = self.as_file(); - let meta = file.metadata()?; - if meta.is_dir() { - types::Filetype::Directory - } else if meta.is_file() { - types::Filetype::RegularFile - } else { - return Err(io::Error::from_raw_os_error(libc::EINVAL)); - } - } else if file_type.is_pipe() { - // pipe object: socket, named pipe or anonymous pipe - // TODO: what about pipes, etc? - types::Filetype::SocketStream - } else { - return Err(io::Error::from_raw_os_error(libc::EINVAL)); - }; - Ok(file_type) - } - - fn get_rights(&self, file_type: types::Filetype) -> io::Result { - use winx::file::{query_access_information, AccessMode}; - let (base, inheriting) = match file_type { - types::Filetype::BlockDevice => ( - types::Rights::block_device_base(), - types::Rights::block_device_inheriting(), - ), - types::Filetype::CharacterDevice => { - (types::Rights::tty_base(), types::Rights::tty_base()) - } - types::Filetype::Directory => ( - types::Rights::directory_base(), - types::Rights::directory_inheriting(), - ), - types::Filetype::RegularFile => ( - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ), - types::Filetype::SocketDgram | types::Filetype::SocketStream => ( - types::Rights::socket_base(), - types::Rights::socket_inheriting(), - ), - types::Filetype::SymbolicLink | types::Filetype::Unknown => ( - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ), - }; - let mut rights = EntryRights::new(base, inheriting); - match file_type { - types::Filetype::Directory | types::Filetype::RegularFile => { - let mode = query_access_information(self.as_raw_handle())?; - if mode.contains(AccessMode::FILE_GENERIC_READ) { - rights.base |= types::Rights::FD_READ; - } - if mode.contains(AccessMode::FILE_GENERIC_WRITE) { - rights.base |= types::Rights::FD_WRITE; - } - } - _ => { - // TODO: is there a way around this? On windows, it seems - // we cannot check access rights for anything but dirs and regular files - } - } - Ok(rights) - } - - fn from_null() -> io::Result { - let file = OpenOptions::new().read(true).write(true).open("NUL")?; - Ok(Self::from(file)) - } -} diff --git a/crates/wasi-common/src/sys/windows/osother.rs b/crates/wasi-common/src/sys/windows/osother.rs new file mode 100644 index 0000000000..47db73217d --- /dev/null +++ b/crates/wasi-common/src/sys/windows/osother.rs @@ -0,0 +1,31 @@ +use super::oshandle::RawOsHandle; +use super::{get_file_type, get_rights}; +use crate::handle::Handle; +use crate::sys::osother::{OsOther, OsOtherExt}; +use crate::wasi::types; +use std::convert::TryFrom; +use std::fs::{File, OpenOptions}; +use std::io; +use std::os::windows::prelude::{FromRawHandle, IntoRawHandle}; + +impl TryFrom for OsOther { + type Error = io::Error; + + fn try_from(file: File) -> io::Result { + let file_type = get_file_type(&file)?; + if file_type == types::Filetype::RegularFile || file_type == types::Filetype::Directory { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let rights = get_rights(&file_type)?; + let handle = unsafe { RawOsHandle::from_raw_handle(file.into_raw_handle()) }; + Ok(Self::new(file_type, rights, handle)) + } +} + +impl OsOtherExt for OsOther { + fn from_null() -> io::Result> { + let file = OpenOptions::new().read(true).write(true).open("NUL")?; + let file = Self::try_from(file)?; + Ok(Box::new(file)) + } +} diff --git a/crates/wasi-common/src/sys/windows/path.rs b/crates/wasi-common/src/sys/windows/path.rs index a33ffc08c6..d410866718 100644 --- a/crates/wasi-common/src/sys/windows/path.rs +++ b/crates/wasi-common/src/sys/windows/path.rs @@ -1,7 +1,8 @@ -use super::oshandle::OsFile; -use crate::entry::EntryRights; -use crate::sys::oshandle::{AsFile, OsHandle}; +use crate::handle::{Handle, HandleRights}; +use crate::sys::osdir::OsDir; +use crate::sys::AsFile; use crate::wasi::{types, Errno, Result}; +use std::convert::TryFrom; use std::ffi::{OsStr, OsString}; use std::fs::{self, Metadata, OpenOptions}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; @@ -10,7 +11,7 @@ use std::path::{Path, PathBuf}; use winapi::shared::winerror; use winx::file::AccessMode; -fn strip_trailing_slashes_and_concatenate(dirfd: &OsFile, path: &str) -> Result> { +fn strip_trailing_slashes_and_concatenate(dirfd: &OsDir, path: &str) -> Result> { if path.ends_with('/') { let suffix = path.trim_end_matches('/'); concatenate(dirfd, Path::new(suffix)).map(Some) @@ -28,7 +29,7 @@ fn strip_extended_prefix>(path: P) -> OsString { } } -fn concatenate>(file: &OsFile, path: P) -> Result { +fn concatenate>(file: &OsDir, path: P) -> Result { use winx::file::get_file_path; // WASI is not able to deal with absolute paths @@ -37,7 +38,7 @@ fn concatenate>(file: &OsFile, path: P) -> Result { return Err(Errno::Notcapable); } - let dir_path = get_file_path(&file.as_file())?; + let dir_path = get_file_path(&*file.as_file()?)?; // concatenate paths let mut out_path = PathBuf::from(dir_path); out_path.push(path.as_ref()); @@ -89,10 +90,10 @@ pub(crate) fn from_host>(s: S) -> Result { } pub(crate) fn open_rights( - input_rights: &EntryRights, + input_rights: &HandleRights, oflags: types::Oflags, fdflags: types::Fdflags, -) -> EntryRights { +) -> HandleRights { // which rights are needed on the dirfd? let mut needed_base = types::Rights::PATH_OPEN; let mut needed_inheriting = input_rights.base | input_rights.inheriting; @@ -113,10 +114,10 @@ pub(crate) fn open_rights( needed_inheriting |= types::Rights::FD_SYNC; } - EntryRights::new(needed_base, needed_inheriting) + HandleRights::new(needed_base, needed_inheriting) } -pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result { +pub(crate) fn readlinkat(dirfd: &OsDir, s_path: &str) -> Result { use winx::file::get_file_path; let path = concatenate(dirfd, Path::new(s_path))?; @@ -126,7 +127,7 @@ pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result { // 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.as_file())?; + let dir_path = get_file_path(&*dirfd.as_file()?)?; let dir_path = PathBuf::from(strip_extended_prefix(dir_path)); let target_path = target_path .strip_prefix(dir_path) @@ -151,16 +152,16 @@ pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result { Err(err.into()) } -pub(crate) fn create_directory(file: &OsFile, path: &str) -> Result<()> { +pub(crate) fn create_directory(file: &OsDir, path: &str) -> Result<()> { let path = concatenate(file, path)?; std::fs::create_dir(&path)?; Ok(()) } pub(crate) fn link( - old_dirfd: &OsFile, + old_dirfd: &OsDir, old_path: &str, - new_dirfd: &OsFile, + new_dirfd: &OsDir, new_path: &str, follow_symlinks: bool, ) -> Result<()> { @@ -197,13 +198,13 @@ pub(crate) fn link( } pub(crate) fn open( - dirfd: &OsFile, + dirfd: &OsDir, path: &str, read: bool, write: bool, oflags: types::Oflags, fdflags: types::Fdflags, -) -> Result { +) -> Result> { use winx::file::{AccessMode, CreationDisposition, Flags}; let is_trunc = oflags.contains(&types::Oflags::TRUNC); @@ -280,11 +281,11 @@ pub(crate) fn open( .access_mode(access_mode.bits()) .custom_flags(flags.bits()) .open(&path)?; - let handle = OsHandle::from(file); + let handle = >::try_from(file)?; Ok(handle) } -pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result { +pub(crate) fn readlink(dirfd: &OsDir, path: &str, buf: &mut [u8]) -> Result { use winx::file::get_file_path; let path = concatenate(dirfd, path)?; @@ -294,7 +295,7 @@ pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result Result Result<()> { use std::fs; @@ -390,7 +391,7 @@ pub(crate) fn rename( } } -pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path_: &str) -> Result<()> { +pub(crate) fn symlink(old_path: &str, new_dirfd: &OsDir, new_path_: &str) -> Result<()> { use std::os::windows::fs::{symlink_dir, symlink_file}; let old_path = concatenate(new_dirfd, Path::new(old_path))?; @@ -447,7 +448,7 @@ pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path_: &str) -> Re } } -pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { +pub(crate) fn unlink_file(dirfd: &OsDir, path: &str) -> Result<()> { use std::fs; let path = concatenate(dirfd, path)?; @@ -489,7 +490,7 @@ pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { } } -pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> { +pub(crate) fn remove_directory(dirfd: &OsDir, path: &str) -> Result<()> { let path = concatenate(dirfd, path)?; std::fs::remove_dir(&path).map_err(Into::into) } diff --git a/crates/wasi-common/src/sys/windows/poll.rs b/crates/wasi-common/src/sys/windows/poll.rs index 7e56c39829..d49524a5d5 100644 --- a/crates/wasi-common/src/sys/windows/poll.rs +++ b/crates/wasi-common/src/sys/windows/poll.rs @@ -1,11 +1,14 @@ -use super::super::oshandle::OsHandle; +use crate::handle::Handle; use crate::poll::{ClockEventData, FdEventData}; -use crate::sys::oshandle::AsFile; +use crate::sys::osdir::OsDir; +use crate::sys::osfile::OsFile; +use crate::sys::osother::OsOther; +use crate::sys::stdio::{Stderr, Stdin, Stdout}; +use crate::sys::AsFile; use crate::wasi::{types, Errno, Result}; use lazy_static::lazy_static; use log::{debug, error, trace, warn}; use std::convert::TryInto; -use std::os::windows::io::AsRawHandle; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; use std::sync::Mutex; use std::thread; @@ -141,32 +144,31 @@ fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec) { - let handle = event - .handle - .as_any() - .downcast_ref::() - .expect("can poll FdEvent for OS resources only"); - let size = match handle { - OsHandle::OsFile(file) => { - if event.r#type == types::Eventtype::FdRead { - file.as_file() - .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 - // the implementation on Unix just returns 0 here, so it's probably fine - // to do the same on Windows for now. - // cf. https://github.com/WebAssembly/WASI/issues/148 - Ok(0) - } - } + let handle = &event.handle; + let size = if let Some(_) = handle.as_any().downcast_ref::() { // We return the only universally correct lower bound, see the comment later in the function. - OsHandle::Stdin => Ok(1), + Ok(1) + } else if let Some(_) = handle.as_any().downcast_ref::() { + // On Unix, ioctl(FIONREAD) will return 0 for stdout. Emulate the same behavior on Windows. + Ok(0) + } else if let Some(_) = handle.as_any().downcast_ref::() { // On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows. - OsHandle::Stdout | OsHandle::Stderr => Ok(0), + Ok(0) + } else { + if event.r#type == types::Eventtype::FdRead { + handle + .as_file() + .and_then(|f| f.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 + // the implementation on Unix just returns 0 here, so it's probably fine + // to do the same on Windows for now. + // cf. https://github.com/WebAssembly/WASI/issues/148 + Ok(0) + } }; - let new_event = make_rw_event(&event, size); out_events.push(new_event); } @@ -206,33 +208,42 @@ pub(crate) fn oneoff( let mut pipe_events = vec![]; for event in fd_events { - let handle = event - .handle - .as_any() - .downcast_ref::() - .expect("can poll FdEvent for OS resources only"); - match handle { - OsHandle::Stdin if event.r#type == types::Eventtype::FdRead => stdin_events.push(event), - // stdout/stderr are always considered ready to write because there seems to + let handle = &event.handle; + if let Some(_) = handle.as_any().downcast_ref::() { + immediate_events.push(event); + } else if let Some(_) = handle.as_any().downcast_ref::() { + immediate_events.push(event); + } else if let Some(_) = handle.as_any().downcast_ref::() { + stdin_events.push(event); + } else if let Some(_) = handle.as_any().downcast_ref::() { + // stdout are always considered ready to write because there seems to // be no way of checking if a write to stdout would block. // // If stdin is polled for anything else then reading, then it is also // considered immediately ready, following the behavior on Linux. - OsHandle::Stdin | OsHandle::Stderr | OsHandle::Stdout => immediate_events.push(event), - OsHandle::OsFile(file) => { - let ftype = unsafe { winx::file::get_file_type(file.as_raw_handle()) }?; - if ftype.is_unknown() || ftype.is_char() { - debug!("poll_oneoff: unsupported file type: {:?}", ftype); - handle_error_event(event, Errno::Notsup, events); - } else if ftype.is_disk() { - immediate_events.push(event); - } else if ftype.is_pipe() { - pipe_events.push(event); - } else { - unreachable!(); - } + immediate_events.push(event); + } else if let Some(_) = handle.as_any().downcast_ref::() { + // stderr are always considered ready to write because there seems to + // be no way of checking if a write to stdout would block. + // + // If stdin is polled for anything else then reading, then it is also + // considered immediately ready, following the behavior on Linux. + immediate_events.push(event); + } else if let Some(other) = handle.as_any().downcast_ref::() { + if other.get_file_type() == types::Filetype::SocketStream { + // We map pipe to SocketStream + pipe_events.push(event); + } else { + debug!( + "poll_oneoff: unsupported file type: {}", + other.get_file_type() + ); + handle_error_event(event, Errno::Notsup, events); } - }; + } else { + log::error!("can poll FdEvent for OS resources only"); + return Err(Errno::Badf); + } } let immediate = !immediate_events.is_empty(); diff --git a/crates/wasi-common/src/sys/windows/stdio.rs b/crates/wasi-common/src/sys/windows/stdio.rs new file mode 100644 index 0000000000..25140db90a --- /dev/null +++ b/crates/wasi-common/src/sys/windows/stdio.rs @@ -0,0 +1,59 @@ +use super::{get_file_type, get_rights}; +use crate::handle::Handle; +use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt}; +use std::cell::Cell; +use std::fs::File; +use std::io; +use std::mem::ManuallyDrop; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle}; + +impl AsRawHandle for Stdin { + fn as_raw_handle(&self) -> RawHandle { + io::stdin().as_raw_handle() + } +} + +impl AsRawHandle for Stdout { + fn as_raw_handle(&self) -> RawHandle { + io::stdout().as_raw_handle() + } +} + +impl AsRawHandle for Stderr { + fn as_raw_handle(&self) -> RawHandle { + io::stderr().as_raw_handle() + } +} + +impl StdinExt for Stdin { + fn stdin() -> io::Result> { + let file = unsafe { File::from_raw_handle(io::stdin().as_raw_handle()) }; + let file = ManuallyDrop::new(file); + let file_type = get_file_type(&file)?; + let rights = get_rights(&file_type)?; + let rights = Cell::new(rights); + Ok(Box::new(Self { file_type, rights })) + } +} + +impl StdoutExt for Stdout { + fn stdout() -> io::Result> { + let file = unsafe { File::from_raw_handle(io::stdin().as_raw_handle()) }; + let file = ManuallyDrop::new(file); + let file_type = get_file_type(&file)?; + let rights = get_rights(&file_type)?; + let rights = Cell::new(rights); + Ok(Box::new(Self { file_type, rights })) + } +} + +impl StderrExt for Stderr { + fn stderr() -> io::Result> { + let file = unsafe { File::from_raw_handle(io::stdin().as_raw_handle()) }; + let file = ManuallyDrop::new(file); + let file_type = get_file_type(&file)?; + let rights = get_rights(&file_type)?; + let rights = Cell::new(rights); + Ok(Box::new(Self { file_type, rights })) + } +} diff --git a/crates/wasi-common/src/virtfs.rs b/crates/wasi-common/src/virtfs.rs index 521119f428..c868a27955 100644 --- a/crates/wasi-common/src/virtfs.rs +++ b/crates/wasi-common/src/virtfs.rs @@ -1,5 +1,4 @@ -use crate::entry::EntryRights; -use crate::handle::Handle; +use crate::handle::{Handle, HandleRights}; use crate::wasi::{self, types, Errno, Result, RightsExt}; use log::trace; use std::any::Any; @@ -134,6 +133,7 @@ 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 { + rights: Cell, cursor: Cell, parent: Rc>>>, fd_flags: Cell, @@ -142,16 +142,17 @@ pub struct InMemoryFile { impl InMemoryFile { pub fn memory_backed() -> Self { - Self { - cursor: Cell::new(0), - parent: Rc::new(RefCell::new(None)), - fd_flags: Cell::new(types::Fdflags::empty()), - data: Rc::new(RefCell::new(Box::new(VecFileContents::new()))), - } + Self::new(Box::new(VecFileContents::new())) } pub fn new(contents: Box) -> Self { + let rights = HandleRights::new( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + ); + let rights = Cell::new(rights); Self { + rights, cursor: Cell::new(0), fd_flags: Cell::new(types::Fdflags::empty()), parent: Rc::new(RefCell::new(None)), @@ -172,20 +173,21 @@ impl Handle for InMemoryFile { } fn try_clone(&self) -> io::Result> { Ok(Box::new(Self { + rights: self.rights.clone(), cursor: Cell::new(0), fd_flags: self.fd_flags.clone(), parent: Rc::clone(&self.parent), data: Rc::clone(&self.data), })) } - fn get_file_type(&self) -> io::Result { - Ok(types::Filetype::RegularFile) + fn get_file_type(&self) -> types::Filetype { + types::Filetype::RegularFile } - fn get_rights(&self) -> io::Result { - Ok(EntryRights::new( - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - )) + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, rights: HandleRights) { + self.rights.set(rights) } // FdOps fn advise( @@ -227,7 +229,7 @@ impl Handle for InMemoryFile { atim: 0, ctim: 0, mtim: 0, - filetype: self.get_file_type()?, + filetype: self.get_file_type(), }; Ok(stat) } @@ -280,11 +282,7 @@ impl Handle for InMemoryFile { Ok(self.cursor.get()) } - fn write_vectored(&self, iovs: &[io::IoSlice], isatty: bool) -> Result { - if isatty { - unimplemented!("writes to virtual tty"); - } - + fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { trace!("write_vectored(iovs={:?})", iovs); let mut data = self.data.borrow_mut(); @@ -402,6 +400,7 @@ impl Handle for InMemoryFile { /// A clonable read/write directory. pub struct VirtualDir { + rights: Cell, writable: bool, // All copies of this `VirtualDir` must share `parent`, and changes in one copy's `parent` // must be reflected in all handles, so they share `Rc` of an underlying `parent`. @@ -411,7 +410,13 @@ pub struct VirtualDir { impl VirtualDir { pub fn new(writable: bool) -> Self { + let rights = HandleRights::new( + types::Rights::directory_base(), + types::Rights::directory_inheriting(), + ); + let rights = Cell::new(rights); Self { + rights, writable, parent: Rc::new(RefCell::new(None)), entries: Rc::new(RefCell::new(HashMap::new())), @@ -468,19 +473,20 @@ impl Handle for VirtualDir { } fn try_clone(&self) -> io::Result> { Ok(Box::new(Self { + rights: self.rights.clone(), writable: self.writable, parent: Rc::clone(&self.parent), entries: Rc::clone(&self.entries), })) } - fn get_file_type(&self) -> io::Result { - Ok(types::Filetype::Directory) + fn get_file_type(&self) -> types::Filetype { + types::Filetype::Directory } - fn get_rights(&self) -> io::Result { - Ok(EntryRights::new( - types::Rights::directory_base(), - types::Rights::directory_inheriting(), - )) + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + fn set_rights(&self, rights: HandleRights) { + self.rights.set(rights) } // FdOps fn filestat_get(&self) -> Result { @@ -492,7 +498,7 @@ impl Handle for VirtualDir { atim: 0, ctim: 0, mtim: 0, - filetype: self.get_file_type()?, + filetype: self.get_file_type(), }; Ok(stat) } @@ -555,7 +561,7 @@ impl Handle for VirtualDir { let dirent = || -> Result { let dirent = types::Dirent { d_namlen: name.len().try_into()?, - d_type: file.get_file_type()?, + d_type: file.get_file_type(), d_ino: 0, d_next: self.start as u64, }; @@ -639,7 +645,7 @@ impl Handle for VirtualDir { } if oflags.contains(&types::Oflags::DIRECTORY) - && e.get().get_file_type()? != types::Filetype::Directory + && e.get().get_file_type() != types::Filetype::Directory { log::trace!( "VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.", @@ -683,7 +689,7 @@ impl Handle for VirtualDir { 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()? != types::Filetype::Directory { + if e.get().get_file_type() != types::Filetype::Directory { return Err(Errno::Notdir); } @@ -731,7 +737,7 @@ impl Handle for VirtualDir { 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()? == types::Filetype::Directory { + if e.get().get_file_type() == types::Filetype::Directory { return Err(Errno::Isdir); }