Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce strongly-typed system primitives #1561

Merged
merged 16 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 70 additions & 47 deletions crates/wasi-common/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use crate::entry::{Entry, EntryHandle};
use crate::fdpool::FdPool;
use crate::handle::Handle;
use crate::sys::oshandle::{OsHandle, OsHandleExt};
use crate::sys::osdir::OsDir;
use crate::sys::osother::{OsOther, OsOtherExt};
use crate::sys::stdio::{Stderr, StderrExt, Stdin, StdinExt, Stdout, StdoutExt};
use crate::virtfs::{VirtualDir, VirtualDirEntry};
use crate::wasi::types;
use crate::wasi::{Errno, Result};
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ffi::{self, CString, OsString};
use std::fs::File;
use std::path::{Path, PathBuf};
Expand All @@ -32,9 +35,9 @@ pub enum WasiCtxBuilderError {
/// Provided sequence of bytes contained an unexpected NUL byte.
#[error("provided sequence contained an unexpected NUL byte")]
UnexpectedNul(#[from] ffi::NulError),
/// Provided `File` is not a directory.
#[error("preopened directory path {} is not a directory", .0.display())]
NotADirectory(PathBuf),
/// The root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory.
#[error("the root of a VirtualDirEntry tree at {} must be a VirtualDirEntry::Directory", .0.display())]
VirtualDirEntryRootNotADirectory(PathBuf),
/// `WasiCtx` has too many opened files.
#[error("context object has too many opened files")]
TooManyFilesOpen,
Expand All @@ -43,8 +46,8 @@ pub enum WasiCtxBuilderError {
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;

enum PendingEntry {
Thunk(fn() -> io::Result<OsHandle>),
File(File),
Thunk(fn() -> io::Result<Box<dyn Handle>>),
OsHandle(File),
}

impl std::fmt::Debug for PendingEntry {
Expand All @@ -53,9 +56,9 @@ impl std::fmt::Debug for PendingEntry {
Self::Thunk(f) => write!(
fmt,
"PendingEntry::Thunk({:p})",
f as *const fn() -> io::Result<OsHandle>
f as *const fn() -> io::Result<Box<dyn Handle>>
),
Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f),
Self::OsHandle(f) => write!(fmt, "PendingEntry::OsHandle({:?})", f),
}
}
}
Expand Down Expand Up @@ -105,22 +108,37 @@ impl PendingCString {
}
}

struct PendingPreopen(Box<dyn FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>>>);

impl PendingPreopen {
fn new<F>(f: F) -> Self
where
F: FnOnce() -> WasiCtxBuilderResult<Box<dyn Handle>> + 'static,
{
Self(Box::new(f))
}

fn into(self) -> WasiCtxBuilderResult<Box<dyn Handle>> {
self.0()
}
}

/// A builder allowing customizable construction of `WasiCtx` instances.
pub struct WasiCtxBuilder {
stdin: Option<PendingEntry>,
stdout: Option<PendingEntry>,
stderr: Option<PendingEntry>,
preopens: Option<Vec<(PathBuf, Box<dyn Handle>)>>,
preopens: Option<Vec<(PathBuf, PendingPreopen)>>,
args: Option<Vec<PendingCString>>,
env: Option<HashMap<PendingCString, PendingCString>>,
}

impl WasiCtxBuilder {
/// Builder for a new `WasiCtx`.
pub fn new() -> Self {
let stdin = Some(PendingEntry::Thunk(OsHandle::from_null));
let stdout = Some(PendingEntry::Thunk(OsHandle::from_null));
let stderr = Some(PendingEntry::Thunk(OsHandle::from_null));
let stdin = Some(PendingEntry::Thunk(OsOther::from_null));
let stdout = Some(PendingEntry::Thunk(OsOther::from_null));
let stderr = Some(PendingEntry::Thunk(OsOther::from_null));

Self {
stdin,
Expand Down Expand Up @@ -167,27 +185,27 @@ impl WasiCtxBuilder {

/// Inherit stdin from the host process.
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
self.stdin = Some(PendingEntry::Thunk(Stdin::stdin));
self
}

/// Inherit stdout from the host process.
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
self.stdout = Some(PendingEntry::Thunk(Stdout::stdout));
self
}

/// Inherit stdout from the host process.
/// Inherit stderr from the host process.
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
self.stderr = Some(PendingEntry::Thunk(Stderr::stderr));
self
}

/// Inherit the stdin, stdout, and stderr streams from the host process.
pub fn inherit_stdio(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdin())));
self.stdout = Some(PendingEntry::Thunk(|| Ok(OsHandle::stdout())));
self.stderr = Some(PendingEntry::Thunk(|| Ok(OsHandle::stderr())));
self.stdin = Some(PendingEntry::Thunk(Stdin::stdin));
self.stdout = Some(PendingEntry::Thunk(Stdout::stdout));
self.stderr = Some(PendingEntry::Thunk(Stderr::stderr));
self
}

