Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/wasi-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ filetime = "0.2.7"
lazy_static = "1.4.0"
num = { version = "0.2.0", default-features = false }
wig = { path = "wig", version = "0.12.0" }
wiggle = { path = "../wiggle" }
wiggle-runtime = { path = "../wiggle/crates/runtime" }

[target.'cfg(unix)'.dependencies]
yanix = { path = "yanix", version = "0.12.0" }
Expand Down
18 changes: 18 additions & 0 deletions crates/wasi-common/src/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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) fn to_relative_ns_delay(clock: SubscriptionClock) -> Result<u128> {
if clock.flags != Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME {
return Ok(u128::from(clock.timeout));
}
let now: u128 = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| Errno::Notcapable)?
.as_nanos();
let deadline = u128::from(clock.timeout);
Ok(deadline.saturating_sub(now))
}
146 changes: 94 additions & 52 deletions crates/wasi-common/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::entry::{Descriptor, Entry};
use crate::fdpool::FdPool;
use crate::sys::entry_impl::OsHandle;
use crate::sys::entry::OsHandle;
use crate::virtfs::{VirtualDir, VirtualDirEntry};
use crate::wasi::{self, WasiError, WasiResult};
use crate::wasi::types;
use crate::wasi::{Errno, Result};
use std::borrow::Borrow;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::ffi::{self, CString, OsString};
use std::fs::File;
Expand Down Expand Up @@ -48,10 +50,10 @@ impl std::fmt::Debug for PendingEntry {
match self {
Self::Thunk(f) => write!(
fmt,
"PendingFdEntry::Thunk({:p})",
"PendingEntry::Thunk({:p})",
f as *const fn() -> io::Result<Entry>
),
Self::File(f) => write!(fmt, "PendingFdEntry::File({:?})", f),
Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f),
}
}
}
Expand Down Expand Up @@ -322,43 +324,34 @@ impl WasiCtxBuilder {
})
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;

let mut fd_pool = FdPool::new();
let mut entries: HashMap<wasi::__wasi_fd_t, Entry> = HashMap::new();
let mut entries = EntryTable::new();
// Populate the non-preopen entries.
for pending in vec![
self.stdin.take().unwrap(),
self.stdout.take().unwrap(),
self.stderr.take().unwrap(),
] {
let fd = fd_pool
.allocate()
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
log::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending {
PendingEntry::Thunk(f) => {
entries.insert(fd, f()?);
}
PendingEntry::File(f) => {
entries.insert(fd, Entry::from(Descriptor::OsHandle(OsHandle::from(f)))?);
}
}
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)?,
};
log::debug!("WasiCtx inserted at {:?}", fd);
}
// Then add the preopen entries.
for (guest_path, dir) in self.preopens.take().unwrap() {
// We do the increment at the beginning of the loop body, so that we don't overflow
// unnecessarily if we have exactly the maximum number of file descriptors.
let preopen_fd = fd_pool
.allocate()
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;

match &dir {
Descriptor::OsHandle(handle) => {
if !handle.metadata()?.is_dir() {
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
}
}
Descriptor::VirtualFile(virt) => {
if virt.get_file_type() != wasi::__WASI_FILETYPE_DIRECTORY {
if virt.get_file_type() != types::Filetype::Directory {
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
}
}
Expand All @@ -367,26 +360,70 @@ impl WasiCtxBuilder {
}
}

let mut fe = Entry::from(dir)?;
fe.preopen_path = Some(guest_path);
log::debug!("WasiCtx inserting ({:?}, {:?})", preopen_fd, fe);
entries.insert(preopen_fd, fe);
let mut entry = Entry::from(dir)?;
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 {
args,
env,
entries,
fd_pool,
entries: RefCell::new(entries),
})
}
}

