diff --git a/Cargo.lock b/Cargo.lock index 30ac7e2dcf..b8c92cbc46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1742,18 +1742,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" dependencies = [ "proc-macro2", "quote", @@ -1762,9 +1762,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867" +checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" dependencies = [ "itoa", "ryu", @@ -1909,9 +1909,9 @@ dependencies = [ [[package]] name = "termios" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" +checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2" dependencies = [ "libc", ] diff --git a/crates/wasi-common/src/ctx.rs b/crates/wasi-common/src/ctx.rs index cdf575b446..de66d81814 100644 --- a/crates/wasi-common/src/ctx.rs +++ b/crates/wasi-common/src/ctx.rs @@ -1,6 +1,7 @@ -use crate::entry::{Descriptor, Entry}; +use crate::entry::{Entry, EntryHandle}; use crate::fdpool::FdPool; -use crate::sys::entry::OsHandle; +use crate::handle::Handle; +use crate::sys::oshandle::{OsHandle, OsHandleExt}; use crate::virtfs::{VirtualDir, VirtualDirEntry}; use crate::wasi::types; use crate::wasi::{Errno, Result}; @@ -42,7 +43,7 @@ pub enum WasiCtxBuilderError { type WasiCtxBuilderResult = std::result::Result; enum PendingEntry { - Thunk(fn() -> io::Result), + Thunk(fn() -> io::Result), File(File), } @@ -52,7 +53,7 @@ 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), } @@ -109,7 +110,7 @@ pub struct WasiCtxBuilder { stdin: Option, stdout: Option, stderr: Option, - preopens: Option>, + preopens: Option)>>, args: Option>, env: Option>, } @@ -117,9 +118,9 @@ pub struct WasiCtxBuilder { impl WasiCtxBuilder { /// Builder for a new `WasiCtx`. pub fn new() -> Self { - let stdin = Some(PendingEntry::Thunk(Entry::null)); - let stdout = Some(PendingEntry::Thunk(Entry::null)); - let stderr = Some(PendingEntry::Thunk(Entry::null)); + let stdin = Some(PendingEntry::Thunk(OsHandle::from_null)); + let stdout = Some(PendingEntry::Thunk(OsHandle::from_null)); + let stderr = Some(PendingEntry::Thunk(OsHandle::from_null)); Self { stdin, @@ -166,27 +167,27 @@ impl WasiCtxBuilder { /// Inherit stdin from the host process. pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin)); + self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin()))); self } /// Inherit stdout from the host process. pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout)); + self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout()))); self } /// Inherit stdout from the host process. pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr)); + self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::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(Entry::duplicate_stdin)); - self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout)); - self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr)); + 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 } @@ -250,7 +251,7 @@ impl WasiCtxBuilder { 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(), - Descriptor::OsHandle(OsHandle::from(dir)), + Box::new(OsHandle::from(dir)), )); self } @@ -287,7 +288,7 @@ impl WasiCtxBuilder { self.preopens .as_mut() .unwrap() - .push((guest_path.as_ref().to_owned(), Descriptor::VirtualFile(dir))); + .push((guest_path.as_ref().to_owned(), dir)); self } @@ -334,41 +335,36 @@ impl WasiCtxBuilder { ] { log::debug!("WasiCtx inserting entry {:?}", pending); let fd = match pending { - PendingEntry::Thunk(f) => entries - .insert(f()?) - .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?, - PendingEntry::File(f) => entries - .insert(Entry::from(Descriptor::OsHandle(OsHandle::from(f)))?) - .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?, + PendingEntry::Thunk(f) => { + let handle = EntryHandle::new(f()?); + let entry = Entry::from(handle)?; + entries + .insert(entry) + .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? + } + PendingEntry::File(f) => { + let handle = EntryHandle::new(OsHandle::from(f)); + let entry = Entry::from(handle)?; + entries + .insert(entry) + .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? + } }; log::debug!("WasiCtx inserted at {:?}", fd); } // Then add the preopen entries. for (guest_path, dir) in self.preopens.take().unwrap() { - match &dir { - Descriptor::OsHandle(handle) => { - if !handle.metadata()?.is_dir() { - return Err(WasiCtxBuilderError::NotADirectory(guest_path)); - } - } - Descriptor::VirtualFile(virt) => { - if !virt.is_directory() { - return Err(WasiCtxBuilderError::NotADirectory(guest_path)); - } - } - Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => { - panic!("implementation error, stdin/stdout/stderr shouldn't be in the list of preopens"); - } + if !dir.is_directory() { + return Err(WasiCtxBuilderError::NotADirectory(guest_path)); } - let mut entry = Entry::from(dir)?; + let handle = EntryHandle::from(dir); + let mut entry = Entry::from(handle)?; entry.preopen_path = Some(guest_path); - log::debug!("WasiCtx inserting {:?}", entry); let fd = entries .insert(entry) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; log::debug!("WasiCtx inserted at {:?}", fd); - log::debug!("WasiCtx entries = {:?}", entries); } Ok(WasiCtx { @@ -379,7 +375,6 @@ impl WasiCtxBuilder { } } -#[derive(Debug)] struct EntryTable { fd_pool: FdPool, entries: HashMap>, @@ -418,7 +413,6 @@ impl EntryTable { } } -#[derive(Debug)] pub struct WasiCtx { entries: RefCell, pub(crate) args: Vec, diff --git a/crates/wasi-common/src/entry.rs b/crates/wasi-common/src/entry.rs index fd90ba1859..debd904617 100644 --- a/crates/wasi-common/src/entry.rs +++ b/crates/wasi-common/src/entry.rs @@ -1,81 +1,53 @@ -use crate::sys::dev_null; -use crate::sys::entry::{descriptor_as_oshandle, determine_type_and_access_rights, OsHandle}; -use crate::virtfs::VirtualFile; +use crate::handle::Handle; use crate::wasi::types::{Filetype, Rights}; use crate::wasi::{Errno, Result}; -use std::cell::{Cell, RefCell}; -use std::marker::PhantomData; -use std::mem::ManuallyDrop; +use std::cell::Cell; use std::ops::Deref; use std::path::PathBuf; use std::rc::Rc; -use std::{fmt, fs, io}; +use std::{fmt, io}; -pub(crate) enum Descriptor { - OsHandle(OsHandle), - VirtualFile(Box), - Stdin, - Stdout, - Stderr, -} +pub(crate) struct EntryHandle(Rc); -impl From for Descriptor { - fn from(handle: OsHandle) -> Self { - Self::OsHandle(handle) +impl EntryHandle { + pub(crate) fn new(handle: T) -> Self { + Self(Rc::new(handle)) + } + + pub(crate) fn get(&self) -> Self { + Self(Rc::clone(&self.0)) } } -impl From> for Descriptor { - fn from(virt: Box) -> Self { - Self::VirtualFile(virt) +impl From> for EntryHandle { + fn from(handle: Box) -> Self { + Self(handle.into()) } } -impl fmt::Debug for Descriptor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::OsHandle(handle) => write!(f, "{:?}", handle), - Self::VirtualFile(_) => write!(f, "VirtualFile"), - Self::Stdin => write!(f, "Stdin"), - Self::Stdout => write!(f, "Stdout"), - Self::Stderr => write!(f, "Stderr"), - } - } -} - -impl Descriptor { - /// Return an `OsHandle`, which may be a stream or socket file descriptor. - pub(crate) fn as_os_handle<'descriptor>(&'descriptor self) -> OsHandleRef<'descriptor> { - descriptor_as_oshandle(self) - } -} - -/// This allows an `OsHandle` to be temporarily borrowed from a -/// `Descriptor`. The `Descriptor` continues to own the resource, -/// and `OsHandleRef`'s lifetime parameter ensures that it doesn't -/// outlive the `Descriptor`. -pub(crate) struct OsHandleRef<'descriptor> { - handle: ManuallyDrop, - _ref: PhantomData<&'descriptor Descriptor>, -} - -impl<'descriptor> OsHandleRef<'descriptor> { - pub(crate) fn new(handle: ManuallyDrop) -> Self { - OsHandleRef { - handle, - _ref: PhantomData, - } - } -} - -impl<'descriptor> Deref for OsHandleRef<'descriptor> { - type Target = fs::File; +impl Deref for EntryHandle { + type Target = dyn Handle; fn deref(&self) -> &Self::Target { - &self.handle + &*self.0 } } +/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires +/// certain rights `rights` in order to be accessed correctly. +/// +/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or +/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` 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. +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)] @@ -123,80 +95,18 @@ impl fmt::Display for EntryRights { } } -/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires -/// certain rights `rights` in order to be accessed correctly. -/// -/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or -/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` 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. -#[derive(Debug)] -pub(crate) struct Entry { - pub(crate) file_type: Filetype, - descriptor: Rc>, - pub(crate) rights: Cell, - pub(crate) preopen_path: Option, - // TODO: directories -} - impl Entry { - pub(crate) fn from(file: Descriptor) -> io::Result { - match file { - Descriptor::OsHandle(handle) => unsafe { determine_type_and_access_rights(&handle) } - .map(|(file_type, rights)| Self { - file_type, - descriptor: Rc::new(RefCell::new(handle.into())), - rights: Cell::new(rights), - preopen_path: None, - }), - Descriptor::VirtualFile(virt) => { - let file_type = virt.get_file_type(); - let rights = EntryRights::new(virt.get_rights_base(), virt.get_rights_inheriting()); - - Ok(Self { - file_type, - descriptor: Rc::new(RefCell::new(virt.into())), - rights: Cell::new(rights), - preopen_path: None, - }) - } - Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => { - panic!("implementation error, stdin/stdout/stderr Entry must not be constructed from Entry::from"); - } - } - } - - pub(crate) fn duplicate_stdin() -> io::Result { - unsafe { determine_type_and_access_rights(&io::stdin()) }.map(|(file_type, rights)| Self { + pub(crate) fn from(handle: EntryHandle) -> io::Result { + let file_type = handle.get_file_type()?; + let rights = handle.get_rights()?; + Ok(Self { file_type, - descriptor: Rc::new(RefCell::new(Descriptor::Stdin)), + handle, rights: Cell::new(rights), preopen_path: None, }) } - pub(crate) fn duplicate_stdout() -> io::Result { - unsafe { determine_type_and_access_rights(&io::stdout()) }.map(|(file_type, rights)| Self { - file_type, - descriptor: Rc::new(RefCell::new(Descriptor::Stdout)), - rights: Cell::new(rights), - preopen_path: None, - }) - } - - pub(crate) fn duplicate_stderr() -> io::Result { - unsafe { determine_type_and_access_rights(&io::stderr()) }.map(|(file_type, rights)| Self { - file_type, - descriptor: Rc::new(RefCell::new(Descriptor::Stderr)), - rights: Cell::new(rights), - preopen_path: None, - }) - } - - pub(crate) fn null() -> io::Result { - Self::from(OsHandle::from(dev_null()?).into()) - } - /// Convert this `Entry` into a host `Descriptor` object provided the specified /// `rights` rights are set on this `Entry` object. /// @@ -205,9 +115,9 @@ impl Entry { /// `EntryRights` 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_descriptor(&self, rights: &EntryRights) -> Result>> { + pub(crate) fn as_handle(&self, rights: &EntryRights) -> Result { self.validate_rights(rights)?; - Ok(Rc::clone(&self.descriptor)) + Ok(self.handle.get()) } /// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if diff --git a/crates/wasi-common/src/handle.rs b/crates/wasi-common/src/handle.rs new file mode 100644 index 0000000000..671ae2d2a1 --- /dev/null +++ b/crates/wasi-common/src/handle.rs @@ -0,0 +1,121 @@ +use crate::entry::EntryRights; +use crate::wasi::{types, Errno, Result}; +use std::any::Any; +use std::io::{self, SeekFrom}; + +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 is_directory(&self) -> bool { + if let Ok(ft) = self.get_file_type() { + return ft == types::Filetype::Directory; + } + false + } + // TODO perhaps should be a separate trait? + // FdOps + fn advise( + &self, + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<()> { + Err(Errno::Badf) + } + fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> { + Err(Errno::Badf) + } + fn datasync(&self) -> Result<()> { + Err(Errno::Inval) + } + fn fdstat_get(&self) -> Result { + Ok(types::Fdflags::empty()) + } + fn fdstat_set_flags(&self, _fdflags: types::Fdflags) -> Result<()> { + Err(Errno::Badf) + } + fn filestat_get(&self) -> Result { + Err(Errno::Badf) + } + fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> { + Err(Errno::Badf) + } + fn filestat_set_times( + &self, + _atim: types::Timestamp, + _mtim: types::Timestamp, + _fst_flags: types::Fstflags, + ) -> Result<()> { + Err(Errno::Badf) + } + fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result { + Err(Errno::Badf) + } + fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result { + Err(Errno::Badf) + } + fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result { + Err(Errno::Badf) + } + fn readdir<'a>( + &'a self, + _cookie: types::Dircookie, + ) -> Result> + 'a>> { + Err(Errno::Badf) + } + fn seek(&self, _offset: SeekFrom) -> Result { + Err(Errno::Badf) + } + fn sync(&self) -> Result<()> { + Ok(()) + } + fn write_vectored(&self, _iovs: &[io::IoSlice], _isatty: bool) -> Result { + Err(Errno::Badf) + } + // TODO perhaps should be a separate trait? + // PathOps + fn create_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Acces) + } + fn openat( + &self, + _path: &str, + _read: bool, + _write: bool, + _oflags: types::Oflags, + _fd_flags: types::Fdflags, + ) -> Result> { + Err(Errno::Acces) + } + fn link( + &self, + _old_path: &str, + _new_handle: Box, + _new_path: &str, + _follow: bool, + ) -> Result<()> { + Err(Errno::Acces) + } + fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result { + Err(Errno::Acces) + } + fn readlinkat(&self, _path: &str) -> Result { + Err(Errno::Acces) + } + fn remove_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Acces) + } + fn rename(&self, _old_path: &str, _new_handle: Box, _new_path: &str) -> Result<()> { + Err(Errno::Acces) + } + fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> { + Err(Errno::Acces) + } + fn unlink_file(&self, _path: &str) -> Result<()> { + Err(Errno::Acces) + } +} diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 5e517fc6c4..df071e5b00 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -21,12 +21,11 @@ ) )] -mod clock; mod ctx; mod entry; -mod fd; mod fdpool; pub mod fs; +mod handle; pub mod old; mod path; mod poll; diff --git a/crates/wasi-common/src/path.rs b/crates/wasi-common/src/path.rs index d7254ade74..515ed18019 100644 --- a/crates/wasi-common/src/path.rs +++ b/crates/wasi-common/src/path.rs @@ -1,112 +1,22 @@ -use crate::entry::{Descriptor, Entry, EntryRights}; -use crate::sys; -use crate::sys::entry::OsHandle; +use crate::entry::{Entry, EntryRights}; +use crate::handle::Handle; use crate::wasi::{types, Errno, Result}; use std::path::{Component, Path}; use std::str; use wiggle::{GuestBorrows, GuestPtr}; -pub(crate) use sys::path::*; - -#[derive(Debug)] -pub(crate) struct PathGet { - dirfd: Descriptor, - path: String, -} - -impl PathGet { - pub(crate) fn dirfd(&self) -> &Descriptor { - &self.dirfd - } - - pub(crate) fn path(&self) -> &str { - &self.path - } - - pub(crate) fn create_directory(self) -> Result<()> { - match &self.dirfd { - Descriptor::OsHandle(file) => create_directory(&file, &self.path), - Descriptor::VirtualFile(virt) => virt.create_directory(&Path::new(&self.path)), - other => { - panic!("invalid descriptor to create directory: {:?}", other); - } - } - } - - pub(crate) fn open_with( - self, - read: bool, - write: bool, - oflags: types::Oflags, - fs_flags: types::Fdflags, - ) -> Result { - match &self.dirfd { - Descriptor::OsHandle(_) => { - open(self, read, write, oflags, fs_flags).map_err(Into::into) - } - Descriptor::VirtualFile(virt) => virt - .openat(Path::new(&self.path), read, write, oflags, fs_flags) - .map(|file| Descriptor::VirtualFile(file)), - other => { - panic!("invalid descriptor to path_open: {:?}", other); - } - } - } -} - -struct PathRef<'a, 'b> { - dirfd: &'a Descriptor, - path: &'b str, -} - -impl<'a, 'b> PathRef<'a, 'b> { - fn new(dirfd: &'a Descriptor, path: &'b str) -> Self { - PathRef { dirfd, path } - } - - fn open(&self) -> Result { - match self.dirfd { - Descriptor::OsHandle(file) => Ok(Descriptor::OsHandle(OsHandle::from(openat( - &file, &self.path, - )?))), - Descriptor::VirtualFile(virt) => virt - .openat( - Path::new(&self.path), - false, - false, - types::Oflags::DIRECTORY, - types::Fdflags::empty(), - ) - .map(|file| Descriptor::VirtualFile(file)), - other => { - panic!("invalid descriptor for open: {:?}", other); - } - } - } - - fn readlink(&self) -> Result { - match self.dirfd { - Descriptor::OsHandle(file) => readlinkat(file, self.path), - Descriptor::VirtualFile(virt) => { - virt.readlinkat(Path::new(self.path)).map_err(Into::into) - } - other => { - panic!("invalid descriptor for readlink: {:?}", other); - } - } - } -} +pub(crate) use crate::sys::path::{from_host, open_rights}; /// Normalizes a path to ensure that the target path is located under the directory provided. /// /// This is a workaround for not having Capsicum support in the OS. pub(crate) fn get( - fe: &Entry, + entry: &Entry, required_rights: &EntryRights, dirflags: types::Lookupflags, path: &GuestPtr<'_, str>, needs_final_component: bool, -) -> Result { +) -> Result<(Box, String)> { const MAX_SYMLINK_EXPANSIONS: usize = 128; // Extract path as &str from guest's memory. @@ -123,17 +33,13 @@ pub(crate) fn get( return Err(Errno::Ilseq); } - if fe.file_type != types::Filetype::Directory { + if entry.file_type != types::Filetype::Directory { // if `dirfd` doesn't refer to a directory, return `Notdir`. return Err(Errno::Notdir); } - let desc = fe.as_descriptor(required_rights)?; - let dirfd = match &*desc.borrow() { - Descriptor::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into())?, - Descriptor::VirtualFile(virt) => virt.try_clone().map(Descriptor::VirtualFile)?, - _ => return Err(Errno::Badf), - }; + let handle = entry.as_handle(required_rights)?; + let dirfd = handle.try_clone()?; // Stack of directory file descriptors. Index 0 always corresponds with the directory provided // to this function. Entering a directory causes a file descriptor to be pushed, while handling @@ -198,9 +104,14 @@ pub(crate) fn get( } if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) { - match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head) - .open() - { + let fd = dir_stack.last().ok_or(Errno::Notcapable)?; + match fd.openat( + &head, + false, + false, + types::Oflags::DIRECTORY, + types::Fdflags::empty(), + ) { Ok(new_dir) => { dir_stack.push(new_dir); } @@ -211,12 +122,8 @@ pub(crate) fn get( // this with ENOTDIR because of the O_DIRECTORY flag. { // attempt symlink expansion - let mut link_path = PathRef::new( - dir_stack.last().ok_or(Errno::Notcapable)?, - &head, - ) - .readlink()?; - + let fd = dir_stack.last().ok_or(Errno::Notcapable)?; + let mut link_path = fd.readlinkat(&head)?; symlink_expansions += 1; if symlink_expansions > MAX_SYMLINK_EXPANSIONS { return Err(Errno::Loop); @@ -246,9 +153,8 @@ pub(crate) fn get( { // if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt // symlink expansion - match PathRef::new(dir_stack.last().ok_or(Errno::Notcapable)?, &head) - .readlink() - { + let fd = dir_stack.last().ok_or(Errno::Notcapable)?; + match fd.readlinkat(&head) { Ok(mut link_path) => { symlink_expansions += 1; if symlink_expansions > MAX_SYMLINK_EXPANSIONS { @@ -282,20 +188,14 @@ pub(crate) fn get( } // not a symlink, so we're done; - return Ok(PathGet { - dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?, - path: head, - }); + return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, head)); } } } None => { // no further components to process. means we've hit a case like "." or "a/..", or if the // input path has trailing slashes and `needs_final_component` is not set - return Ok(PathGet { - dirfd: dir_stack.pop().ok_or(Errno::Notcapable)?, - path: String::from("."), - }); + return Ok((dir_stack.pop().ok_or(Errno::Notcapable)?, String::from("."))); } } } diff --git a/crates/wasi-common/src/poll.rs b/crates/wasi-common/src/poll.rs index 755072fc1a..a0be6b17b5 100644 --- a/crates/wasi-common/src/poll.rs +++ b/crates/wasi-common/src/poll.rs @@ -1,10 +1,7 @@ -use crate::entry::Descriptor; -use crate::sys; +use crate::entry::EntryHandle; use crate::wasi::types; -use std::cell::RefCell; -use std::rc::Rc; -pub(crate) use sys::poll::*; +pub(crate) use crate::sys::poll::*; #[derive(Debug, Copy, Clone)] pub(crate) struct ClockEventData { @@ -12,9 +9,8 @@ pub(crate) struct ClockEventData { pub(crate) userdata: types::Userdata, } -#[derive(Debug)] pub(crate) struct FdEventData { - pub(crate) descriptor: Rc>, + pub(crate) handle: EntryHandle, pub(crate) r#type: types::Eventtype, pub(crate) userdata: types::Userdata, } diff --git a/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs index e4e4a604f3..23649b20b4 100644 --- a/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs +++ b/crates/wasi-common/src/snapshots/wasi_snapshot_preview1.rs @@ -1,13 +1,12 @@ -use crate::entry::{Descriptor, Entry, EntryRights}; -use crate::sandboxed_tty_writer::SandboxedTTYWriter; +use crate::entry::{Entry, EntryHandle, EntryRights}; +use crate::sys::clock; use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1; use crate::wasi::{types, AsBytes, Errno, Result}; use crate::WasiCtx; -use crate::{clock, fd, path, poll}; +use crate::{path, poll}; use log::{debug, error, trace}; use std::convert::TryInto; -use std::fs::File; -use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::io::{self, SeekFrom}; use wiggle::{GuestBorrows, GuestPtr}; impl<'a> WasiSnapshotPreview1 for WasiCtx { @@ -94,13 +93,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::FD_ADVISE); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - match &*desc.borrow() { - Descriptor::OsHandle(fd) => fd::advise(&fd, advice, offset, len)?, - Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len)?, - _ => return Err(Errno::Badf), - }; - Ok(()) + entry + .as_handle(&required_rights)? + .advise(advice, offset, len) } fn fd_allocate( @@ -111,24 +106,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::FD_ALLOCATE); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - match &*desc.borrow() { - Descriptor::OsHandle(fd) => { - let metadata = fd.metadata()?; - let current_size = metadata.len(); - let wanted_size = offset.checked_add(len).ok_or(Errno::TooBig)?; - // This check will be unnecessary when rust-lang/rust#63326 is fixed - if wanted_size > i64::max_value() as u64 { - return Err(Errno::TooBig); - } - if wanted_size > current_size { - fd.set_len(wanted_size)?; - } - } - Descriptor::VirtualFile(virt) => virt.allocate(offset, len)?, - _ => return Err(Errno::Badf), - }; - Ok(()) + entry.as_handle(&required_rights)?.allocate(offset, len) } fn fd_close(&self, fd: types::Fd) -> Result<()> { @@ -145,26 +123,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { fn fd_datasync(&self, fd: types::Fd) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::FD_DATASYNC); let entry = self.get_entry(fd)?; - let file = entry.as_descriptor(&required_rights)?; - match &*file.borrow() { - Descriptor::OsHandle(fd) => fd.sync_data()?, - Descriptor::VirtualFile(virt) => virt.datasync()?, - other => other.as_os_handle().sync_data()?, - }; - Ok(()) + entry.as_handle(&required_rights)?.datasync() } fn fd_fdstat_get(&self, fd: types::Fd) -> Result { - let fe = self.get_entry(fd)?; - let wasi_file = fe.as_descriptor(&EntryRights::empty())?; - let fs_flags = match &*wasi_file.borrow() { - Descriptor::OsHandle(wasi_fd) => fd::fdstat_get(&wasi_fd)?, - Descriptor::VirtualFile(virt) => virt.fdstat_get(), - other => fd::fdstat_get(&other.as_os_handle())?, - }; - let rights = fe.rights.get(); + let entry = self.get_entry(fd)?; + let file = entry.as_handle(&EntryRights::empty())?; + let fs_flags = file.fdstat_get()?; + let rights = entry.rights.get(); let fdstat = types::Fdstat { - fs_filetype: fe.file_type, + fs_filetype: entry.file_type, fs_rights_base: rights.base, fs_rights_inheriting: rights.inheriting, fs_flags, @@ -175,23 +143,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 entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - let maybe_new_desc = match &*desc.borrow() { - Descriptor::OsHandle(handle) => { - fd::fdstat_set_flags(&handle, flags)?.map(Descriptor::OsHandle) - } - Descriptor::VirtualFile(handle) => { - handle.fdstat_set_flags(flags)?.map(Descriptor::VirtualFile) - } - stream => { - fd::fdstat_set_flags(&stream.as_os_handle(), flags)?.map(Descriptor::OsHandle) - } - }; - // TODO What happens on None? - if let Some(new_desc) = maybe_new_desc { - *desc.borrow_mut() = new_desc; - } - Ok(()) + entry.as_handle(&required_rights)?.fdstat_set_flags(flags) } fn fd_fdstat_set_rights( @@ -212,29 +164,18 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { fn fd_filestat_get(&self, fd: types::Fd) -> Result { let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_GET); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - let host_filestat = match &*desc.borrow() { - Descriptor::OsHandle(fd) => fd::filestat_get(&fd)?, - Descriptor::VirtualFile(virt) => virt.filestat_get()?, - _ => return Err(Errno::Badf), - }; + 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 entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; // This check will be unnecessary when rust-lang/rust#63326 is fixed if size > i64::max_value() as u64 { return Err(Errno::TooBig); } - match &*desc.borrow() { - Descriptor::OsHandle(fd) => fd.set_len(size)?, - Descriptor::VirtualFile(virt) => virt.filestat_set_size(size)?, - _ => return Err(Errno::Badf), - }; - Ok(()) + entry.as_handle(&required_rights)?.filestat_set_size(size) } fn fd_filestat_set_times( @@ -246,9 +187,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::FD_FILESTAT_SET_TIMES); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - fd::filestat_set_times_impl(&desc.borrow(), atim, mtim, fst_flags)?; - Ok(()) + entry + .as_handle(&required_rights)? + .filestat_set_times(atim, mtim, fst_flags) } fn fd_pread( @@ -274,25 +215,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { let required_rights = EntryRights::from_base(types::Rights::FD_READ | types::Rights::FD_SEEK); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - if offset > i64::max_value() as u64 { return Err(Errno::Io); } - - let host_nread = match &*desc.borrow() { - Descriptor::OsHandle(fd) => { - let mut fd: &File = fd; - let cur_pos = fd.seek(SeekFrom::Current(0))?; - fd.seek(SeekFrom::Start(offset))?; - let nread = fd.read_vectored(&mut buf)?; - fd.seek(SeekFrom::Start(cur_pos))?; - nread - } - Descriptor::VirtualFile(virt) => virt.preadv(&mut buf, offset)?, - _ => return Err(Errno::Badf), - }; - let host_nread = host_nread.try_into()?; + let host_nread = entry + .as_handle(&required_rights)? + .preadv(&mut buf, offset)? + .try_into()?; Ok(host_nread) } @@ -362,26 +291,15 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { let required_rights = EntryRights::from_base(types::Rights::FD_WRITE | types::Rights::FD_SEEK); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; if offset > i64::max_value() as u64 { return Err(Errno::Io); } - let host_nwritten = match &*desc.borrow() { - Descriptor::OsHandle(fd) => { - let mut fd: &File = fd; - let cur_pos = fd.seek(SeekFrom::Current(0))?; - fd.seek(SeekFrom::Start(offset))?; - let nwritten = fd.write_vectored(&buf)?; - fd.seek(SeekFrom::Start(cur_pos))?; - nwritten - } - Descriptor::VirtualFile(virt) => virt.pwritev(&buf, offset)?, - _ => return Err(Errno::Badf), - }; - let host_nwritten = host_nwritten.try_into()?; - + let host_nwritten = entry + .as_handle(&required_rights)? + .pwritev(&buf, offset)? + .try_into()?; Ok(host_nwritten) } @@ -402,13 +320,10 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { let required_rights = EntryRights::from_base(types::Rights::FD_READ); let entry = self.get_entry(fd)?; - let host_nread = match &*entry.as_descriptor(&required_rights)?.borrow() { - Descriptor::OsHandle(file) => (file as &File).read_vectored(&mut slices)?, - Descriptor::VirtualFile(virt) => virt.read_vectored(&mut slices)?, - Descriptor::Stdin => io::stdin().read_vectored(&mut slices)?, - _ => return Err(Errno::Badf), - }; - let host_nread = host_nread.try_into()?; + let host_nread = entry + .as_handle(&required_rights)? + .read_vectored(&mut slices)? + .try_into()?; Ok(host_nread) } @@ -422,39 +337,26 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result { let required_rights = EntryRights::from_base(types::Rights::FD_READDIR); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - fn copy_entities>>( - iter: T, - buf: &GuestPtr, - buf_len: types::Size, - ) -> Result { - let mut bufused = 0; - let mut buf = buf.clone(); - for pair in iter { - let (dirent, name) = pair?; - let dirent_raw = dirent.as_bytes()?; - let dirent_len: types::Size = dirent_raw.len().try_into()?; - let name_raw = name.as_bytes(); - let name_len = name_raw.len().try_into()?; - let offset = dirent_len.checked_add(name_len).ok_or(Errno::Overflow)?; - if (buf_len - bufused) < offset { - break; - } else { - buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?; - buf = buf.add(dirent_len)?; - buf.as_array(name_len).copy_from_slice(name_raw)?; - buf = buf.add(name_len)?; - bufused += offset; - } + let mut bufused = 0; + let mut buf = buf.clone(); + for pair in entry.as_handle(&required_rights)?.readdir(cookie)? { + let (dirent, name) = pair?; + let dirent_raw = dirent.as_bytes()?; + let dirent_len: types::Size = dirent_raw.len().try_into()?; + let name_raw = name.as_bytes(); + let name_len = name_raw.len().try_into()?; + let offset = dirent_len.checked_add(name_len).ok_or(Errno::Overflow)?; + if (buf_len - bufused) < offset { + break; + } else { + buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?; + buf = buf.add(dirent_len)?; + buf.as_array(name_len).copy_from_slice(name_raw)?; + buf = buf.add(name_len)?; + bufused += offset; } - Ok(bufused) } - let bufused = match &*desc.borrow() { - Descriptor::OsHandle(file) => copy_entities(fd::readdir(file, cookie)?, buf, buf_len)?, - Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, buf, buf_len)?, - _ => return Err(Errno::Badf), - }; Ok(bufused) } @@ -495,43 +397,27 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { }; let required_rights = EntryRights::from_base(base); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; let pos = match whence { types::Whence::Cur => SeekFrom::Current(offset), types::Whence::End => SeekFrom::End(offset), types::Whence::Set => SeekFrom::Start(offset as u64), }; - let host_newoffset = match &*desc.borrow() { - Descriptor::OsHandle(fd) => (fd as &File).seek(pos)?, - Descriptor::VirtualFile(virt) => virt.seek(pos)?, - _ => return Err(Errno::Badf), - }; - + let host_newoffset = entry.as_handle(&required_rights)?.seek(pos)?; Ok(host_newoffset) } fn fd_sync(&self, fd: types::Fd) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::FD_SYNC); let entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - match &*desc.borrow() { - Descriptor::OsHandle(fd) => fd.sync_all()?, - Descriptor::VirtualFile(virt) => virt.sync()?, - _ => return Err(Errno::Badf), - }; - Ok(()) + 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 entry = self.get_entry(fd)?; - let desc = entry.as_descriptor(&required_rights)?; - let host_offset = match &*desc.borrow() { - Descriptor::OsHandle(fd) => (fd as &File).seek(SeekFrom::Current(0))?, - Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?, - _ => return Err(Errno::Badf), - }; - + let host_offset = entry + .as_handle(&required_rights)? + .seek(SeekFrom::Current(0))?; Ok(host_offset) } @@ -549,63 +435,28 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { }; slices.push(io::IoSlice::new(slice)); } - - // perform unbuffered writes let required_rights = EntryRights::from_base(types::Rights::FD_WRITE); let entry = self.get_entry(fd)?; let isatty = entry.isatty(); - let desc = entry.as_descriptor(&required_rights)?; - let host_nwritten = match &*desc.borrow() { - Descriptor::OsHandle(file) => { - if isatty { - SandboxedTTYWriter::new(&mut (file as &File)).write_vectored(&slices)? - } else { - (file as &File).write_vectored(&slices)? - } - } - Descriptor::VirtualFile(virt) => { - if isatty { - unimplemented!("writes to virtual tty"); - } else { - virt.write_vectored(&slices)? - } - } - Descriptor::Stdin => return Err(Errno::Badf), - Descriptor::Stdout => { - // lock for the duration of the scope - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - let nwritten = if isatty { - SandboxedTTYWriter::new(&mut stdout).write_vectored(&slices)? - } else { - stdout.write_vectored(&slices)? - }; - stdout.flush()?; - nwritten - } - // Always sanitize stderr, even if it's not directly connected to a tty, - // because stderr is meant for diagnostics rather than binary output, - // and may be redirected to a file which could end up being displayed - // on a tty later. - Descriptor::Stderr => { - SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&slices)? - } - }; - Ok(host_nwritten.try_into()?) + let host_nwritten = entry + .as_handle(&required_rights)? + .write_vectored(&slices, isatty)? + .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 entry = self.get_entry(dirfd)?; - let resolved = path::get( + let (dirfd, path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), path, false, )?; - resolved.create_directory() + dirfd.create_directory(&path) } fn path_filestat_get( @@ -616,20 +467,16 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result { let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_GET); let entry = self.get_entry(dirfd)?; - let resolved = path::get(&entry, &required_rights, flags, path, false)?; - let host_filestat = match resolved.dirfd() { - Descriptor::VirtualFile(virt) => virt - .openat( - std::path::Path::new(resolved.path()), - false, - false, - types::Oflags::empty(), - types::Fdflags::empty(), - )? - .filestat_get()?, - _ => path::filestat_get(resolved, flags)?, - }; - + let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?; + let host_filestat = dirfd + .openat( + &path, + false, + false, + types::Oflags::empty(), + types::Fdflags::empty(), + )? + .filestat_get()?; Ok(host_filestat) } @@ -644,13 +491,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::PATH_FILESTAT_SET_TIMES); let entry = self.get_entry(dirfd)?; - let resolved = path::get(&entry, &required_rights, flags, path, false)?; - match resolved.dirfd() { - Descriptor::VirtualFile(_virt) => { - unimplemented!("virtual filestat_set_times"); - } - _ => path::filestat_set_times(resolved, flags, atim, mtim, fst_flags), - } + let (dirfd, path) = path::get(&entry, &required_rights, flags, path, false)?; + dirfd + .openat( + &path, + false, + false, + types::Oflags::empty(), + types::Fdflags::empty(), + )? + .filestat_set_times(atim, mtim, fst_flags)?; + Ok(()) } fn path_link( @@ -663,7 +514,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_SOURCE); let old_entry = self.get_entry(old_fd)?; - let resolved_old = path::get( + let (old_dirfd, old_path) = path::get( &old_entry, &required_rights, types::Lookupflags::empty(), @@ -672,16 +523,17 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { )?; let required_rights = EntryRights::from_base(types::Rights::PATH_LINK_TARGET); let new_entry = self.get_entry(new_fd)?; - let resolved_new = path::get( + let (new_dirfd, new_path) = path::get( &new_entry, &required_rights, types::Lookupflags::empty(), new_path, false, )?; - path::link( - resolved_old, - resolved_new, + old_dirfd.link( + &old_path, + new_dirfd, + &new_path, old_flags.contains(&types::Lookupflags::SYMLINK_FOLLOW), ) } @@ -701,20 +553,15 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { oflags, fdflags, ); - trace!(" | needed_rights={}", needed_rights); - - let resolved = { - let entry = self.get_entry(dirfd)?; - path::get( - &entry, - &needed_rights, - dirflags, - path, - oflags & types::Oflags::CREAT != types::Oflags::empty(), - )? - }; - + let entry = self.get_entry(dirfd)?; + let (dirfd, path) = path::get( + &entry, + &needed_rights, + dirflags, + path, + oflags & types::Oflags::CREAT != types::Oflags::empty(), + )?; // which open mode do we need? let read = fs_rights_base & (types::Rights::FD_READ | types::Rights::FD_READDIR) != types::Rights::empty(); @@ -724,15 +571,13 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { | types::Rights::FD_ALLOCATE | types::Rights::FD_FILESTAT_SET_SIZE) != types::Rights::empty(); - trace!( " | calling path_open impl: read={}, write={}", read, write ); - - let fd = resolved.open_with(read, write, oflags, fdflags)?; - let fe = Entry::from(fd)?; + let fd = dirfd.openat(&path, read, write, oflags, fdflags)?; + let fe = Entry::from(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(); @@ -752,47 +597,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result { let required_rights = EntryRights::from_base(types::Rights::PATH_READLINK); let entry = self.get_entry(dirfd)?; - let resolved = path::get( + let (dirfd, path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), path, false, )?; - let slice = unsafe { let mut bc = GuestBorrows::new(); let buf = buf.as_array(buf_len); let raw = buf.as_raw(&mut bc)?; &mut *raw }; - let host_bufused = match resolved.dirfd() { - Descriptor::VirtualFile(_virt) => { - unimplemented!("virtual readlink"); - } - _ => path::readlink(resolved, slice)?, - }; - let host_bufused = host_bufused.try_into()?; + let host_bufused = dirfd.readlink(&path, slice)?.try_into()?; Ok(host_bufused) } fn path_remove_directory(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::PATH_REMOVE_DIRECTORY); let entry = self.get_entry(dirfd)?; - let resolved = path::get( + let (dirfd, path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), path, true, )?; - - debug!("path_remove_directory resolved={:?}", resolved); - - match resolved.dirfd() { - Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()), - _ => path::remove_directory(resolved), - } + dirfd.remove_directory(&path) } fn path_rename( @@ -804,7 +636,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_SOURCE); let entry = self.get_entry(old_fd)?; - let resolved_old = path::get( + let (old_dirfd, old_path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), @@ -813,26 +645,14 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { )?; let required_rights = EntryRights::from_base(types::Rights::PATH_RENAME_TARGET); let entry = self.get_entry(new_fd)?; - let resolved_new = path::get( + let (new_dirfd, new_path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), new_path, true, )?; - - debug!("path_rename resolved_old={:?}", resolved_old); - debug!("path_rename resolved_new={:?}", resolved_new); - - if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) = - (resolved_old.dirfd(), resolved_new.dirfd()) - { - path::rename(resolved_old, resolved_new) - } else { - // Virtual files do not support rename, at the moment, and streams don't have paths to - // rename, so any combination of Descriptor that gets here is an error in the making. - panic!("path_rename with one or more non-OS files"); - } + old_dirfd.rename(&old_path, new_dirfd, &new_path) } fn path_symlink( @@ -843,44 +663,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { ) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::PATH_SYMLINK); let entry = self.get_entry(dirfd)?; - let resolved_new = path::get( + let (new_fd, new_path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), new_path, true, )?; - let old_path = unsafe { let mut bc = GuestBorrows::new(); let raw = old_path.as_raw(&mut bc)?; &*raw }; - trace!(" | old_path='{}'", old_path); - - match resolved_new.dirfd() { - Descriptor::VirtualFile(_virt) => { - unimplemented!("virtual path_symlink"); - } - _non_virtual => path::symlink(old_path, resolved_new), - } + new_fd.symlink(&old_path, &new_path) } fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<()> { let required_rights = EntryRights::from_base(types::Rights::PATH_UNLINK_FILE); let entry = self.get_entry(dirfd)?; - let resolved = path::get( + let (dirfd, path) = path::get( &entry, &required_rights, types::Lookupflags::empty(), path, false, )?; - match resolved.dirfd() { - Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()), - _ => path::unlink_file(resolved), - } + dirfd.unlink_file(&path)?; + Ok(()) } fn poll_oneoff( @@ -949,7 +759,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } }; fd_events.push(poll::FdEventData { - descriptor: entry.as_descriptor(&required_rights)?, + handle: entry.as_handle(&required_rights)?, r#type: types::Eventtype::FdRead, userdata: subscription.userdata, }); @@ -975,7 +785,7 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } }; fd_events.push(poll::FdEventData { - descriptor: entry.as_descriptor(&required_rights)?, + handle: entry.as_handle(&required_rights)?, r#type: types::Eventtype::FdWrite, userdata: subscription.userdata, }); @@ -984,7 +794,6 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { } debug!("poll_oneoff events = {:?}", events); debug!("poll_oneoff timeout = {:?}", timeout); - debug!("poll_oneoff fd_events = {:?}", fd_events); // The underlying implementation should successfully and immediately return // if no events have been passed. Such situation may occur if all provided // events have been filtered out as errors in the code above. @@ -997,6 +806,9 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx { let event_ptr = event_ptr?; event_ptr.write(event)?; } + + trace!(" | *nevents={:?}", nevents); + Ok(nevents) } diff --git a/crates/wasi-common/src/clock.rs b/crates/wasi-common/src/sys/clock.rs similarity index 92% rename from crates/wasi-common/src/clock.rs rename to crates/wasi-common/src/sys/clock.rs index 9536e5034e..28a04f597b 100644 --- a/crates/wasi-common/src/clock.rs +++ b/crates/wasi-common/src/sys/clock.rs @@ -1,9 +1,8 @@ -use crate::sys; use crate::wasi::types::{Subclockflags, SubscriptionClock}; use crate::wasi::{Errno, Result}; use std::time::SystemTime; -pub(crate) use sys::clock::*; +pub(crate) use super::sys_impl::clock::*; pub(crate) fn to_relative_ns_delay(clock: &SubscriptionClock) -> Result { if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME { diff --git a/crates/wasi-common/src/fd.rs b/crates/wasi-common/src/sys/fd.rs similarity index 71% rename from crates/wasi-common/src/fd.rs rename to crates/wasi-common/src/sys/fd.rs index 05d4706f42..2fa59ee95c 100644 --- a/crates/wasi-common/src/fd.rs +++ b/crates/wasi-common/src/sys/fd.rs @@ -1,13 +1,12 @@ -use crate::entry::Descriptor; -use crate::sys; use crate::wasi::{types, Errno, Result}; use filetime::{set_file_handle_times, FileTime}; +use std::fs::File; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -pub(crate) use sys::fd::*; +pub(crate) use super::sys_impl::fd::*; -pub(crate) fn filestat_set_times_impl( - file: &Descriptor, +pub(crate) fn filestat_set_times( + file: &File, st_atim: types::Timestamp, st_mtim: types::Timestamp, fst_flags: types::Fstflags, @@ -39,13 +38,7 @@ pub(crate) fn filestat_set_times_impl( } else { None }; - match file { - Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into), - Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim), - _ => { - unreachable!( - "implementation error: fd should have been checked to not be a stream already" - ); - } - } + + set_file_handle_times(file, atim, mtim)?; + Ok(()) } diff --git a/crates/wasi-common/src/sys/mod.rs b/crates/wasi-common/src/sys/mod.rs index 849fb7ee16..61ed66700f 100644 --- a/crates/wasi-common/src/sys/mod.rs +++ b/crates/wasi-common/src/sys/mod.rs @@ -1,15 +1,22 @@ +pub(crate) mod clock; +pub(crate) mod fd; +pub(crate) mod oshandle; + use cfg_if::cfg_if; cfg_if! { if #[cfg(unix)] { mod unix; - pub(crate) use unix::*; + use unix as sys_impl; pub use unix::preopen_dir; } else if #[cfg(windows)] { mod windows; - pub(crate) use windows::*; + use windows as sys_impl; pub use windows::preopen_dir; } else { compile_error!("wasi-common doesn't compile for this platform yet"); } } + +pub(crate) use sys_impl::path; +pub(crate) use sys_impl::poll; diff --git a/crates/wasi-common/src/sys/oshandle.rs b/crates/wasi-common/src/sys/oshandle.rs new file mode 100644 index 0000000000..eeff049cce --- /dev/null +++ b/crates/wasi-common/src/sys/oshandle.rs @@ -0,0 +1,277 @@ +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/unix/bsd/mod.rs b/crates/wasi-common/src/sys/unix/bsd/mod.rs index a58b813e4a..a4a1882453 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 oshandle; +pub(crate) mod osfile; 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/osfile.rs b/crates/wasi-common/src/sys/unix/bsd/osfile.rs new file mode 100644 index 0000000000..0ead556838 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/bsd/osfile.rs @@ -0,0 +1,109 @@ +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/oshandle.rs b/crates/wasi-common/src/sys/unix/bsd/oshandle.rs deleted file mode 100644 index efd5d2602c..0000000000 --- a/crates/wasi-common/src/sys/unix/bsd/oshandle.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::wasi::Result; -use std::cell::{RefCell, RefMut}; -use std::fs; -use std::ops::Deref; -use std::os::unix::prelude::{AsRawFd, RawFd}; -use yanix::dir::Dir; - -#[derive(Debug)] -pub(crate) struct OsHandle { - file: fs::File, - // 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 OsHandle { - 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 fd = self.file.try_clone()?; - let d = Dir::from(fd)?; - *self.dir.borrow_mut() = Some(d); - } - Ok(RefMut::map(self.dir.borrow_mut(), |dir| { - dir.as_mut().unwrap() - })) - } -} - -impl From for OsHandle { - fn from(file: fs::File) -> Self { - Self { - file, - dir: RefCell::new(None), - } - } -} - -impl AsRawFd for OsHandle { - fn as_raw_fd(&self) -> RawFd { - self.file.as_raw_fd() - } -} - -impl Deref for OsHandle { - type Target = fs::File; - - fn deref(&self) -> &Self::Target { - &self.file - } -} diff --git a/crates/wasi-common/src/sys/unix/bsd/path.rs b/crates/wasi-common/src/sys/unix/bsd/path.rs index 702c0d1b42..872d3f25cd 100644 --- a/crates/wasi-common/src/sys/unix/bsd/path.rs +++ b/crates/wasi-common/src/sys/unix/bsd/path.rs @@ -1,16 +1,10 @@ -use crate::path::PathGet; +use super::osfile::OsFile; use crate::wasi::{Errno, Result}; use std::os::unix::prelude::AsRawFd; -pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { +pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { use yanix::file::{unlinkat, AtFlag}; - match unsafe { - unlinkat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::empty(), - ) - } { + match unsafe { unlinkat(dirfd.as_raw_fd(), path, AtFlag::empty()) } { Err(err) => { let raw_errno = err.raw_os_error().unwrap(); // Non-Linux implementations may return EPERM when attempting to remove a @@ -23,13 +17,7 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { use yanix::file::{fstatat, FileType}; if raw_errno == libc::EPERM { - match unsafe { - fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::SYMLINK_NOFOLLOW, - ) - } { + match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } { Ok(stat) => { if FileType::from_stat_st_mode(stat.st_mode) == FileType::Directory { return Err(Errno::Isdir); @@ -47,13 +35,17 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { } } -pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { +pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path: &str) -> Result<()> { use yanix::file::{fstatat, symlinkat, AtFlag}; log::debug!("path_symlink old_path = {:?}", old_path); - log::debug!("path_symlink resolved = {:?}", resolved); + log::debug!( + "path_symlink (new_dirfd, new_path) = ({:?}, {:?})", + new_dirfd, + new_path + ); - match unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path()) } { + match unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path) } { Err(err) => { if err.raw_os_error().unwrap() == libc::ENOTDIR { // On BSD, symlinkat returns ENOTDIR when it should in fact @@ -61,14 +53,9 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { // the trailing slash in the target path. Thus, we strip // the trailing slash and check if the path exists, and // adjust the error code appropriately. - let new_path = resolved.path().trim_end_matches('/'); - match unsafe { - fstatat( - resolved.dirfd().as_raw_fd(), - new_path, - AtFlag::SYMLINK_NOFOLLOW, - ) - } { + let new_path = new_path.trim_end_matches('/'); + match unsafe { fstatat(new_dirfd.as_raw_fd(), new_path, AtFlag::SYMLINK_NOFOLLOW) } + { Ok(_) => return Err(Errno::Exist), Err(err) => { log::debug!("path_symlink fstatat error: {:?}", err); @@ -81,14 +68,19 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { } } -pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { +pub(crate) fn rename( + old_dirfd: &OsFile, + old_path: &str, + new_dirfd: &OsFile, + new_path: &str, +) -> Result<()> { use yanix::file::{fstatat, renameat, AtFlag}; match unsafe { renameat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - resolved_new.dirfd().as_raw_fd(), - resolved_new.path(), + old_dirfd.as_raw_fd(), + old_path, + new_dirfd.as_raw_fd(), + new_path, ) } { Err(err) => { @@ -103,16 +95,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> // Verify on other BSD-based OSes. if err.raw_os_error().unwrap() == libc::ENOENT { // check if the source path exists - match unsafe { - fstatat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - AtFlag::SYMLINK_NOFOLLOW, - ) - } { + match unsafe { fstatat(old_dirfd.as_raw_fd(), old_path, AtFlag::SYMLINK_NOFOLLOW) } + { Ok(_) => { // check if destination contains a trailing slash - if resolved_new.path().contains('/') { + if new_path.contains('/') { return Err(Errno::Notdir); } else { return Err(Errno::Noent); diff --git a/crates/wasi-common/src/sys/unix/emscripten/mod.rs b/crates/wasi-common/src/sys/unix/emscripten/mod.rs index 8f233de8fe..151d735025 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/oshandle.rs"] -pub(crate) mod oshandle; +#[path = "../linux/osfile.rs"] +pub(crate) mod osfile; #[path = "../linux/path.rs"] pub(crate) mod path; diff --git a/crates/wasi-common/src/sys/unix/entry.rs b/crates/wasi-common/src/sys/unix/entry.rs deleted file mode 100644 index 49e82ec481..0000000000 --- a/crates/wasi-common/src/sys/unix/entry.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::entry::{Descriptor, EntryRights, OsHandleRef}; -use crate::wasi::{types, RightsExt}; -use std::fs::File; -use std::io; -use std::mem::ManuallyDrop; -use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, RawFd}; - -pub(crate) use super::sys_impl::oshandle::*; - -impl AsRawFd for Descriptor { - fn as_raw_fd(&self) -> RawFd { - match self { - Self::OsHandle(file) => file.as_raw_fd(), - Self::VirtualFile(_) => panic!("virtual files do not have a raw fd"), - Self::Stdin => io::stdin().as_raw_fd(), - Self::Stdout => io::stdout().as_raw_fd(), - Self::Stderr => io::stderr().as_raw_fd(), - } - } -} - -pub(crate) fn descriptor_as_oshandle<'lifetime>( - desc: &'lifetime Descriptor, -) -> OsHandleRef<'lifetime> { - OsHandleRef::new(ManuallyDrop::new(OsHandle::from(unsafe { - File::from_raw_fd(desc.as_raw_fd()) - }))) -} - -/// Returns the set of all possible rights that are both relevant for the file -/// type and consistent with the open mode. -/// -/// This function is unsafe because it operates on a raw file descriptor. -pub(crate) unsafe fn determine_type_and_access_rights( - fd: &Fd, -) -> io::Result<(types::Filetype, EntryRights)> { - let (file_type, mut rights) = determine_type_rights(fd)?; - - use yanix::{fcntl, file::OFlag}; - let flags = fcntl::get_status_flags(fd.as_raw_fd())?; - let accmode = flags & OFlag::ACCMODE; - if accmode == OFlag::RDONLY { - rights.base &= !types::Rights::FD_WRITE; - } else if accmode == OFlag::WRONLY { - rights.base &= !types::Rights::FD_READ; - } - - Ok((file_type, rights)) -} - -/// Returns the set of all possible rights that are relevant for file type. -/// -/// This function is unsafe because it operates on a raw file descriptor. -pub(crate) unsafe fn determine_type_rights( - fd: &Fd, -) -> io::Result<(types::Filetype, EntryRights)> { - let (file_type, rights) = { - // we just make a `File` here for convenience; we don't want it to close when it drops - let file = std::mem::ManuallyDrop::new(std::fs::File::from_raw_fd(fd.as_raw_fd())); - let ft = file.metadata()?.file_type(); - let (filetype, base, inheriting) = if ft.is_block_device() { - log::debug!("Host fd {:?} is a block device", fd.as_raw_fd()); - ( - types::Filetype::BlockDevice, - types::Rights::block_device_base(), - types::Rights::block_device_inheriting(), - ) - } else if ft.is_char_device() { - log::debug!("Host fd {:?} is a char device", fd.as_raw_fd()); - use yanix::file::isatty; - if isatty(fd.as_raw_fd())? { - ( - types::Filetype::CharacterDevice, - types::Rights::tty_base(), - types::Rights::tty_base(), - ) - } else { - ( - types::Filetype::CharacterDevice, - types::Rights::character_device_base(), - types::Rights::character_device_inheriting(), - ) - } - } else if ft.is_dir() { - log::debug!("Host fd {:?} is a directory", fd.as_raw_fd()); - ( - types::Filetype::Directory, - types::Rights::directory_base(), - types::Rights::directory_inheriting(), - ) - } else if ft.is_file() { - log::debug!("Host fd {:?} is a file", fd.as_raw_fd()); - ( - types::Filetype::RegularFile, - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ) - } else if ft.is_socket() { - log::debug!("Host fd {:?} is a socket", fd.as_raw_fd()); - use yanix::socket::{get_socket_type, SockType}; - match get_socket_type(fd.as_raw_fd())? { - SockType::Datagram => ( - types::Filetype::SocketDgram, - types::Rights::socket_base(), - types::Rights::socket_inheriting(), - ), - SockType::Stream => ( - types::Filetype::SocketStream, - types::Rights::socket_base(), - types::Rights::socket_inheriting(), - ), - _ => return Err(io::Error::from_raw_os_error(libc::EINVAL)), - } - } else if ft.is_fifo() { - log::debug!("Host fd {:?} is a fifo", fd.as_raw_fd()); - ( - types::Filetype::Unknown, - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ) - } else { - log::debug!("Host fd {:?} is unknown", fd.as_raw_fd()); - return Err(io::Error::from_raw_os_error(libc::EINVAL)); - }; - (filetype, EntryRights::new(base, inheriting)) - }; - - Ok((file_type, rights)) -} diff --git a/crates/wasi-common/src/sys/unix/fd.rs b/crates/wasi-common/src/sys/unix/fd.rs index d150a68ac6..e192bd44d8 100644 --- a/crates/wasi-common/src/sys/unix/fd.rs +++ b/crates/wasi-common/src/sys/unix/fd.rs @@ -1,4 +1,4 @@ -use crate::sys::entry::OsHandle; +use super::oshandle::OsFile; use crate::wasi::{self, types, Result}; use std::convert::TryInto; use std::fs::File; @@ -9,7 +9,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. @@ -18,7 +18,7 @@ pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result Result { +pub(crate) fn filestat_get(file: &File) -> Result { use yanix::file::fstat; let stat = unsafe { fstat(file.as_raw_fd())? }; Ok(stat.try_into()?) } pub(crate) fn readdir<'a>( - os_handle: &'a OsHandle, + file: &'a OsFile, cookie: types::Dircookie, -) -> Result> + 'a> { +) -> 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 = os_handle.dir_stream()?; + let mut dir = file.dir_stream()?; // Seek if needed. Unless cookie is wasi::__WASI_DIRCOOKIE_START, // new items may not be returned to the caller. @@ -65,7 +65,7 @@ pub(crate) fn readdir<'a>( dir.seek(loc); } - Ok(DirIter::new(dir).map(|entry| { + Ok(Box::new(DirIter::new(dir).map(|entry| { let entry: Entry = entry?; let name = entry.file_name().to_str()?.to_owned(); let dirent = types::Dirent { @@ -75,5 +75,5 @@ pub(crate) fn readdir<'a>( d_type: entry.file_type().into(), }; Ok((dirent, name)) - })) + }))) } diff --git a/crates/wasi-common/src/sys/unix/linux/mod.rs b/crates/wasi-common/src/sys/unix/linux/mod.rs index 9d7cc60191..b587ed6b6f 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 oshandle; +pub(crate) mod osfile; 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/osfile.rs b/crates/wasi-common/src/sys/unix/linux/osfile.rs new file mode 100644 index 0000000000..388f245dfa --- /dev/null +++ b/crates/wasi-common/src/sys/unix/linux/osfile.rs @@ -0,0 +1,82 @@ +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/oshandle.rs b/crates/wasi-common/src/sys/unix/linux/oshandle.rs deleted file mode 100644 index 241f6cc6b7..0000000000 --- a/crates/wasi-common/src/sys/unix/linux/oshandle.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::wasi::Result; -use std::fs; -use std::ops::Deref; -use std::os::unix::prelude::{AsRawFd, RawFd}; -use yanix::dir::Dir; - -#[derive(Debug)] -pub(crate) struct OsHandle(fs::File); - -impl OsHandle { - 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 fd = self.0.try_clone()?; - // TODO This doesn't look very clean. Can we do something about it? - // Boxing is needed here in order to satisfy `yanix`'s trait requirement for the `DirIter` - // where `T: Deref`. - Ok(Box::new(Dir::from(fd)?)) - } -} - -impl From for OsHandle { - fn from(file: fs::File) -> Self { - Self(file) - } -} - -impl AsRawFd for OsHandle { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl Deref for OsHandle { - type Target = fs::File; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/crates/wasi-common/src/sys/unix/linux/path.rs b/crates/wasi-common/src/sys/unix/linux/path.rs index 89a53b27f2..02ee2b3fdd 100644 --- a/crates/wasi-common/src/sys/unix/linux/path.rs +++ b/crates/wasi-common/src/sys/unix/linux/path.rs @@ -1,46 +1,41 @@ -use crate::entry::Descriptor; -use crate::path::PathGet; +use super::osfile::OsFile; use crate::wasi::Result; use std::os::unix::prelude::AsRawFd; -pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { +pub(crate) fn unlink_file(dirfd: &OsFile, 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<()> { + use yanix::file::symlinkat; + + log::debug!("path_symlink old_path = {:?}", old_path); + log::debug!( + "path_symlink (new_dirfd, new_path) = ({:?}, {:?})", + new_dirfd, + new_path + ); + + unsafe { symlinkat(old_path, new_dirfd.as_raw_fd(), new_path)? }; + Ok(()) +} + +pub(crate) fn rename( + old_dirfd: &OsFile, + old_path: &str, + new_dirfd: &OsFile, + new_path: &str, +) -> Result<()> { + use yanix::file::renameat; unsafe { - unlinkat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::empty(), + renameat( + old_dirfd.as_raw_fd(), + old_path, + new_dirfd.as_raw_fd(), + new_path, )? }; Ok(()) } - -pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { - use yanix::file::symlinkat; - - log::debug!("path_symlink old_path = {:?}", old_path); - log::debug!("path_symlink resolved = {:?}", resolved); - - unsafe { symlinkat(old_path, resolved.dirfd().as_raw_fd(), resolved.path())? }; - Ok(()) -} - -pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { - use yanix::file::renameat; - match (resolved_old.dirfd(), resolved_new.dirfd()) { - (Descriptor::OsHandle(resolved_old_file), Descriptor::OsHandle(resolved_new_file)) => { - unsafe { - renameat( - resolved_old_file.as_raw_fd(), - resolved_old.path(), - resolved_new_file.as_raw_fd(), - resolved_new.path(), - )? - }; - Ok(()) - } - _ => { - unimplemented!("path_link with one or more virtual files"); - } - } -} diff --git a/crates/wasi-common/src/sys/unix/mod.rs b/crates/wasi-common/src/sys/unix/mod.rs index cafc26f3aa..8bad50fc79 100644 --- a/crates/wasi-common/src/sys/unix/mod.rs +++ b/crates/wasi-common/src/sys/unix/mod.rs @@ -1,6 +1,6 @@ pub(crate) mod clock; -pub(crate) mod entry; pub(crate) mod fd; +pub(crate) mod oshandle; pub(crate) mod path; pub(crate) mod poll; @@ -24,16 +24,13 @@ cfg_if::cfg_if! { use crate::wasi::{types, Errno, Result}; use std::convert::{TryFrom, TryInto}; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::io; use std::path::Path; -use sys_impl::O_RSYNC; use yanix::clock::ClockId; use yanix::file::{AtFlag, OFlag}; -pub(crate) fn dev_null() -> io::Result { - OpenOptions::new().read(true).write(true).open("/dev/null") -} +pub(crate) use sys_impl::*; pub fn preopen_dir>(path: P) -> io::Result { File::open(path) diff --git a/crates/wasi-common/src/sys/unix/oshandle.rs b/crates/wasi-common/src/sys/unix/oshandle.rs new file mode 100644 index 0000000000..236ba07884 --- /dev/null +++ b/crates/wasi-common/src/sys/unix/oshandle.rs @@ -0,0 +1,123 @@ +use crate::entry::EntryRights; +use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt}; +use crate::wasi::{types, RightsExt}; +use std::fs::{File, OpenOptions}; +use std::io; +use std::mem::ManuallyDrop; +use std::os::unix::prelude::{AsRawFd, FileTypeExt, FromRawFd, IntoRawFd, RawFd}; + +pub(crate) use super::sys_impl::osfile::*; + +impl AsRawFd for OsHandle { + 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(), + } + } +} + +impl AsFile for OsHandle { + fn as_file(&self) -> ManuallyDrop { + let file = unsafe { File::from_raw_fd(self.as_raw_fd()) }; + ManuallyDrop::new(file) + } +} + +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)) + } +} diff --git a/crates/wasi-common/src/sys/unix/path.rs b/crates/wasi-common/src/sys/unix/path.rs index e693041ade..e52aa0f67f 100644 --- a/crates/wasi-common/src/sys/unix/path.rs +++ b/crates/wasi-common/src/sys/unix/path.rs @@ -1,16 +1,13 @@ -use crate::entry::{Descriptor, EntryRights}; -use crate::path::PathGet; -use crate::sys::entry::OsHandle; -use crate::sys::unix::sys_impl; +use super::oshandle::OsFile; +use crate::entry::EntryRights; +use crate::sys::oshandle::OsHandle; use crate::wasi::{types, Errno, Result}; -use std::convert::TryInto; use std::ffi::OsStr; -use std::fs::File; use std::os::unix::prelude::{AsRawFd, FromRawFd, OsStrExt}; use std::str; use yanix::file::OFlag; -pub(crate) use sys_impl::path::*; +pub(crate) use super::sys_impl::path::*; /// Creates owned WASI path from OS string. /// @@ -44,32 +41,14 @@ pub(crate) fn open_rights( if fdflags.contains(OFlag::DSYNC) { needed_inheriting |= types::Rights::FD_DATASYNC; } - if fdflags.intersects(sys_impl::O_RSYNC | OFlag::SYNC) { + if fdflags.intersects(super::O_RSYNC | OFlag::SYNC) { needed_inheriting |= types::Rights::FD_SYNC; } EntryRights::new(needed_base, needed_inheriting) } -pub(crate) fn openat(dirfd: &File, path: &str) -> Result { - use std::os::unix::prelude::{AsRawFd, FromRawFd}; - use yanix::file::{openat, Mode}; - - log::debug!("path_get openat path = {:?}", path); - - let raw_fd = unsafe { - openat( - dirfd.as_raw_fd(), - path, - OFlag::RDONLY | OFlag::DIRECTORY | OFlag::NOFOLLOW, - Mode::empty(), - )? - }; - let file = unsafe { File::from_raw_fd(raw_fd) }; - Ok(file) -} - -pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result { +pub(crate) fn readlinkat(dirfd: &OsFile, path: &str) -> Result { use std::os::unix::prelude::AsRawFd; use yanix::file::readlinkat; @@ -80,15 +59,17 @@ pub(crate) fn readlinkat(dirfd: &File, path: &str) -> Result { Ok(path) } -pub(crate) fn create_directory(base: &File, path: &str) -> Result<()> { +pub(crate) fn create_directory(base: &OsFile, path: &str) -> Result<()> { use yanix::file::{mkdirat, Mode}; unsafe { mkdirat(base.as_raw_fd(), path, Mode::from_bits_truncate(0o777))? }; Ok(()) } pub(crate) fn link( - resolved_old: PathGet, - resolved_new: PathGet, + old_dirfd: &OsFile, + old_path: &str, + new_dirfd: &OsFile, + new_path: &str, follow_symlinks: bool, ) -> Result<()> { use yanix::file::{linkat, AtFlag}; @@ -99,10 +80,10 @@ pub(crate) fn link( }; unsafe { linkat( - resolved_old.dirfd().as_raw_fd(), - resolved_old.path(), - resolved_new.dirfd().as_raw_fd(), - resolved_new.path(), + old_dirfd.as_raw_fd(), + old_path, + new_dirfd.as_raw_fd(), + new_path, flags, )? }; @@ -110,12 +91,13 @@ pub(crate) fn link( } pub(crate) fn open( - resolved: PathGet, + dirfd: &OsFile, + 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 { @@ -139,13 +121,14 @@ pub(crate) fn open( // umask is, but don't set the executable flag, because it isn't yet // meaningful for WASI programs to create executable files. - log::debug!("path_open resolved = {:?}", resolved); + log::debug!("path_open dirfd = {:?}", dirfd); + log::debug!("path_open path = {:?}", path); log::debug!("path_open oflags = {:?}", nix_all_oflags); let fd_no = unsafe { openat( - resolved.dirfd().as_raw_fd(), - resolved.path(), + dirfd.as_raw_fd(), + path, nix_all_oflags, Mode::from_bits_truncate(0o666), ) @@ -156,13 +139,7 @@ pub(crate) fn open( match e.raw_os_error().unwrap() { // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket libc::ENXIO => { - match unsafe { - fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::SYMLINK_NOFOLLOW, - ) - } { + match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } { Ok(stat) => { if FileType::from_stat_st_mode(stat.st_mode) == FileType::Socket { return Err(Errno::Notsup); @@ -178,13 +155,7 @@ pub(crate) fn open( libc::ENOTDIR if !(nix_all_oflags & (OFlag::NOFOLLOW | OFlag::DIRECTORY)).is_empty() => { - match unsafe { - fstatat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::SYMLINK_NOFOLLOW, - ) - } { + match unsafe { fstatat(dirfd.as_raw_fd(), path, AtFlag::SYMLINK_NOFOLLOW) } { Ok(stat) => { if FileType::from_stat_st_mode(stat.st_mode) == FileType::Symlink { return Err(Errno::Loop); @@ -210,13 +181,13 @@ 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 { File::from_raw_fd(new_fd) }).into()) + Ok(OsHandle::from(unsafe { OsFile::from_raw_fd(new_fd) })) } -pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result { +pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result { use std::cmp::min; use yanix::file::readlinkat; - let read_link = unsafe { readlinkat(resolved.dirfd().as_raw_fd(), resolved.path())? }; + let read_link = unsafe { readlinkat(dirfd.as_raw_fd(), path)? }; let read_link = from_host(read_link)?; let copy_len = min(read_link.len(), buf.len()); if copy_len > 0 { @@ -225,73 +196,8 @@ pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result { Ok(copy_len) } -pub(crate) fn filestat_get( - resolved: PathGet, - dirflags: types::Lookupflags, -) -> Result { - use yanix::file::fstatat; - let atflags = dirflags.into(); - let filestat = unsafe { fstatat(resolved.dirfd().as_raw_fd(), resolved.path(), atflags)? }; - let filestat = filestat.try_into()?; - Ok(filestat) -} - -pub(crate) fn filestat_set_times( - resolved: PathGet, - dirflags: types::Lookupflags, - st_atim: types::Timestamp, - st_mtim: types::Timestamp, - fst_flags: types::Fstflags, -) -> Result<()> { - use std::time::{Duration, UNIX_EPOCH}; - use yanix::filetime::*; - - let set_atim = fst_flags.contains(&types::Fstflags::ATIM); - let set_atim_now = fst_flags.contains(&types::Fstflags::ATIM_NOW); - let set_mtim = fst_flags.contains(&types::Fstflags::MTIM); - let set_mtim_now = fst_flags.contains(&types::Fstflags::MTIM_NOW); - - if (set_atim && set_atim_now) || (set_mtim && set_mtim_now) { - return Err(Errno::Inval); - } - - let symlink_nofollow = types::Lookupflags::SYMLINK_FOLLOW != dirflags; - let atim = if set_atim { - let time = UNIX_EPOCH + Duration::from_nanos(st_atim); - FileTime::FileTime(filetime::FileTime::from_system_time(time)) - } else if set_atim_now { - FileTime::Now - } else { - FileTime::Omit - }; - let mtim = if set_mtim { - let time = UNIX_EPOCH + Duration::from_nanos(st_mtim); - FileTime::FileTime(filetime::FileTime::from_system_time(time)) - } else if set_mtim_now { - FileTime::Now - } else { - FileTime::Omit - }; - - utimensat( - &resolved.dirfd().as_os_handle(), - resolved.path(), - atim, - mtim, - symlink_nofollow, - )?; - Ok(()) -} - -pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> { +pub(crate) fn remove_directory(dirfd: &OsFile, path: &str) -> Result<()> { use yanix::file::{unlinkat, AtFlag}; - - unsafe { - unlinkat( - resolved.dirfd().as_raw_fd(), - resolved.path(), - AtFlag::REMOVEDIR, - )? - }; + 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 dcd382d293..7b5ffddded 100644 --- a/crates/wasi-common/src/sys/unix/poll.rs +++ b/crates/wasi-common/src/sys/unix/poll.rs @@ -1,15 +1,17 @@ +use super::super::oshandle::OsHandle; use crate::poll::{ClockEventData, FdEventData}; +use crate::sys::oshandle::AsFile; use crate::wasi::{types, Errno, Result}; use std::io; +use std::{convert::TryInto, os::unix::prelude::AsRawFd}; +use yanix::file::fionread; +use yanix::poll::{poll, PollFd, PollFlags}; pub(crate) fn oneoff( timeout: Option, fd_events: Vec, events: &mut Vec, ) -> Result<()> { - use std::{convert::TryInto, os::unix::prelude::AsRawFd}; - use yanix::poll::{poll, PollFd, PollFlags}; - if fd_events.is_empty() && timeout.is_none() { return Ok(()); } @@ -26,7 +28,12 @@ pub(crate) fn oneoff( // events we filtered before. If we get something else here, the code has a serious bug. _ => unreachable!(), }; - unsafe { PollFd::new(event.descriptor.borrow().as_raw_fd(), flags) } + let handle = event + .handle + .as_any() + .downcast_ref::() + .expect("can poll FdEvent for OS resources only"); + unsafe { PollFd::new(handle.as_raw_fd(), flags) } }) .collect(); @@ -73,26 +80,22 @@ fn handle_fd_event( ready_events: impl Iterator, events: &mut Vec, ) -> Result<()> { - use crate::entry::Descriptor; - use std::{convert::TryInto, os::unix::prelude::AsRawFd}; - use yanix::{file::fionread, poll::PollFlags}; - - fn query_nbytes(fd: &Descriptor) -> Result { + fn query_nbytes(handle: &OsHandle) -> Result { // fionread may overflow for large files, so use another way for regular files. - if let Descriptor::OsHandle(os_handle) = fd { - let meta = os_handle.metadata()?; + if 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(os_handle.as_raw_fd())? }; + let host_offset = unsafe { tell(file.as_raw_fd())? }; return Ok(len - host_offset); } } - unsafe { Ok(fionread(fd.as_raw_fd())?.into()) } + unsafe { Ok(fionread(handle.as_raw_fd())?.into()) } } for (fd_event, poll_fd) in ready_events { - log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event); + // log::debug!("poll_oneoff_handle_fd_event fd_event = {:?}", fd_event); log::debug!("poll_oneoff_handle_fd_event poll_fd = {:?}", poll_fd); let revents = match poll_fd.revents() { @@ -103,7 +106,12 @@ fn handle_fd_event( log::debug!("poll_oneoff_handle_fd_event revents = {:?}", revents); let nbytes = if fd_event.r#type == types::Eventtype::FdRead { - query_nbytes(&fd_event.descriptor.borrow())? + let handle = fd_event + .handle + .as_any() + .downcast_ref::() + .expect("can poll FdEvent for OS resources only"); + query_nbytes(handle)? } else { 0 }; diff --git a/crates/wasi-common/src/sys/windows/entry.rs b/crates/wasi-common/src/sys/windows/entry.rs deleted file mode 100644 index ffb96f4f44..0000000000 --- a/crates/wasi-common/src/sys/windows/entry.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::entry::{Descriptor, EntryRights, OsHandleRef}; -use crate::wasi::{types, RightsExt}; -use std::fs::File; -use std::io; -use std::mem::ManuallyDrop; -use std::ops::Deref; -use std::os::windows::prelude::{AsRawHandle, FromRawHandle, RawHandle}; - -#[derive(Debug)] -pub(crate) struct OsHandle(File); - -impl From for OsHandle { - fn from(file: File) -> Self { - Self(file) - } -} - -impl AsRawHandle for OsHandle { - fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() - } -} - -impl Deref for OsHandle { - type Target = File; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRawHandle for Descriptor { - fn as_raw_handle(&self) -> RawHandle { - match self { - Self::OsHandle(file) => file.as_raw_handle(), - Self::VirtualFile(_file) => { - unimplemented!("virtual as_raw_handle"); - } - Self::Stdin => io::stdin().as_raw_handle(), - Self::Stdout => io::stdout().as_raw_handle(), - Self::Stderr => io::stderr().as_raw_handle(), - } - } -} - -pub(crate) fn descriptor_as_oshandle<'lifetime>( - desc: &'lifetime Descriptor, -) -> OsHandleRef<'lifetime> { - OsHandleRef::new(ManuallyDrop::new(OsHandle::from(unsafe { - File::from_raw_handle(desc.as_raw_handle()) - }))) -} - -/// Returns the set of all possible rights that are both relevant for the file -/// type and consistent with the open mode. -/// -/// This function is unsafe because it operates on a raw file descriptor. -pub(crate) unsafe fn determine_type_and_access_rights( - handle: &Handle, -) -> io::Result<(types::Filetype, EntryRights)> { - use winx::file::{query_access_information, AccessMode}; - - let (file_type, mut rights) = determine_type_rights(handle)?; - - match file_type { - types::Filetype::Directory | types::Filetype::RegularFile => { - let mode = query_access_information(handle.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((file_type, rights)) -} - -/// Returns the set of all possible rights that are relevant for file type. -/// -/// This function is unsafe because it operates on a raw file descriptor. -pub(crate) unsafe fn determine_type_rights( - handle: &Handle, -) -> io::Result<(types::Filetype, EntryRights)> { - let (file_type, rights) = { - let file_type = winx::file::get_file_type(handle.as_raw_handle())?; - let (file_type, base, inheriting) = if file_type.is_char() { - // character file: LPT device or console - // TODO: rule out LPT device - ( - types::Filetype::CharacterDevice, - types::Rights::tty_base(), - types::Rights::tty_base(), - ) - } else if file_type.is_disk() { - // disk file: file, dir or disk device - let file = std::mem::ManuallyDrop::new(File::from_raw_handle(handle.as_raw_handle())); - let meta = file.metadata()?; - if meta.is_dir() { - ( - types::Filetype::Directory, - types::Rights::directory_base(), - types::Rights::directory_inheriting(), - ) - } else if meta.is_file() { - ( - types::Filetype::RegularFile, - types::Rights::regular_file_base(), - types::Rights::regular_file_inheriting(), - ) - } 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, - types::Rights::socket_base(), - types::Rights::socket_inheriting(), - ) - } else { - return Err(io::Error::from_raw_os_error(libc::EINVAL)); - }; - (file_type, EntryRights::new(base, inheriting)) - }; - Ok((file_type, rights)) -} diff --git a/crates/wasi-common/src/sys/windows/fd.rs b/crates/wasi-common/src/sys/windows/fd.rs index d9be83b9c3..0cc20a79bb 100644 --- a/crates/wasi-common/src/sys/windows/fd.rs +++ b/crates/wasi-common/src/sys/windows/fd.rs @@ -1,6 +1,7 @@ use super::file_serial_no; +use super::oshandle::OsFile; use crate::path; -use crate::sys::entry::OsHandle; +use crate::sys::oshandle::AsFile; use crate::wasi::{types, Result}; use log::trace; use std::convert::TryInto; @@ -10,9 +11,9 @@ use std::os::windows::prelude::{AsRawHandle, FromRawHandle}; use std::path::Path; use winx::file::{AccessMode, FileModeInformation, Flags}; -pub(crate) fn fdstat_get(fd: &File) -> Result { +pub(crate) fn fdstat_get(file: &File) -> Result { let mut fdflags = types::Fdflags::empty(); - let handle = fd.as_raw_handle(); + let handle = file.as_raw_handle(); let access_mode = winx::file::query_access_information(handle)?; let mode = winx::file::query_mode_information(handle)?; @@ -34,27 +35,30 @@ pub(crate) fn fdstat_get(fd: &File) -> Result { Ok(fdflags) } -pub(crate) fn fdstat_set_flags(fd: &File, fdflags: types::Fdflags) -> Result> { - let handle = fd.as_raw_handle(); - +// TODO Investigate further for Stdio handles. `ReOpenFile` requires the file +// 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> { + let handle = file.as_raw_handle(); let access_mode = winx::file::query_access_information(handle)?; - let new_access_mode = file_access_mode_from_fdflags( fdflags, access_mode.contains(AccessMode::FILE_READ_DATA), access_mode.contains(AccessMode::FILE_WRITE_DATA) | access_mode.contains(AccessMode::FILE_APPEND_DATA), ); - unsafe { - Ok(Some(OsHandle::from(File::from_raw_handle( - winx::file::reopen_file(handle, new_access_mode, fdflags.into())?, - )))) + Ok(Some(OsFile::from_raw_handle(winx::file::reopen_file( + handle, + new_access_mode, + fdflags.into(), + )?))) } } pub(crate) fn advise( - _file: &File, + _file: &OsFile, _advice: types::Advice, _offset: types::Filesize, _len: types::Filesize, @@ -116,13 +120,13 @@ fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: boo // .. gets cookie = 2 // other entries, in order they were returned by FindNextFileW get subsequent integers as their cookies pub(crate) fn readdir( - fd: &File, + file: &OsFile, cookie: types::Dircookie, -) -> Result>> { +) -> Result>>> { use winx::file::get_file_path; let cookie = cookie.try_into()?; - let path = get_file_path(fd)?; + let path = get_file_path(&file.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 @@ -155,7 +159,7 @@ pub(crate) fn readdir( // small host_buf, but this is difficult to implement efficiently. // // See https://github.com/WebAssembly/WASI/issues/61 for more details. - Ok(iter.skip(cookie)) + Ok(Box::new(iter.skip(cookie))) } fn dirent_from_path>( @@ -182,7 +186,7 @@ fn dirent_from_path>( Ok((dirent, name)) } -pub(crate) fn filestat_get(file: &std::fs::File) -> Result { +pub(crate) fn filestat_get(file: &File) -> Result { let filestat = file.try_into()?; Ok(filestat) } diff --git a/crates/wasi-common/src/sys/windows/mod.rs b/crates/wasi-common/src/sys/windows/mod.rs index b875f4b1c3..c493115ebe 100644 --- a/crates/wasi-common/src/sys/windows/mod.rs +++ b/crates/wasi-common/src/sys/windows/mod.rs @@ -1,22 +1,18 @@ pub(crate) mod clock; -pub(crate) mod entry; pub(crate) mod fd; +pub(crate) mod oshandle; pub(crate) mod path; pub(crate) mod poll; use crate::wasi::{types, Errno, Result}; use std::convert::{TryFrom, TryInto}; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; use std::{io, string}; use winapi::shared::winerror; use winx::file::{CreationDisposition, Flags}; -pub(crate) fn dev_null() -> io::Result { - OpenOptions::new().read(true).write(true).open("NUL") -} - 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/oshandle.rs b/crates/wasi-common/src/sys/windows/oshandle.rs new file mode 100644 index 0000000000..a313416a2a --- /dev/null +++ b/crates/wasi-common/src/sys/windows/oshandle.rs @@ -0,0 +1,169 @@ +use crate::entry::EntryRights; +use crate::sys::oshandle::{AsFile, OsHandle, OsHandleExt}; +use crate::wasi::{types, RightsExt}; +use std::cell::Cell; +use std::fs::{File, OpenOptions}; +use std::io; +use std::mem::ManuallyDrop; +use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; + +#[derive(Debug)] +pub(crate) struct OsFile(Cell); + +impl OsFile { + /// Consumes `other` taking the ownership of the underlying + /// `RawHandle` file handle. + pub(crate) fn update_from(&self, other: Self) { + let new_handle = other.into_raw_handle(); + let old_handle = self.0.get(); + self.0.set(new_handle); + // We need to remember to close the old_handle. + unsafe { + 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 { + fn drop(&mut self) { + unsafe { + File::from_raw_handle(self.as_raw_handle()); + } + } +} + +impl AsRawHandle for OsFile { + fn as_raw_handle(&self) -> RawHandle { + self.0.get() + } +} + +impl FromRawHandle for OsFile { + unsafe fn from_raw_handle(handle: RawHandle) -> Self { + Self(Cell::new(handle)) + } +} + +impl IntoRawHandle for OsFile { + 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/path.rs b/crates/wasi-common/src/sys/windows/path.rs index ad9e4c8f91..a33ffc08c6 100644 --- a/crates/wasi-common/src/sys/windows/path.rs +++ b/crates/wasi-common/src/sys/windows/path.rs @@ -1,18 +1,83 @@ -use crate::entry::{Descriptor, EntryRights}; -use crate::fd; -use crate::path::PathGet; -use crate::sys::entry::OsHandle; +use super::oshandle::OsFile; +use crate::entry::EntryRights; +use crate::sys::oshandle::{AsFile, OsHandle}; use crate::wasi::{types, Errno, Result}; -use log::debug; -use std::convert::TryInto; use std::ffi::{OsStr, OsString}; -use std::fs::{File, Metadata, OpenOptions}; +use std::fs::{self, Metadata, OpenOptions}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::os::windows::fs::OpenOptionsExt; use std::path::{Path, PathBuf}; use winapi::shared::winerror; use winx::file::AccessMode; +fn strip_trailing_slashes_and_concatenate(dirfd: &OsFile, path: &str) -> Result> { + if path.ends_with('/') { + let suffix = path.trim_end_matches('/'); + concatenate(dirfd, Path::new(suffix)).map(Some) + } else { + Ok(None) + } +} + +fn strip_extended_prefix>(path: P) -> OsString { + let path: Vec = path.as_ref().encode_wide().collect(); + if &[92, 92, 63, 92] == &path[0..4] { + OsString::from_wide(&path[4..]) + } else { + OsString::from_wide(&path) + } +} + +fn concatenate>(file: &OsFile, path: P) -> Result { + use winx::file::get_file_path; + + // WASI is not able to deal with absolute paths + // so error out if absolute + if path.as_ref().is_absolute() { + return Err(Errno::Notcapable); + } + + let dir_path = get_file_path(&file.as_file())?; + // concatenate paths + let mut out_path = PathBuf::from(dir_path); + out_path.push(path.as_ref()); + // strip extended prefix; otherwise we will error out on any relative + // components with `out_path` + let out_path = PathBuf::from(strip_extended_prefix(out_path)); + + log::debug!("out_path={:?}", out_path); + + Ok(out_path) +} + +fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode { + let mut access_mode = AccessMode::READ_CONTROL; + + // We always need `FILE_WRITE_ATTRIBUTES` so that we can set attributes such as filetimes, etc. + access_mode.insert(AccessMode::FILE_WRITE_ATTRIBUTES); + + // Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode + // The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead + // These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below) + if read { + access_mode.insert(AccessMode::FILE_GENERIC_READ); + } + + if write { + access_mode.insert(AccessMode::FILE_GENERIC_WRITE); + } + + // For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA. + // This makes the handle "append only". + // Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior). + if fdflags.contains(&types::Fdflags::APPEND) { + access_mode.insert(AccessMode::FILE_APPEND_DATA); + access_mode.remove(AccessMode::FILE_WRITE_DATA); + } + + access_mode +} + /// Creates owned WASI path from OS string. /// /// NB WASI spec requires OS string to be valid UTF-8. Otherwise, @@ -23,24 +88,6 @@ pub(crate) fn from_host>(s: S) -> Result { Ok(s) } -pub(crate) trait PathGetExt { - fn concatenate(&self) -> Result; -} - -impl PathGetExt for PathGet { - fn concatenate(&self) -> Result { - match self.dirfd() { - Descriptor::OsHandle(file) => concatenate(file, Path::new(self.path())), - Descriptor::VirtualFile(_virt) => { - panic!("concatenate on a virtual base"); - } - Descriptor::Stdin | Descriptor::Stdout | Descriptor::Stderr => { - unreachable!("streams do not have paths and should not be accessible via PathGet"); - } - } - } -} - pub(crate) fn open_rights( input_rights: &EntryRights, oflags: types::Oflags, @@ -69,30 +116,7 @@ pub(crate) fn open_rights( EntryRights::new(needed_base, needed_inheriting) } -pub(crate) fn openat(dirfd: &File, path: &str) -> Result { - use std::fs::OpenOptions; - use std::os::windows::fs::OpenOptionsExt; - use winx::file::Flags; - - let path = concatenate(dirfd, Path::new(path))?; - let err = match OpenOptions::new() - .read(true) - .custom_flags(Flags::FILE_FLAG_BACKUP_SEMANTICS.bits()) - .open(&path) - { - Ok(file) => return Ok(file), - Err(e) => e, - }; - if let Some(code) = err.raw_os_error() { - log::debug!("openat error={:?}", code); - if code as u32 == winerror::ERROR_INVALID_NAME { - return Err(Errno::Notdir); - } - } - Err(err.into()) -} - -pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result { +pub(crate) fn readlinkat(dirfd: &OsFile, s_path: &str) -> Result { use winx::file::get_file_path; let path = concatenate(dirfd, Path::new(s_path))?; @@ -102,7 +126,7 @@ pub(crate) fn readlinkat(dirfd: &File, 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)?; + 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) @@ -127,54 +151,25 @@ pub(crate) fn readlinkat(dirfd: &File, s_path: &str) -> Result { Err(err.into()) } -fn strip_extended_prefix>(path: P) -> OsString { - let path: Vec = path.as_ref().encode_wide().collect(); - if &[92, 92, 63, 92] == &path[0..4] { - OsString::from_wide(&path[4..]) - } else { - OsString::from_wide(&path) - } -} - -fn concatenate>(file: &File, path: P) -> Result { - use winx::file::get_file_path; - - // WASI is not able to deal with absolute paths - // so error out if absolute - if path.as_ref().is_absolute() { - return Err(Errno::Notcapable); - } - - let dir_path = get_file_path(file)?; - // concatenate paths - let mut out_path = PathBuf::from(dir_path); - out_path.push(path.as_ref()); - // strip extended prefix; otherwise we will error out on any relative - // components with `out_path` - let out_path = PathBuf::from(strip_extended_prefix(out_path)); - - log::debug!("out_path={:?}", out_path); - - Ok(out_path) -} - -pub(crate) fn create_directory(file: &File, path: &str) -> Result<()> { +pub(crate) fn create_directory(file: &OsFile, path: &str) -> Result<()> { let path = concatenate(file, path)?; std::fs::create_dir(&path)?; Ok(()) } pub(crate) fn link( - resolved_old: PathGet, - resolved_new: PathGet, + old_dirfd: &OsFile, + old_path: &str, + new_dirfd: &OsFile, + new_path: &str, follow_symlinks: bool, ) -> Result<()> { use std::fs; - let mut old_path = resolved_old.concatenate()?; - let new_path = resolved_new.concatenate()?; + let mut old_path = concatenate(old_dirfd, old_path)?; + let new_path = concatenate(new_dirfd, new_path)?; if follow_symlinks { // in particular, this will return an error if the target path doesn't exist - debug!("Following symlinks for path: {:?}", old_path); + log::debug!("Following symlinks for path: {:?}", old_path); old_path = fs::canonicalize(&old_path).map_err(|e| match e.raw_os_error() { // fs::canonicalize under Windows will return: // * ERROR_FILE_NOT_FOUND, if it encounters a dangling symlink @@ -183,39 +178,32 @@ pub(crate) fn link( _ => e.into(), })?; } - fs::hard_link(&old_path, &new_path).or_else(|err| { - match err.raw_os_error() { - Some(code) => { - debug!("path_link at fs::hard_link error code={:?}", code); - match code as u32 { - winerror::ERROR_ACCESS_DENIED => { - // If an attempt is made to create a hard link to a directory, POSIX-compliant - // implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted - // to `EACCES`. We detect and correct this case here. - if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) { - return Err(Errno::Perm); - } - } - _ => {} - } - - Err(err.into()) - } - None => { - log::debug!("Inconvertible OS error: {}", err); - Err(Errno::Io) + let err = match fs::hard_link(&old_path, &new_path) { + Ok(()) => return Ok(()), + Err(e) => e, + }; + if let Some(code) = err.raw_os_error() { + log::debug!("path_link at fs::hard_link error code={:?}", code); + if code as u32 == winerror::ERROR_ACCESS_DENIED { + // If an attempt is made to create a hard link to a directory, POSIX-compliant + // implementations of link return `EPERM`, but `ERROR_ACCESS_DENIED` is converted + // to `EACCES`. We detect and correct this case here. + if fs::metadata(&old_path).map(|m| m.is_dir()).unwrap_or(false) { + return Err(Errno::Perm); } } - }) + } + Err(err.into()) } pub(crate) fn open( - resolved: PathGet, + dirfd: &OsFile, + 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); @@ -245,9 +233,7 @@ pub(crate) fn open( } _ => {} } - - let path = resolved.concatenate()?; - + let path = concatenate(dirfd, path)?; match path.symlink_metadata().map(|metadata| metadata.file_type()) { Ok(file_type) => { // check if we are trying to open a symlink @@ -262,12 +248,18 @@ pub(crate) fn open( Err(err) => match err.raw_os_error() { Some(code) => { log::debug!("path_open at symlink_metadata error code={:?}", code); - - if code as u32 != winerror::ERROR_FILE_NOT_FOUND { - return Err(err.into()); - } - // file not found, let it proceed to actually - // trying to open it + match code as u32 { + winerror::ERROR_FILE_NOT_FOUND => { + // file not found, let it proceed to actually + // trying to open it + } + winerror::ERROR_INVALID_NAME => { + // TODO rethink this. For now, migrate how we handled + // it in `path::openat` on Windows. + return Err(Errno::Notdir); + } + _ => return Err(err.into()), + }; } None => { log::debug!("Inconvertible OS error: {}", err); @@ -284,49 +276,25 @@ pub(crate) fn open( } let flags: Flags = fdflags.into(); - opts.access_mode(access_mode.bits()) + let file = opts + .access_mode(access_mode.bits()) .custom_flags(flags.bits()) - .open(&path) - .map(|f| OsHandle::from(f).into()) - .map_err(Into::into) + .open(&path)?; + let handle = OsHandle::from(file); + Ok(handle) } -fn file_access_mode_from_fdflags(fdflags: types::Fdflags, read: bool, write: bool) -> AccessMode { - let mut access_mode = AccessMode::READ_CONTROL; - - // Note that `GENERIC_READ` and `GENERIC_WRITE` cannot be used to properly support append-only mode - // The file-specific flags `FILE_GENERIC_READ` and `FILE_GENERIC_WRITE` are used here instead - // These flags have the same semantic meaning for file objects, but allow removal of specific permissions (see below) - if read { - access_mode.insert(AccessMode::FILE_GENERIC_READ); - } - - if write { - access_mode.insert(AccessMode::FILE_GENERIC_WRITE); - } - - // For append, grant the handle FILE_APPEND_DATA access but *not* FILE_WRITE_DATA. - // This makes the handle "append only". - // Changes to the file pointer will be ignored (like POSIX's O_APPEND behavior). - if fdflags.contains(&types::Fdflags::APPEND) { - access_mode.insert(AccessMode::FILE_APPEND_DATA); - access_mode.remove(AccessMode::FILE_WRITE_DATA); - } - - access_mode -} - -pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result { +pub(crate) fn readlink(dirfd: &OsFile, path: &str, buf: &mut [u8]) -> Result { use winx::file::get_file_path; - let path = resolved.concatenate()?; + let path = concatenate(dirfd, path)?; let target_path = path.read_link()?; // since on Windows we are effectively emulating 'at' syscalls // we need to strip the prefix from the absolute path // as otherwise we will error out since WASI is not capable // of dealing with absolute paths - let dir_path = get_file_path(&resolved.dirfd().as_os_handle())?; + let dir_path = 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) @@ -353,20 +321,16 @@ pub(crate) fn readlink(resolved: PathGet, buf: &mut [u8]) -> Result { } } -fn strip_trailing_slashes_and_concatenate(resolved: &PathGet) -> Result> { - if resolved.path().ends_with('/') { - let suffix = resolved.path().trim_end_matches('/'); - concatenate(&resolved.dirfd().as_os_handle(), Path::new(suffix)).map(Some) - } else { - Ok(None) - } -} - -pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> { +pub(crate) fn rename( + old_dirfd: &OsFile, + old_path_: &str, + new_dirfd: &OsFile, + new_path_: &str, +) -> Result<()> { use std::fs; - let old_path = resolved_old.concatenate()?; - let new_path = resolved_new.concatenate()?; + let old_path = concatenate(old_dirfd, old_path_)?; + let new_path = concatenate(new_dirfd, new_path_)?; // First sanity check: check we're not trying to rename dir to file or vice versa. // NB on Windows, the former is actually permitted [std::fs::rename]. @@ -377,7 +341,7 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> } // Second sanity check: check we're not trying to rename a file into a path // ending in a trailing slash. - if old_path.is_file() && resolved_new.path().ends_with('/') { + if old_path.is_file() && new_path_.ends_with('/') { return Err(Errno::Notdir); } @@ -406,7 +370,9 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> winerror::ERROR_INVALID_NAME => { // If source contains trailing slashes, check if we are dealing with // a file instead of a dir, and if so, throw ENOTDIR. - if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved_old)? { + if let Some(path) = + strip_trailing_slashes_and_concatenate(old_dirfd, old_path_)? + { if path.is_file() { return Err(Errno::Notdir); } @@ -424,38 +390,11 @@ pub(crate) fn rename(resolved_old: PathGet, resolved_new: PathGet) -> Result<()> } } -pub(crate) fn filestat_get( - resolved: PathGet, - _dirflags: types::Lookupflags, -) -> Result { - let path = resolved.concatenate()?; - let file = File::open(path)?; - let filestat = (&file).try_into()?; - Ok(filestat) -} - -pub(crate) fn filestat_set_times( - resolved: PathGet, - _dirflags: types::Lookupflags, - st_atim: types::Timestamp, - st_mtim: types::Timestamp, - fst_flags: types::Fstflags, -) -> Result<()> { - use winx::file::AccessMode; - let path = resolved.concatenate()?; - let file = OpenOptions::new() - .access_mode(AccessMode::FILE_WRITE_ATTRIBUTES.bits()) - .open(path)?; - let modifiable_fd = Descriptor::OsHandle(OsHandle::from(file)); - fd::filestat_set_times_impl(&modifiable_fd, st_atim, st_mtim, fst_flags) -} - -pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { - use std::fs; +pub(crate) fn symlink(old_path: &str, new_dirfd: &OsFile, new_path_: &str) -> Result<()> { use std::os::windows::fs::{symlink_dir, symlink_file}; - let old_path = concatenate(&resolved.dirfd().as_os_handle(), Path::new(old_path))?; - let new_path = resolved.concatenate()?; + let old_path = concatenate(new_dirfd, Path::new(old_path))?; + let new_path = concatenate(new_dirfd, new_path_)?; // Windows distinguishes between file and directory symlinks. // If the source doesn't exist or is an exotic file type, we fall back @@ -488,7 +427,9 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { // // Since POSIX will return EEXIST in such case, we simulate this behavior winerror::ERROR_INVALID_NAME => { - if let Some(path) = strip_trailing_slashes_and_concatenate(&resolved)? { + if let Some(path) = + strip_trailing_slashes_and_concatenate(new_dirfd, new_path_)? + { if path.exists() { return Err(Errno::Exist); } @@ -506,10 +447,10 @@ pub(crate) fn symlink(old_path: &str, resolved: PathGet) -> Result<()> { } } -pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { +pub(crate) fn unlink_file(dirfd: &OsFile, path: &str) -> Result<()> { use std::fs; - let path = resolved.concatenate()?; + let path = concatenate(dirfd, path)?; let file_type = path .symlink_metadata() .map(|metadata| metadata.file_type())?; @@ -548,7 +489,7 @@ pub(crate) fn unlink_file(resolved: PathGet) -> Result<()> { } } -pub(crate) fn remove_directory(resolved: PathGet) -> Result<()> { - let path = resolved.concatenate()?; +pub(crate) fn remove_directory(dirfd: &OsFile, 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 7472501d56..7e56c39829 100644 --- a/crates/wasi-common/src/sys/windows/poll.rs +++ b/crates/wasi-common/src/sys/windows/poll.rs @@ -1,11 +1,11 @@ -use crate::entry::Descriptor; +use super::super::oshandle::OsHandle; use crate::poll::{ClockEventData, FdEventData}; +use crate::sys::oshandle::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::rc::Rc; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; use std::sync::Mutex; use std::thread; @@ -141,10 +141,18 @@ fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec) { - let size = match &*event.descriptor.borrow() { - Descriptor::OsHandle(os_handle) => { + 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 { - os_handle.metadata().map(|m| m.len()).map_err(Into::into) + 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 @@ -154,12 +162,9 @@ fn handle_rw_event(event: FdEventData, out_events: &mut Vec) { } } // We return the only universally correct lower bound, see the comment later in the function. - Descriptor::Stdin => Ok(1), + OsHandle::Stdin => Ok(1), // On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows. - Descriptor::Stdout | Descriptor::Stderr => Ok(0), - Descriptor::VirtualFile(_) => { - panic!("virtual files do not get rw events"); - } + OsHandle::Stdout | OsHandle::Stderr => Ok(0), }; let new_event = make_rw_event(&event, size); @@ -201,21 +206,21 @@ pub(crate) fn oneoff( let mut pipe_events = vec![]; for event in fd_events { - let descriptor = Rc::clone(&event.descriptor); - match &*descriptor.borrow() { - Descriptor::Stdin if event.r#type == types::Eventtype::FdRead => { - stdin_events.push(event) - } + 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 // 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. - Descriptor::Stdin | Descriptor::Stderr | Descriptor::Stdout => { - immediate_events.push(event) - } - Descriptor::OsHandle(os_handle) => { - let ftype = unsafe { winx::file::get_file_type(os_handle.as_raw_handle()) }?; + 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); @@ -227,9 +232,6 @@ pub(crate) fn oneoff( unreachable!(); } } - Descriptor::VirtualFile(_) => { - panic!("virtual files do not get rw events"); - } }; } diff --git a/crates/wasi-common/src/virtfs.rs b/crates/wasi-common/src/virtfs.rs index 8627c8afd2..521119f428 100644 --- a/crates/wasi-common/src/virtfs.rs +++ b/crates/wasi-common/src/virtfs.rs @@ -1,6 +1,8 @@ +use crate::entry::EntryRights; +use crate::handle::Handle; use crate::wasi::{self, types, Errno, Result, RightsExt}; -use filetime::FileTime; use log::trace; +use std::any::Any; use std::cell::{Cell, RefCell}; use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -22,132 +24,10 @@ impl VirtualDirEntry { } /// Files and directories may be moved, and for implementation reasons retain a reference to their -/// parent VirtualFile, so files that can be moved must provide an interface to update their parent +/// parent Handle, so files that can be moved must provide an interface to update their parent /// reference. pub(crate) trait MovableFile { - fn set_parent(&self, new_parent: Option>); -} - -/// `VirtualFile` encompasses the whole interface of a `File`, `Directory`, or `Stream`-like -/// object, suitable for forwarding from `wasi-common` public interfaces. `File` and -/// `Directory`-style objects can be moved, so implemetors of this trait must also implement -/// `MovableFile`. -/// -/// Default implementations of functions here fail in ways that are intended to mimic a file-like -/// object with no permissions, no content, and that cannot be used in any way. -// TODO This trait should potentially be made unsafe since we need to assert that we don't -// reenter wasm or try to reborrow/read/etc. from wasm memory. -pub(crate) trait VirtualFile: MovableFile { - fn fdstat_get(&self) -> types::Fdflags { - types::Fdflags::empty() - } - - fn try_clone(&self) -> io::Result>; - - fn readlinkat(&self, _path: &Path) -> Result { - Err(Errno::Acces) - } - - fn openat( - &self, - _path: &Path, - _read: bool, - _write: bool, - _oflags: types::Oflags, - _fd_flags: types::Fdflags, - ) -> Result> { - Err(Errno::Acces) - } - - fn remove_directory(&self, _path: &str) -> Result<()> { - Err(Errno::Acces) - } - - fn unlink_file(&self, _path: &str) -> Result<()> { - Err(Errno::Acces) - } - - fn datasync(&self) -> Result<()> { - Err(Errno::Inval) - } - - fn sync(&self) -> Result<()> { - Ok(()) - } - - fn create_directory(&self, _path: &Path) -> Result<()> { - Err(Errno::Acces) - } - - fn readdir( - &self, - _cookie: types::Dircookie, - ) -> Result>>> { - Err(Errno::Badf) - } - - fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result { - Err(Errno::Badf) - } - - fn preadv(&self, _buf: &mut [io::IoSliceMut], _offset: u64) -> Result { - Err(Errno::Badf) - } - - fn pwritev(&self, _buf: &[io::IoSlice], _offset: u64) -> Result { - Err(Errno::Badf) - } - - fn seek(&self, _offset: SeekFrom) -> Result { - Err(Errno::Badf) - } - - fn advise( - &self, - _advice: types::Advice, - _offset: types::Filesize, - _len: types::Filesize, - ) -> Result<()> { - Err(Errno::Badf) - } - - fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> { - Err(Errno::Badf) - } - - fn filestat_get(&self) -> Result { - Err(Errno::Badf) - } - - fn filestat_set_times(&self, _atim: Option, _mtim: Option) -> Result<()> { - Err(Errno::Badf) - } - - fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> { - Err(Errno::Badf) - } - - fn fdstat_set_flags(&self, _fdflags: types::Fdflags) -> Result>> { - Err(Errno::Badf) - } - - fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result { - Err(Errno::Badf) - } - - fn get_file_type(&self) -> types::Filetype; - - fn is_directory(&self) -> bool { - self.get_file_type() == types::Filetype::Directory - } - - fn get_rights_base(&self) -> types::Rights { - types::Rights::empty() - } - - fn get_rights_inheriting(&self) -> types::Rights { - types::Rights::empty() - } + fn set_parent(&self, new_parent: Option>); } pub trait FileContents { @@ -255,7 +135,7 @@ impl VecFileContents { /// of data and permissions on a filesystem. pub struct InMemoryFile { cursor: Cell, - parent: Rc>>>, + parent: Rc>>>, fd_flags: Cell, data: Rc>>, } @@ -281,17 +161,16 @@ impl InMemoryFile { } impl MovableFile for InMemoryFile { - fn set_parent(&self, new_parent: Option>) { + fn set_parent(&self, new_parent: Option>) { *self.parent.borrow_mut() = new_parent; } } -impl VirtualFile for InMemoryFile { - fn fdstat_get(&self) -> types::Fdflags { - self.fd_flags.get() +impl Handle for InMemoryFile { + fn as_any(&self) -> &dyn Any { + self } - - fn try_clone(&self) -> io::Result> { + fn try_clone(&self) -> io::Result> { Ok(Box::new(Self { cursor: Cell::new(0), fd_flags: self.fd_flags.clone(), @@ -299,68 +178,113 @@ impl VirtualFile for InMemoryFile { data: Rc::clone(&self.data), })) } - - fn readlinkat(&self, _path: &Path) -> Result { - // no symlink support, so always say it's invalid. - Err(Errno::Notdir) + fn get_file_type(&self) -> io::Result { + Ok(types::Filetype::RegularFile) } - - fn openat( + fn get_rights(&self) -> io::Result { + Ok(EntryRights::new( + types::Rights::regular_file_base(), + types::Rights::regular_file_inheriting(), + )) + } + // FdOps + fn advise( &self, - path: &Path, - read: bool, - write: bool, - oflags: types::Oflags, - fd_flags: types::Fdflags, - ) -> Result> { - log::trace!( - "InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}", - path, - read, - write, - oflags, - fd_flags - ); + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<()> { + // we'll just ignore advice for now, unless it's totally invalid + Ok(()) + } + fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> { + let new_limit = offset.checked_add(len).ok_or(Errno::Fbig)?; + let mut data = self.data.borrow_mut(); - if oflags.contains(&types::Oflags::DIRECTORY) { - log::trace!( - "InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.", - path - ); - log::trace!(" return Notdir"); - return Err(Errno::Notdir); + if new_limit > data.max_size() { + return Err(Errno::Fbig); } - if path == Path::new(".") { - return self.try_clone().map_err(Into::into); - } else if path == Path::new("..") { - match &*self.parent.borrow() { - Some(file) => file.try_clone().map_err(Into::into), - None => self.try_clone().map_err(Into::into), - } - } else { - Err(Errno::Acces) + if new_limit > data.size() { + data.resize(new_limit)?; } - } - fn remove_directory(&self, _path: &str) -> Result<()> { - Err(Errno::Notdir) + Ok(()) } - - fn unlink_file(&self, _path: &str) -> Result<()> { - Err(Errno::Notdir) + fn fdstat_get(&self) -> Result { + Ok(self.fd_flags.get()) } - - fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result>> { + fn fdstat_set_flags(&self, fdflags: types::Fdflags) -> Result<()> { self.fd_flags.set(fdflags); - // We return None here to signal that the operation succeeded on the original - // file descriptor and mutating the original WASI Descriptor is thus unnecessary. - // This is needed as on Windows this operation required reopening a file. So we're - // adhering to the common signature required across platforms. - Ok(None) + Ok(()) } + fn filestat_get(&self) -> Result { + let stat = types::Filestat { + dev: 0, + ino: 0, + nlink: 0, + size: self.data.borrow().size(), + atim: 0, + ctim: 0, + mtim: 0, + filetype: self.get_file_type()?, + }; + Ok(stat) + } + fn filestat_set_size(&self, st_size: types::Filesize) -> Result<()> { + let mut data = self.data.borrow_mut(); + if st_size > data.max_size() { + return Err(Errno::Fbig); + } + data.resize(st_size) + } + fn preadv(&self, buf: &mut [io::IoSliceMut], offset: types::Filesize) -> Result { + self.data.borrow_mut().preadv(buf, offset) + } + fn pwritev(&self, buf: &[io::IoSlice], offset: types::Filesize) -> Result { + self.data.borrow_mut().pwritev(buf, offset) + } + fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { + trace!("read_vectored(iovs={:?})", iovs); + trace!(" | *read_start={:?}", self.cursor.get()); + self.data.borrow_mut().preadv(iovs, self.cursor.get()) + } + fn seek(&self, offset: SeekFrom) -> Result { + let content_len = self.data.borrow().size(); + match offset { + SeekFrom::Current(offset) => { + let new_cursor = if offset < 0 { + self.cursor + .get() + .checked_sub(offset.wrapping_neg() as u64) + .ok_or(Errno::Inval)? + } else { + self.cursor + .get() + .checked_add(offset as u64) + .ok_or(Errno::Inval)? + }; + self.cursor.set(std::cmp::min(content_len, new_cursor)); + } + SeekFrom::End(offset) => { + // A negative offset from the end would be past the end of the file, + let offset: u64 = offset.try_into().map_err(|_| Errno::Inval)?; + self.cursor.set(content_len.saturating_sub(offset)); + } + SeekFrom::Start(offset) => { + // A negative offset from the end would be before the start of the file. + let offset: u64 = offset.try_into().map_err(|_| Errno::Inval)?; + self.cursor.set(std::cmp::min(content_len, offset)); + } + } + + 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(); @@ -406,110 +330,73 @@ impl VirtualFile for InMemoryFile { Ok(written) } - - fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { - trace!("read_vectored(iovs={:?})", iovs); - trace!(" | *read_start={:?}", self.cursor.get()); - self.data.borrow_mut().preadv(iovs, self.cursor.get()) + // PathOps + fn create_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) } - - fn preadv(&self, buf: &mut [io::IoSliceMut], offset: types::Filesize) -> Result { - self.data.borrow_mut().preadv(buf, offset) - } - - fn pwritev(&self, buf: &[io::IoSlice], offset: types::Filesize) -> Result { - self.data.borrow_mut().pwritev(buf, offset) - } - - fn seek(&self, offset: SeekFrom) -> Result { - let content_len = self.data.borrow().size(); - match offset { - SeekFrom::Current(offset) => { - let new_cursor = if offset < 0 { - self.cursor - .get() - .checked_sub(offset.wrapping_neg() as u64) - .ok_or(Errno::Inval)? - } else { - self.cursor - .get() - .checked_add(offset as u64) - .ok_or(Errno::Inval)? - }; - self.cursor.set(std::cmp::min(content_len, new_cursor)); - } - SeekFrom::End(offset) => { - // A negative offset from the end would be past the end of the file, - let offset: u64 = offset.try_into().map_err(|_| Errno::Inval)?; - self.cursor.set(content_len.saturating_sub(offset)); - } - SeekFrom::Start(offset) => { - // A negative offset from the end would be before the start of the file. - let offset: u64 = offset.try_into().map_err(|_| Errno::Inval)?; - self.cursor.set(std::cmp::min(content_len, offset)); - } - } - - Ok(self.cursor.get()) - } - - fn advise( + fn openat( &self, - _advice: types::Advice, - _offset: types::Filesize, - _len: types::Filesize, + path: &str, + read: bool, + write: bool, + oflags: types::Oflags, + fd_flags: types::Fdflags, + ) -> Result> { + log::trace!( + "InMemoryFile::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}", + path, + read, + write, + oflags, + fd_flags + ); + + if oflags.contains(&types::Oflags::DIRECTORY) { + log::trace!( + "InMemoryFile::openat was passed oflags DIRECTORY, but {:?} is a file.", + path + ); + log::trace!(" return Notdir"); + return Err(Errno::Notdir); + } + + if path == "." { + return self.try_clone().map_err(Into::into); + } else if path == ".." { + match &*self.parent.borrow() { + Some(file) => file.try_clone().map_err(Into::into), + None => self.try_clone().map_err(Into::into), + } + } else { + Err(Errno::Acces) + } + } + fn link( + &self, + _old_path: &str, + _new_handle: Box, + _new_path: &str, + _follow: bool, ) -> Result<()> { - // we'll just ignore advice for now, unless it's totally invalid - Ok(()) + Err(Errno::Notdir) } - - fn allocate(&self, offset: types::Filesize, len: types::Filesize) -> Result<()> { - let new_limit = offset.checked_add(len).ok_or(Errno::Fbig)?; - let mut data = self.data.borrow_mut(); - - if new_limit > data.max_size() { - return Err(Errno::Fbig); - } - - if new_limit > data.size() { - data.resize(new_limit)?; - } - - Ok(()) + fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result { + Err(Errno::Notdir) } - - fn filestat_set_size(&self, st_size: types::Filesize) -> Result<()> { - let mut data = self.data.borrow_mut(); - if st_size > data.max_size() { - return Err(Errno::Fbig); - } - data.resize(st_size) + fn readlinkat(&self, _path: &str) -> Result { + Err(Errno::Notdir) } - - fn filestat_get(&self) -> Result { - let stat = types::Filestat { - dev: 0, - ino: 0, - nlink: 0, - size: self.data.borrow().size(), - atim: 0, - ctim: 0, - mtim: 0, - filetype: self.get_file_type(), - }; - Ok(stat) + fn rename(&self, _old_path: &str, _new_handle: Box, _new_path: &str) -> Result<()> { + Err(Errno::Notdir) } - - fn get_file_type(&self) -> types::Filetype { - types::Filetype::RegularFile + fn remove_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) } - - fn get_rights_base(&self) -> types::Rights { - types::Rights::regular_file_base() + fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> { + Err(Errno::Notdir) } - - fn get_rights_inheriting(&self) -> types::Rights { - types::Rights::regular_file_inheriting() + fn unlink_file(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) } } @@ -518,8 +405,8 @@ pub struct VirtualDir { 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`. - parent: Rc>>>, - entries: Rc>>>, + parent: Rc>>>, + entries: Rc>>>, } impl VirtualDir { @@ -563,7 +450,7 @@ impl VirtualDir { } impl MovableFile for VirtualDir { - fn set_parent(&self, new_parent: Option>) { + fn set_parent(&self, new_parent: Option>) { *self.parent.borrow_mut() = new_parent; } } @@ -575,200 +462,47 @@ const PARENT_DIR_COOKIE: u32 = 1; // that would wrap and be mapped to the same dir cookies as `self` or `parent`. const RESERVED_ENTRY_COUNT: u32 = 2; -impl VirtualFile for VirtualDir { - fn try_clone(&self) -> io::Result> { +impl Handle for VirtualDir { + fn as_any(&self) -> &dyn Any { + self + } + fn try_clone(&self) -> io::Result> { Ok(Box::new(Self { writable: self.writable, parent: Rc::clone(&self.parent), entries: Rc::clone(&self.entries), })) } - - fn readlinkat(&self, _path: &Path) -> Result { - // Files are not symbolic links or directories, faithfully report Notdir. - Err(Errno::Notdir) + fn get_file_type(&self) -> io::Result { + Ok(types::Filetype::Directory) } - - fn openat( - &self, - path: &Path, - read: bool, - write: bool, - oflags: types::Oflags, - fd_flags: types::Fdflags, - ) -> Result> { - log::trace!( - "VirtualDir::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}", - path, - read, - write, - oflags, - fd_flags - ); - - if path == Path::new(".") { - return self.try_clone().map_err(Into::into); - } else if path == Path::new("..") { - match &*self.parent.borrow() { - Some(file) => { - return file.try_clone().map_err(Into::into); - } - None => { - return self.try_clone().map_err(Into::into); - } - } - } - - // openat may have been passed a path with a trailing slash, but files are mapped to paths - // with trailing slashes normalized out. - let file_name = path.file_name().ok_or(Errno::Inval)?; - let mut entries = self.entries.borrow_mut(); - let entry_count = entries.len(); - match entries.entry(Path::new(file_name).to_path_buf()) { - Entry::Occupied(e) => { - let creat_excl_mask = types::Oflags::CREAT | types::Oflags::EXCL; - if (oflags & creat_excl_mask) == creat_excl_mask { - log::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name); - log::trace!(" return Exist"); - return Err(Errno::Exist); - } - - if oflags.contains(&types::Oflags::DIRECTORY) - && e.get().get_file_type() != types::Filetype::Directory - { - log::trace!( - "VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.", - file_name - ); - log::trace!(" return Notdir"); - return Err(Errno::Notdir); - } - - e.get().try_clone().map_err(Into::into) - } - Entry::Vacant(v) => { - if self.writable { - // Enforce a hard limit at `u32::MAX - 2` files. - // This is to have a constant limit (rather than target-dependent limit we - // would have with `usize`. The limit is the full `u32` range minus two so we - // can reserve "self" and "parent" cookie values. - if entry_count >= (std::u32::MAX - RESERVED_ENTRY_COUNT) as usize { - return Err(Errno::Nospc); - } - - log::trace!( - "VirtualDir::openat creating an InMemoryFile named {}", - path.display() - ); - - let file = Box::new(InMemoryFile::memory_backed()); - file.fd_flags.set(fd_flags); - file.set_parent(Some(self.try_clone().expect("can clone self"))); - v.insert(file).try_clone().map_err(Into::into) - } else { - Err(Errno::Acces) - } - } - } + fn get_rights(&self) -> io::Result { + Ok(EntryRights::new( + types::Rights::directory_base(), + types::Rights::directory_inheriting(), + )) } - - fn remove_directory(&self, path: &str) -> Result<()> { - let trimmed_path = path.trim_end_matches('/'); - let mut entries = self.entries.borrow_mut(); - match entries.entry(Path::new(trimmed_path).to_path_buf()) { - Entry::Occupied(e) => { - // first, does this name a directory? - if e.get().get_file_type() != types::Filetype::Directory { - return Err(Errno::Notdir); - } - - // Okay, but is the directory empty? - let iter = e.get().readdir(wasi::DIRCOOKIE_START)?; - if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() { - return Err(Errno::Notempty); - } - - // Alright, it's an empty directory. We can remove it. - let removed = e.remove_entry(); - - // And sever the file's parent ref to avoid Rc cycles. - removed.1.set_parent(None); - - Ok(()) - } - Entry::Vacant(_) => { - log::trace!( - "VirtualDir::remove_directory failed to remove {}, no such entry", - trimmed_path - ); - Err(Errno::Noent) - } - } + // FdOps + fn filestat_get(&self) -> Result { + let stat = types::Filestat { + dev: 0, + ino: 0, + nlink: 0, + size: 0, + atim: 0, + ctim: 0, + mtim: 0, + filetype: self.get_file_type()?, + }; + Ok(stat) } - - fn unlink_file(&self, path: &str) -> Result<()> { - let trimmed_path = path.trim_end_matches('/'); - - // Special case: we may be unlinking this directory itself if path is `"."`. In that case, - // fail with Isdir, since this is a directory. Alternatively, we may be unlinking `".."`, - // which is bound the same way, as this is by definition contained in a directory. - if trimmed_path == "." || trimmed_path == ".." { - return Err(Errno::Isdir); - } - - let mut entries = self.entries.borrow_mut(); - match entries.entry(Path::new(trimmed_path).to_path_buf()) { - Entry::Occupied(e) => { - // Directories must be removed through `remove_directory`, not `unlink_file`. - if e.get().get_file_type() == types::Filetype::Directory { - return Err(Errno::Isdir); - } - - let removed = e.remove_entry(); - - // Sever the file's parent ref to avoid Rc cycles. - removed.1.set_parent(None); - - Ok(()) - } - Entry::Vacant(_) => { - log::trace!( - "VirtualDir::unlink_file failed to remove {}, no such entry", - trimmed_path - ); - Err(Errno::Noent) - } - } - } - - fn create_directory(&self, path: &Path) -> Result<()> { - let mut entries = self.entries.borrow_mut(); - match entries.entry(path.to_owned()) { - Entry::Occupied(_) => Err(Errno::Exist), - Entry::Vacant(v) => { - if self.writable { - let new_dir = Box::new(Self::new(true)); - new_dir.set_parent(Some(self.try_clone()?)); - v.insert(new_dir); - Ok(()) - } else { - Err(Errno::Acces) - } - } - } - } - - fn write_vectored(&self, _iovs: &[io::IoSlice]) -> Result { - Err(Errno::Badf) - } - fn readdir( &self, cookie: types::Dircookie, ) -> Result>>> { struct VirtualDirIter { start: u32, - entries: Rc>>>, + entries: Rc>>>, } impl Iterator for VirtualDirIter { type Item = Result<(types::Dirent, String)>; @@ -821,7 +555,7 @@ impl VirtualFile 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, }; @@ -843,30 +577,185 @@ impl VirtualFile for VirtualDir { entries: Rc::clone(&self.entries), })) } - - fn filestat_get(&self) -> Result { - let stat = types::Filestat { - dev: 0, - ino: 0, - nlink: 0, - size: 0, - atim: 0, - ctim: 0, - mtim: 0, - filetype: self.get_file_type(), - }; - Ok(stat) + // PathOps + fn create_directory(&self, path: &str) -> Result<()> { + let mut entries = self.entries.borrow_mut(); + match entries.entry(PathBuf::from(path)) { + Entry::Occupied(_) => Err(Errno::Exist), + Entry::Vacant(v) => { + if self.writable { + let new_dir = Box::new(Self::new(true)); + new_dir.set_parent(Some(self.try_clone()?)); + v.insert(new_dir); + Ok(()) + } else { + Err(Errno::Acces) + } + } + } } + fn openat( + &self, + path: &str, + read: bool, + write: bool, + oflags: types::Oflags, + fd_flags: types::Fdflags, + ) -> Result> { + log::trace!( + "VirtualDir::openat(path={:?}, read={:?}, write={:?}, oflags={:?}, fd_flags={:?}", + path, + read, + write, + oflags, + fd_flags + ); - fn get_file_type(&self) -> types::Filetype { - types::Filetype::Directory + if path == "." { + return self.try_clone().map_err(Into::into); + } else if path == ".." { + match &*self.parent.borrow() { + Some(file) => { + return file.try_clone().map_err(Into::into); + } + None => { + return self.try_clone().map_err(Into::into); + } + } + } + + // openat may have been passed a path with a trailing slash, but files are mapped to paths + // with trailing slashes normalized out. + let file_name = Path::new(path).file_name().ok_or(Errno::Inval)?; + let mut entries = self.entries.borrow_mut(); + let entry_count = entries.len(); + match entries.entry(Path::new(file_name).to_path_buf()) { + Entry::Occupied(e) => { + let creat_excl_mask = types::Oflags::CREAT | types::Oflags::EXCL; + if (oflags & creat_excl_mask) == creat_excl_mask { + log::trace!("VirtualDir::openat was passed oflags CREAT|EXCL, but the file {:?} exists.", file_name); + log::trace!(" return Exist"); + return Err(Errno::Exist); + } + + if oflags.contains(&types::Oflags::DIRECTORY) + && e.get().get_file_type()? != types::Filetype::Directory + { + log::trace!( + "VirtualDir::openat was passed oflags DIRECTORY, but {:?} is a file.", + file_name + ); + log::trace!(" return Notdir"); + return Err(Errno::Notdir); + } + + e.get().try_clone().map_err(Into::into) + } + Entry::Vacant(v) => { + if self.writable { + // Enforce a hard limit at `u32::MAX - 2` files. + // This is to have a constant limit (rather than target-dependent limit we + // would have with `usize`. The limit is the full `u32` range minus two so we + // can reserve "self" and "parent" cookie values. + if entry_count >= (std::u32::MAX - RESERVED_ENTRY_COUNT) as usize { + return Err(Errno::Nospc); + } + + log::trace!("VirtualDir::openat creating an InMemoryFile named {}", path); + + let file = Box::new(InMemoryFile::memory_backed()); + file.fd_flags.set(fd_flags); + file.set_parent(Some(self.try_clone().expect("can clone self"))); + v.insert(file).try_clone().map_err(Into::into) + } else { + Err(Errno::Acces) + } + } + } } - - fn get_rights_base(&self) -> types::Rights { - types::Rights::directory_base() + fn readlinkat(&self, _path: &str) -> Result { + // Files are not symbolic links or directories, faithfully report Notdir. + Err(Errno::Notdir) } + fn remove_directory(&self, path: &str) -> Result<()> { + let trimmed_path = path.trim_end_matches('/'); + let mut entries = self.entries.borrow_mut(); + match entries.entry(Path::new(trimmed_path).to_path_buf()) { + Entry::Occupied(e) => { + // first, does this name a directory? + if e.get().get_file_type()? != types::Filetype::Directory { + return Err(Errno::Notdir); + } - fn get_rights_inheriting(&self) -> types::Rights { - types::Rights::directory_inheriting() + // Okay, but is the directory empty? + let iter = e.get().readdir(wasi::DIRCOOKIE_START)?; + if iter.skip(RESERVED_ENTRY_COUNT as usize).next().is_some() { + return Err(Errno::Notempty); + } + + // Alright, it's an empty directory. We can remove it. + let removed = e.remove_entry(); + + // TODO refactor + // And sever the file's parent ref to avoid Rc cycles. + if let Some(dir) = removed.1.as_any().downcast_ref::() { + dir.set_parent(None); + } else if let Some(file) = removed.1.as_any().downcast_ref::() { + file.set_parent(None); + } else { + panic!("neither VirtualDir nor InMemoryFile"); + } + + Ok(()) + } + Entry::Vacant(_) => { + log::trace!( + "VirtualDir::remove_directory failed to remove {}, no such entry", + trimmed_path + ); + Err(Errno::Noent) + } + } + } + fn unlink_file(&self, path: &str) -> Result<()> { + let trimmed_path = path.trim_end_matches('/'); + + // Special case: we may be unlinking this directory itself if path is `"."`. In that case, + // fail with Isdir, since this is a directory. Alternatively, we may be unlinking `".."`, + // which is bound the same way, as this is by definition contained in a directory. + if trimmed_path == "." || trimmed_path == ".." { + return Err(Errno::Isdir); + } + + let mut entries = self.entries.borrow_mut(); + match entries.entry(Path::new(trimmed_path).to_path_buf()) { + Entry::Occupied(e) => { + // Directories must be removed through `remove_directory`, not `unlink_file`. + if e.get().get_file_type()? == types::Filetype::Directory { + return Err(Errno::Isdir); + } + + let removed = e.remove_entry(); + + // TODO refactor + // Sever the file's parent ref to avoid Rc cycles. + if let Some(dir) = removed.1.as_any().downcast_ref::() { + dir.set_parent(None); + } else if let Some(file) = removed.1.as_any().downcast_ref::() { + file.set_parent(None); + } else { + panic!("neither VirtualDir nor InMemoryFile"); + } + + Ok(()) + } + Entry::Vacant(_) => { + log::trace!( + "VirtualDir::unlink_file failed to remove {}, no such entry", + trimmed_path + ); + Err(Errno::Noent) + } + } } }