Expand Down Expand Up @@ -231,28 +249,32 @@ impl WasiCtxBuilder {

/// Provide a File to use as stdin
pub fn stdin(&mut self, file: File) -> &mut Self {
self.stdin = Some(PendingEntry::File(file));
self.stdin = Some(PendingEntry::OsHandle(file));
self
}

/// Provide a File to use as stdout
pub fn stdout(&mut self, file: File) -> &mut Self {
self.stdout = Some(PendingEntry::File(file));
self.stdout = Some(PendingEntry::OsHandle(file));
self
}

/// Provide a File to use as stderr
pub fn stderr(&mut self, file: File) -> &mut Self {
self.stderr = Some(PendingEntry::File(file));
self.stderr = Some(PendingEntry::OsHandle(file));
self
}

/// Add a preopened directory.
pub fn preopened_dir<P: AsRef<Path>>(&mut self, dir: File, guest_path: P) -> &mut Self {
self.preopens.as_mut().unwrap().push((
guest_path.as_ref().to_owned(),
Box::new(OsHandle::from(dir)),
));
let preopen = PendingPreopen::new(move || {
let dir = OsDir::try_from(dir).map_err(WasiCtxBuilderError::from)?;
Ok(Box::new(dir))
});
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), preopen));
self
}

Expand All @@ -277,18 +299,22 @@ impl WasiCtxBuilder {
}
}

let dir = if let VirtualDirEntry::Directory(entries) = dir {
let mut dir = VirtualDir::new(true);
populate_directory(entries, &mut dir);
Box::new(dir)
} else {
panic!("the root of a VirtualDirEntry tree must be a VirtualDirEntry::Directory");
};

let guest_path_owned = guest_path.as_ref().to_owned();
let preopen = PendingPreopen::new(move || {
if let VirtualDirEntry::Directory(entries) = dir {
let mut dir = VirtualDir::new(true);
populate_directory(entries, &mut dir);
Ok(Box::new(dir))
} else {
Err(WasiCtxBuilderError::VirtualDirEntryRootNotADirectory(
guest_path_owned,
))
}
});
self.preopens
.as_mut()
.unwrap()
.push((guest_path.as_ref().to_owned(), dir));
.push((guest_path.as_ref().to_owned(), preopen));
self
}