#[derive(Debug)]
pub struct WasiCtx {
struct EntryTable {
fd_pool: FdPool,
entries: HashMap<wasi::__wasi_fd_t, Entry>,
entries: HashMap<types::Fd, Entry>,
}

impl EntryTable {
fn new() -> Self {
Self {
fd_pool: FdPool::new(),
entries: HashMap::new(),
}
}

fn contains(&self, fd: &types::Fd) -> bool {
self.entries.contains_key(fd)
}

fn insert(&mut self, entry: Entry) -> Option<types::Fd> {
let fd = self.fd_pool.allocate()?;
self.entries.insert(fd, entry);
Some(fd)
}

fn insert_at(&mut self, fd: &types::Fd, entry: Entry) {
self.entries.insert(*fd, entry);
}

fn get(&self, fd: &types::Fd) -> Option<&Entry> {
self.entries.get(fd)
}

fn get_mut(&mut self, fd: &types::Fd) -> Option<&mut Entry> {
self.entries.get_mut(fd)
}

fn remove(&mut self, fd: types::Fd) -> Option<Entry> {
let entry = self.entries.remove(&fd)?;
self.fd_pool.deallocate(fd);
Some(entry)
}
}

#[derive(Debug)]
pub struct WasiCtx {
entries: RefCell<EntryTable>,
pub(crate) args: Vec<CString>,
pub(crate) env: Vec<CString>,
}
Expand All @@ -408,42 +445,47 @@ impl WasiCtx {
}

/// Check if `WasiCtx` contains the specified raw WASI `fd`.
pub(crate) unsafe fn contains_entry(&self, fd: wasi::__wasi_fd_t) -> bool {
self.entries.contains_key(&fd)
pub(crate) fn contains_entry(&self, fd: types::Fd) -> bool {
self.entries.borrow().contains(&fd)
}

/// Get an immutable `Entry` corresponding to the specified raw WASI `fd`.
pub(crate) unsafe fn get_entry(&self, fd: wasi::__wasi_fd_t) -> WasiResult<&Entry> {
self.entries.get(&fd).ok_or(WasiError::EBADF)
pub(crate) fn get_entry(&self, fd: types::Fd) -> Result<Ref<Entry>> {
if !self.contains_entry(fd) {
return Err(Errno::Badf);
}
Ok(Ref::map(self.entries.borrow(), |entries| {
entries.get(&fd).unwrap()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't strictly incorrect (at least while we only have one thread), but it feels awkward. Would it work to do something like this? A little verbose still, but it only does one borrow and one entries lookup.

        let maybe = Ref::map(self.entries.borrow(), |entries| {
            entries.get(&fd)
        });
        if maybe.is_none() {
            return Err(Errno::BadF);
        }
        Ref::map(maybe, |maybe_entry| maybe_entry.unwrap())
    }

?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, this is exactly what I wanted to do and couldn't figure out how. Thanks! :-D

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this proves to be more difficult than I originally envisioned. If I follow your suggestion, than we end up with a lifetime mismatch since the first maybe will contain weirdness of the like &Option<&Entry>, and the compiler cannot assign approriate lifetimes to the final Ref<'_, Entry>. Perhaps when we revisit the use of RefCell in wasi-common altogether, this will no longer be an issue?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if it gets awkward, then I agree it makes sense to leave the code as-is for now.

}))
}

/// Get a mutable `Entry` corresponding to the specified raw WASI `fd`.
pub(crate) unsafe fn get_entry_mut(&mut self, fd: wasi::__wasi_fd_t) -> WasiResult<&mut Entry> {
self.entries.get_mut(&fd).ok_or(WasiError::EBADF)
// TODO This runs the risk of a potential difficult-to-predict panic down-the-line.
pub(crate) fn get_entry_mut(&self, fd: types::Fd) -> Result<RefMut<Entry>> {
if !self.contains_entry(fd) {
return Err(Errno::Badf);
}
Ok(RefMut::map(self.entries.borrow_mut(), |entries| {
entries.get_mut(&fd).unwrap()
}))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And similar here? If so, we may not even need the contains_key method, which would be nice because it can be tricky to use without look-before-you-leap hazards.

}

/// Insert the specified `Entry` into the `WasiCtx` object.
///
/// The `FdEntry` will automatically get another free raw WASI `fd` assigned. Note that
/// The `Entry` will automatically get another free raw WASI `fd` assigned. Note that
/// the two subsequent free raw WASI `fd`s do not have to be stored contiguously.
pub(crate) fn insert_entry(&mut self, fe: Entry) -> WasiResult<wasi::__wasi_fd_t> {
let fd = self.fd_pool.allocate().ok_or(WasiError::EMFILE)?;
self.entries.insert(fd, fe);
Ok(fd)
pub(crate) fn insert_entry(&self, entry: Entry) -> Result<types::Fd> {
self.entries.borrow_mut().insert(entry).ok_or(Errno::Mfile)
}

/// Insert the specified `Entry` with the specified raw WASI `fd` key into the `WasiCtx`
/// object.
pub(crate) fn insert_entry_at(&mut self, fd: wasi::__wasi_fd_t, fe: Entry) -> Option<Entry> {
self.entries.insert(fd, fe)
pub(crate) fn insert_entry_at(&self, fd: types::Fd, entry: Entry) {
self.entries.borrow_mut().insert_at(&fd, entry)
}

/// Remove `Entry` corresponding to the specified raw WASI `fd` from the `WasiCtx` object.
pub(crate) fn remove_entry(&mut self, fd: wasi::__wasi_fd_t) -> WasiResult<Entry> {
// Remove the `fd` from valid entries.
let entry = self.entries.remove(&fd).ok_or(WasiError::EBADF)?;
// Next, deallocate the `fd`.
self.fd_pool.deallocate(fd);
Ok(entry)
pub(crate) fn remove_entry(&self, fd: types::Fd) -> Result<Entry> {
self.entries.borrow_mut().remove(fd).ok_or(Errno::Badf)
}
}
Loading