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:
Jakub Konka
2020-03-27 09:34:52 +01:00
committed by GitHub
parent 092538cc54
commit 5c51940100
20 changed files with 446 additions and 550 deletions

View File

@@ -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))
}
}