Expand Down Expand Up @@ -336,15 +362,16 @@ impl WasiCtxBuilder {
log::debug!("WasiCtx inserting entry {:?}", pending);
let fd = match pending {
PendingEntry::Thunk(f) => {
let handle = EntryHandle::new(f()?);
let entry = Entry::from(handle)?;
let handle = EntryHandle::from(f()?);
let entry = Entry::new(handle);
entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
}
PendingEntry::File(f) => {
let handle = EntryHandle::new(OsHandle::from(f));
let entry = Entry::from(handle)?;
PendingEntry::OsHandle(f) => {
let handle = OsOther::try_from(f)?;
kubkon marked this conversation as resolved.
Show resolved Hide resolved
let handle = EntryHandle::new(handle);
let entry = Entry::new(handle);
entries
.insert(entry)
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?
Expand All @@ -353,13 +380,9 @@ impl WasiCtxBuilder {
log::debug!("WasiCtx inserted at {:?}", fd);
}
// Then add the preopen entries.
for (guest_path, dir) in self.preopens.take().unwrap() {
if !dir.is_directory() {
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
}

let handle = EntryHandle::from(dir);
let mut entry = Entry::from(handle)?;
for (guest_path, preopen) in self.preopens.take().unwrap() {
let handle = EntryHandle::from(preopen.into()?);
let mut entry = Entry::new(handle);
entry.preopen_path = Some(guest_path);
let fd = entries
.insert(entry)
Expand Down
105 changes: 26 additions & 79 deletions crates/wasi-common/src/entry.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::handle::Handle;
use crate::wasi::types::{Filetype, Rights};
use crate::handle::{Handle, HandleRights};
use crate::wasi::types::Filetype;
use crate::wasi::{Errno, Result};
use std::cell::Cell;
use std::ops::Deref;
use std::path::PathBuf;
use std::rc::Rc;
use std::{fmt, io};

pub(crate) struct EntryHandle(Rc<dyn Handle>);

Expand Down Expand Up @@ -33,118 +31,67 @@ impl Deref for EntryHandle {
}
}

/// An abstraction struct serving as a wrapper for a host `Descriptor` object which requires
/// certain rights `rights` in order to be accessed correctly.
/// An abstraction struct serving as a wrapper for a `Handle` object.
///
/// Here, the `descriptor` field stores the host `Descriptor` object (such as a file descriptor, or
/// stdin handle), and accessing it can only be done via the provided `Entry::as_descriptor` method
/// Here, the `handle` field stores an instance of `Handle` type (such as a file descriptor, or
/// stdin handle), and accessing it can only be done via the provided `Entry::as_handle` method
/// which require a set of base and inheriting rights to be specified, verifying whether the stored
/// `Descriptor` object is valid for the rights specified.
/// `Handle` object is valid for the rights specified.
pub(crate) struct Entry {
pub(crate) file_type: Filetype,
handle: EntryHandle,
pub(crate) rights: Cell<EntryRights>,
pub(crate) preopen_path: Option<PathBuf>,
// TODO: directories
}

/// Represents rights of an `Entry` entity, either already held or
/// required.
#[derive(Debug, Copy, Clone)]
pub(crate) struct EntryRights {
pub(crate) base: Rights,
pub(crate) inheriting: Rights,
}

impl EntryRights {
pub(crate) fn new(base: Rights, inheriting: Rights) -> Self {
Self { base, inheriting }
}

/// Create new `EntryRights` instance from `base` rights only, keeping
/// `inheriting` set to none.
pub(crate) fn from_base(base: Rights) -> Self {
Self {
base,
inheriting: Rights::empty(),
}
}

/// Create new `EntryRights` instance with both `base` and `inheriting`
/// rights set to none.
pub(crate) fn empty() -> Self {
impl Entry {
pub(crate) fn new(handle: EntryHandle) -> Self {
let preopen_path = None;
Self {
base: Rights::empty(),
inheriting: Rights::empty(),
handle,
preopen_path,
}
}

/// 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)
pub(crate) fn get_file_type(&self) -> Filetype {
self.handle.get_file_type()
}
}

impl fmt::Display for EntryRights {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"EntryRights {{ base: {}, inheriting: {} }}",
self.base, self.inheriting
)
pub(crate) fn get_rights(&self) -> HandleRights {
self.handle.get_rights()
}
}

impl Entry {
pub(crate) fn from(handle: EntryHandle) -> io::Result<Self> {
let file_type = handle.get_file_type()?;
let rights = handle.get_rights()?;
Ok(Self {
file_type,
handle,
rights: Cell::new(rights),
preopen_path: None,
})
pub(crate) fn set_rights(&self, rights: HandleRights) {
self.handle.set_rights(rights)
}

/// Convert this `Entry` into a host `Descriptor` object provided the specified
/// Convert this `Entry` into a `Handle` object provided the specified
/// `rights` rights are set on this `Entry` object.
///
/// The `Entry` can only be converted into a valid `Descriptor` object if
/// The `Entry` can only be converted into a valid `Handle` object if
/// the specified set of base rights, and inheriting rights encapsulated within `rights`
/// `EntryRights` structure is a subset of rights attached to this `Entry`. The check is
/// `HandleRights` structure is a subset of rights attached to this `Entry`. The check is
/// performed using `Entry::validate_rights` method. If the check fails, `Errno::Notcapable`
/// is returned.
pub(crate) fn as_handle(&self, rights: &EntryRights) -> Result<EntryHandle> {
pub(crate) fn as_handle(&self, rights: &HandleRights) -> Result<EntryHandle> {
self.validate_rights(rights)?;
Ok(self.handle.get())
}

/// Check if this `Entry` object satisfies the specified `EntryRights`; i.e., if
/// Check if this `Entry` object satisfies the specified `HandleRights`; i.e., if
/// rights attached to this `Entry` object are a superset.
///
/// Upon unsuccessful check, `Errno::Notcapable` is returned.
pub(crate) fn validate_rights(&self, rights: &EntryRights) -> Result<()> {
if self.rights.get().contains(rights) {
pub(crate) fn validate_rights(&self, rights: &HandleRights) -> Result<()> {
let this_rights = self.handle.get_rights();
if this_rights.contains(rights) {
Ok(())
} else {
log::trace!(
" | validate_rights failed: required rights = {}; actual rights = {}",
rights,
self.rights.get(),
this_rights,
);
Err(Errno::Notcapable)
}
}

/// Test whether this descriptor is considered a tty within WASI.
/// Note that since WASI itself lacks an `isatty` syscall and relies
/// on a conservative approximation, we use the same approximation here.
pub(crate) fn isatty(&self) -> bool {
self.file_type == Filetype::CharacterDevice
&& self
.rights
.get()
.contains(&EntryRights::from_base(Rights::FD_SEEK | Rights::FD_TELL))
}
}
Loading