Refactor use of Refs and RefMuts in wasi-common (#1412)
* Refactor use of Refs and RefMuts in wasi-common This commit refactors the use of `Ref`s and `RefMut`s in `wasi-common`. Now, `Entry` is stored behind an `Rc` inside the `EntryTable`. The `Entry` itself on the other hand now stores rights behind a `RefCell` and the descriptor as `Rc<RefCell<..>>` combo to enable easy reference tracking and interior mutability which is required down the line in a couple of syscalls. In essence, this implies that we no longer have need for mutable accessor to `Entry` from `WasiCtx`, and so all related methods go away (`get_entry_mut`, etc.). While here, I've also simplified handling and aggregating of rights on the `Entry` object. Instead of storing base and inheriting rights as separate fields, they are now aggregated into one struct `EntryRights` which features convenient constructors for each possible combination; i.e., when only base rights are set, or both base and inheriting are set, or both are left as empty. Since we do need to be able to mutate those rights down the line in `fdstat_set_rights` syscall, this object is kept behind a `RefCell` (note no `Rc` since we don't need to pass it around anywhere). The descriptor field in `Entry` is now kept behind `Rc<RefCell<..>>` combo since we not only need to mutate it down the line, but we also need to be able to pass it around (as part of the machinery making `poll_oneoff` work). I've also removed `as_file` and `try_clone` methods on `Descriptor` struct since they were adding more noise than necessary, and making them work with `Rc` was unnecessarily complicated. Finally, I've converted the `get_dir_from_os_handle` function into a method attached to the `OsHandle` itself, called `dir_stream`. IMHO, it makes more sense to have it there directly as a method than as a separate function. * Use Cell for types that are Copy
This commit is contained in:
@@ -3,10 +3,12 @@ use crate::sys::entry::{descriptor_as_oshandle, determine_type_and_access_rights
|
||||
use crate::virtfs::VirtualFile;
|
||||
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::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, fs, io};
|
||||
|
||||
pub(crate) enum Descriptor {
|
||||
@@ -42,193 +44,12 @@ impl fmt::Debug for Descriptor {
|
||||
}
|
||||
|
||||
impl Descriptor {
|
||||
pub(crate) fn try_clone(&self) -> io::Result<Self> {
|
||||
match self {
|
||||
Self::OsHandle(file) => file.try_clone().map(|f| OsHandle::from(f).into()),
|
||||
Self::VirtualFile(virt) => virt.try_clone().map(Self::VirtualFile),
|
||||
Self::Stdin => Ok(Self::Stdin),
|
||||
Self::Stdout => Ok(Self::Stdout),
|
||||
Self::Stderr => Ok(Self::Stderr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a reference to the `OsHandle` or `VirtualFile` treating it as an
|
||||
/// actual file/dir, and allowing operations which require an actual file and
|
||||
/// not just a stream or socket file descriptor.
|
||||
pub(crate) fn as_file<'descriptor>(&'descriptor self) -> Result<&'descriptor Self> {
|
||||
match self {
|
||||
Self::OsHandle(_) => Ok(self),
|
||||
Self::VirtualFile(_) => Ok(self),
|
||||
_ => Err(Errno::Badf),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
|
||||
/// certain base rights `rights_base` and inheriting rights `rights_inheriting` 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` and
|
||||
/// `Entry::as_descriptor_mut` methods which require a set of base and inheriting rights to be
|
||||
/// specified, verifying whether the stored `Descriptor` object is valid for the rights specified.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Entry {
|
||||
pub(crate) file_type: Filetype,
|
||||
descriptor: Descriptor,
|
||||
pub(crate) rights_base: Rights,
|
||||
pub(crate) rights_inheriting: Rights,
|
||||
pub(crate) preopen_path: Option<PathBuf>,
|
||||
// TODO: directories
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub(crate) fn from(file: Descriptor) -> io::Result<Self> {
|
||||
match file {
|
||||
Descriptor::OsHandle(handle) => unsafe { determine_type_and_access_rights(&handle) }
|
||||
.map(|(file_type, rights_base, rights_inheriting)| Self {
|
||||
file_type,
|
||||
descriptor: handle.into(),
|
||||
rights_base,
|
||||
rights_inheriting,
|
||||
preopen_path: None,
|
||||
}),
|
||||
Descriptor::VirtualFile(virt) => {
|
||||
let file_type = virt.get_file_type();
|
||||
let rights_base = virt.get_rights_base();
|
||||
let rights_inheriting = virt.get_rights_inheriting();
|
||||
|
||||
Ok(Self {
|
||||
file_type,
|
||||
descriptor: virt.into(),
|
||||
rights_base,
|
||||
rights_inheriting,
|
||||
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<Self> {
|
||||
unsafe { determine_type_and_access_rights(&io::stdin()) }.map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
file_type,
|
||||
descriptor: Descriptor::Stdin,
|
||||
rights_base,
|
||||
rights_inheriting,
|
||||
preopen_path: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn duplicate_stdout() -> io::Result<Self> {
|
||||
unsafe { determine_type_and_access_rights(&io::stdout()) }.map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
file_type,
|
||||
descriptor: Descriptor::Stdout,
|
||||
rights_base,
|
||||
rights_inheriting,
|
||||
preopen_path: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn duplicate_stderr() -> io::Result<Self> {
|
||||
unsafe { determine_type_and_access_rights(&io::stderr()) }.map(
|
||||
|(file_type, rights_base, rights_inheriting)| Self {
|
||||
file_type,
|
||||
descriptor: Descriptor::Stderr,
|
||||
rights_base,
|
||||
rights_inheriting,
|
||||
preopen_path: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn null() -> io::Result<Self> {
|
||||
Self::from(OsHandle::from(dev_null()?).into())
|
||||
}
|
||||
|
||||
/// Convert this `Entry` into a host `Descriptor` object provided the specified
|
||||
/// `rights_base` and `rights_inheriting` rights are set on this `Entry` object.
|
||||
///
|
||||
/// The `Entry` can only be converted into a valid `Descriptor` object if
|
||||
/// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting`
|
||||
/// is a subset of rights attached to this `Entry`. The check is performed using
|
||||
/// `Entry::validate_rights` method. If the check fails, `Errno::Notcapable` is returned.
|
||||
pub(crate) fn as_descriptor(
|
||||
&self,
|
||||
rights_base: Rights,
|
||||
rights_inheriting: Rights,
|
||||
) -> Result<&Descriptor> {
|
||||
self.validate_rights(rights_base, rights_inheriting)?;
|
||||
Ok(&self.descriptor)
|
||||
}
|
||||
|
||||
/// Convert this `Entry` into a mutable host `Descriptor` object provided the specified
|
||||
/// `rights_base` and `rights_inheriting` rights are set on this `Entry` object.
|
||||
///
|
||||
/// The `Entry` can only be converted into a valid `Descriptor` object if
|
||||
/// the specified set of base rights `rights_base`, and inheriting rights `rights_inheriting`
|
||||
/// is a subset of rights attached to this `Entry`. The check is performed using
|
||||
/// `Entry::validate_rights` method. If the check fails, `Errno::Notcapable` is returned.
|
||||
pub(crate) fn as_descriptor_mut(
|
||||
&mut self,
|
||||
rights_base: Rights,
|
||||
rights_inheriting: Rights,
|
||||
) -> Result<&mut Descriptor> {
|
||||
self.validate_rights(rights_base, rights_inheriting)?;
|
||||
Ok(&mut self.descriptor)
|
||||
}
|
||||
|
||||
/// Check if this `Entry` object satisfies the specified base rights `rights_base`, and
|
||||
/// inheriting rights `rights_inheriting`; i.e., if rights attached to this `Entry` object
|
||||
/// are a superset.
|
||||
///
|
||||
/// Upon unsuccessful check, `Errno::Notcapable` is returned.
|
||||
pub(crate) fn validate_rights(
|
||||
&self,
|
||||
rights_base: Rights,
|
||||
rights_inheriting: Rights,
|
||||
) -> Result<()> {
|
||||
let missing_base = !self.rights_base & rights_base;
|
||||
let missing_inheriting = !self.rights_inheriting & rights_inheriting;
|
||||
if missing_base != Rights::empty() || missing_inheriting != Rights::empty() {
|
||||
log::trace!(
|
||||
" | validate_rights failed: required: \
|
||||
rights_base = {}, rights_inheriting = {}; \
|
||||
actual: rights_base = {}, rights_inheriting = {}; \
|
||||
missing_base = {}, missing_inheriting = {}",
|
||||
rights_base,
|
||||
rights_inheriting,
|
||||
self.rights_base,
|
||||
self.rights_inheriting,
|
||||
missing_base,
|
||||
missing_inheriting
|
||||
);
|
||||
Err(Errno::Notcapable)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Test whether this descriptor is considered a tty within WASI.
|
||||
/// Note that since WASI itself lacks an `isatty` syscall and relies
|
||||
/// on a conservative approximation, we use the same approximation here.
|
||||
pub(crate) fn isatty(&self) -> bool {
|
||||
self.file_type == Filetype::CharacterDevice
|
||||
&& (self.rights_base & (Rights::FD_SEEK | Rights::FD_TELL)) == Rights::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -254,3 +75,166 @@ impl<'descriptor> Deref for OsHandleRef<'descriptor> {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents rights of an `Entry` entity, either already held or
|
||||
/// required.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct EntryRights {
|
||||
pub(crate) base: Rights,
|
||||
pub(crate) inheriting: Rights,
|
||||
}
|
||||
|
||||
impl EntryRights {
|
||||
pub(crate) fn new(base: Rights, inheriting: Rights) -> Self {
|
||||
Self { base, inheriting }
|
||||
}
|
||||
|
||||
/// Create new `EntryRights` instance from `base` rights only, keeping
|
||||
/// `inheriting` set to none.
|
||||
pub(crate) fn from_base(base: Rights) -> Self {
|
||||
Self {
|
||||
base,
|
||||
inheriting: Rights::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new `EntryRights` instance with both `base` and `inheriting`
|
||||
/// rights set to none.
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
base: Rights::empty(),
|
||||
inheriting: Rights::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `other` is a subset of those rights.
|
||||
pub(crate) fn contains(&self, other: &Self) -> bool {
|
||||
self.base.contains(&other.base) && self.inheriting.contains(&other.inheriting)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EntryRights {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"EntryRights {{\n\tbase: {},\n\tinheriting: {}\n}}",
|
||||
self.base, self.inheriting
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<RefCell<Descriptor>>,
|
||||
pub(crate) rights: Cell<EntryRights>,
|
||||
pub(crate) preopen_path: Option<PathBuf>,
|
||||
// TODO: directories
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub(crate) fn from(file: Descriptor) -> io::Result<Self> {
|
||||
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<Self> {
|
||||
unsafe { determine_type_and_access_rights(&io::stdin()) }.map(|(file_type, rights)| Self {
|
||||
file_type,
|
||||
descriptor: Rc::new(RefCell::new(Descriptor::Stdin)),
|
||||
rights: Cell::new(rights),
|
||||
preopen_path: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn duplicate_stdout() -> io::Result<Self> {
|
||||
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<Self> {
|
||||
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> {
|
||||
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.
|
||||
///
|
||||
/// The `Entry` can only be converted into a valid `Descriptor` object if
|
||||
/// the specified set of base rights, and inheriting rights encapsulated within `rights`
|
||||
/// `EntryRights` structure is a subset of rights attached to this `Entry`. The check is
|
||||
/// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable`
|
||||
/// is returned.
|
||||
pub(crate) fn as_descriptor(&self, rights: &EntryRights) -> Result<Rc<RefCell<Descriptor>>> {
|
||||
self.validate_rights(rights)?;
|
||||
Ok(Rc::clone(&self.descriptor))
|
||||
}
|
||||
|
||||
/// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if
|
||||
/// rights attached to this `Entry` object are a superset.
|
||||
///
|
||||
/// Upon unsuccessful check, `Errno::Notcapable` is returned.
|
||||
pub(crate) fn validate_rights(&self, rights: &EntryRights) -> Result<()> {
|
||||
if self.rights.get().contains(rights) {
|
||||
Ok(())
|
||||
} else {
|
||||
log::trace!(
|
||||
" | validate_rights failed: required rights = {}; actual rights = {}",
|
||||
rights,
|
||||
self.rights.get(),
|
||||
);
|
||||
Err(Errno::Notcapable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Test whether this descriptor is considered a tty within WASI.
|
||||
/// Note that since WASI itself lacks an `isatty` syscall and relies
|
||||
/// on a conservative approximation, we use the same approximation here.
|
||||
pub(crate) fn isatty(&self) -> bool {
|
||||
self.file_type == Filetype::CharacterDevice
|
||||
&& self
|
||||
.rights
|
||||
.get()
|
||||
.contains(&EntryRights::from_base(Rights::FD_SEEK | Rights::FD_TELL))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user