move wasi-c2 into wasi-common

This commit is contained in:
Pat Hickey
2021-01-28 15:15:50 -08:00
parent dd005208b6
commit 47fec44c10
29 changed files with 3 additions and 11 deletions

View File

@@ -0,0 +1,22 @@
use cap_std::time::{Duration, Instant, SystemTime};
pub enum SystemTimeSpec {
SymbolicNow,
Absolute(SystemTime),
}
pub trait WasiSystemClock {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> SystemTime;
}
pub trait WasiMonotonicClock {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> Instant;
}
pub struct WasiClocks {
pub system: Box<dyn WasiSystemClock>,
pub monotonic: Box<dyn WasiMonotonicClock>,
pub creation_time: cap_std::time::Instant,
}

View File

@@ -0,0 +1,122 @@
use crate::clocks::WasiClocks;
use crate::dir::{DirCaps, DirEntry, WasiDir};
use crate::file::{FileCaps, FileEntry, WasiFile};
use crate::sched::WasiSched;
use crate::string_array::{StringArray, StringArrayError};
use crate::table::Table;
use crate::Error;
use cap_rand::RngCore;
use std::cell::{RefCell, RefMut};
use std::path::{Path, PathBuf};
use std::rc::Rc;
pub struct WasiCtx {
pub args: StringArray,
pub env: StringArray,
pub random: RefCell<Box<dyn RngCore>>,
pub clocks: WasiClocks,
pub sched: Box<dyn WasiSched>,
pub table: Rc<RefCell<Table>>,
}
impl WasiCtx {
pub fn builder(
random: RefCell<Box<dyn RngCore>>,
clocks: WasiClocks,
sched: Box<dyn WasiSched>,
table: Rc<RefCell<Table>>,
) -> WasiCtxBuilder {
WasiCtxBuilder(WasiCtx {
args: StringArray::new(),
env: StringArray::new(),
random,
clocks,
sched,
table,
})
}
pub fn insert_file(&self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
self.table()
.insert_at(fd, Box::new(FileEntry::new(caps, file)));
}
pub fn insert_dir(
&self,
fd: u32,
dir: Box<dyn WasiDir>,
caps: DirCaps,
file_caps: FileCaps,
path: PathBuf,
) {
self.table().insert_at(
fd,
Box::new(DirEntry::new(caps, file_caps, Some(path), dir)),
);
}
pub fn table(&self) -> RefMut<Table> {
self.table.borrow_mut()
}
}
pub struct WasiCtxBuilder(WasiCtx);
impl WasiCtxBuilder {
pub fn build(self) -> Result<WasiCtx, Error> {
Ok(self.0)
}
pub fn arg(mut self, arg: &str) -> Result<Self, StringArrayError> {
self.0.args.push(arg.to_owned())?;
Ok(self)
}
pub fn env(mut self, var: &str, value: &str) -> Result<Self, StringArrayError> {
self.0.env.push(format!("{}={}", var, value))?;
Ok(self)
}
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
0,
f,
FileCaps::READ | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is read-only
);
self
}
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
1,
f,
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
);
self
}
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
2,
f,
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
);
self
}
pub fn preopened_dir(
self,
dir: Box<dyn WasiDir>,
path: impl AsRef<Path>,
) -> Result<Self, Error> {
let caps = DirCaps::all();
let file_caps = FileCaps::all();
self.0.table().push(Box::new(DirEntry::new(
caps,
file_caps,
Some(path.as_ref().to_owned()),
dir,
)))?;
Ok(self)
}
}

View File

@@ -0,0 +1,182 @@
use crate::file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile};
use crate::{Error, ErrorExt, SystemTimeSpec};
use bitflags::bitflags;
use std::any::Any;
use std::cell::Ref;
use std::ops::Deref;
use std::path::PathBuf;
pub trait WasiDir {
fn as_any(&self) -> &dyn Any;
fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
caps: FileCaps,
fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error>;
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error>;
fn create_dir(&self, path: &str) -> Result<(), Error>;
fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<(ReaddirEntity, String), Error>>>, Error>;
fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>;
fn remove_dir(&self, path: &str) -> Result<(), Error>;
fn unlink_file(&self, path: &str) -> Result<(), Error>;
fn read_link(&self, path: &str) -> Result<PathBuf, Error>;
fn get_filestat(&self) -> Result<Filestat, Error>;
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error>;
fn rename(&self, path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error>;
fn hard_link(
&self,
path: &str,
target_dir: &dyn WasiDir,
target_path: &str,
) -> Result<(), Error>;
fn set_times(
&self,
path: &str,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
follow_symlinks: bool,
) -> Result<(), Error>;
}
pub(crate) struct DirEntry {
caps: DirCaps,
file_caps: FileCaps,
preopen_path: Option<PathBuf>, // precondition: PathBuf is valid unicode
dir: Box<dyn WasiDir>,
}
impl DirEntry {
pub fn new(
caps: DirCaps,
file_caps: FileCaps,
preopen_path: Option<PathBuf>,
dir: Box<dyn WasiDir>,
) -> Self {
DirEntry {
caps,
file_caps,
preopen_path,
dir,
}
}
pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> {
if self.caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
}
}
pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> {
if self.file_caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable()
.context(format!("desired {:?}, has {:?}", caps, self.file_caps)))
}
}
pub fn drop_caps_to(&mut self, caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> {
self.capable_of_dir(caps)?;
self.capable_of_file(file_caps)?;
self.caps = caps;
self.file_caps = file_caps;
Ok(())
}
pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps {
self.caps & desired_caps
}
pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps {
self.file_caps & desired_caps
}
pub fn get_dir_fdstat(&self) -> DirFdStat {
DirFdStat {
dir_caps: self.caps,
file_caps: self.file_caps,
}
}
pub fn preopen_path(&self) -> &Option<PathBuf> {
&self.preopen_path
}
}
pub trait DirEntryExt<'a> {
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error>;
}
impl<'a> DirEntryExt<'a> for Ref<'a, DirEntry> {
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error> {
self.capable_of_dir(caps)?;
Ok(Ref::map(self, |r| r.dir.deref()))
}
}
bitflags! {
pub struct DirCaps: u32 {
const CREATE_DIRECTORY = 0b1;
const CREATE_FILE = 0b10;
const LINK_SOURCE = 0b100;
const LINK_TARGET = 0b1000;
const OPEN = 0b10000;
const READDIR = 0b100000;
const READLINK = 0b1000000;
const RENAME_SOURCE = 0b10000000;
const RENAME_TARGET = 0b100000000;
const SYMLINK = 0b1000000000;
const REMOVE_DIRECTORY = 0b10000000000;
const UNLINK_FILE = 0b100000000000;
const PATH_FILESTAT_GET = 0b1000000000000;
const PATH_FILESTAT_SET_TIMES = 0b10000000000000;
const FILESTAT_GET = 0b100000000000000;
const FILESTAT_SET_TIMES = 0b1000000000000000;
}
}
#[derive(Debug, Clone)]
pub struct DirFdStat {
pub file_caps: FileCaps,
pub dir_caps: DirCaps,
}
pub(crate) trait TableDirExt {
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error>;
fn is_preopen(&self, fd: u32) -> bool;
}
impl TableDirExt for crate::table::Table {
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error> {
self.get(fd)
}
fn is_preopen(&self, fd: u32) -> bool {
if self.is::<DirEntry>(fd) {
let dir_entry: std::cell::Ref<DirEntry> = self.get(fd).unwrap();
dir_entry.preopen_path.is_some()
} else {
false
}
}
}
pub struct ReaddirEntity {
pub next: ReaddirCursor,
pub inode: u64,
pub namelen: u32,
pub filetype: FileType,
}
#[derive(Debug, Copy, Clone)]
pub struct ReaddirCursor(u64);
impl From<u64> for ReaddirCursor {
fn from(c: u64) -> ReaddirCursor {
ReaddirCursor(c)
}
}
impl From<ReaddirCursor> for u64 {
fn from(c: ReaddirCursor) -> u64 {
c.0
}
}

View File

@@ -0,0 +1,110 @@
pub use anyhow::Error;
/// Internal error type for the `wasi-common` crate.
/// Contains variants of the WASI `$errno` type are added according to what is actually used internally by
/// the crate. Not all values are represented presently.
#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
/// Errno::TooBig: Argument list too long
#[error("TooBig: Argument list too long")]
TooBig,
/// Errno::Badf: Bad file descriptor
#[error("Badf: Bad file descriptor")]
Badf,
/// Errno::Exist: File exists
#[error("Exist: File exists")]
Exist,
/// Errno::Ilseq: Illegal byte sequence
#[error("Ilseq: Illegal byte sequence")]
Ilseq,
/// Errno::Inval: Invalid argument
#[error("Inval: Invalid argument")]
Inval,
/// Errno::Io: I/O error
#[error("Io: I/O error")]
Io,
/// Errno::Nametoolong: Filename too long
#[error("Nametoolong: Filename too long")]
Nametoolong,
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
#[error("Notdir: Not a directory or a symbolic link to a directory")]
Notdir,
/// Errno::Notsup: Not supported, or operation not supported on socket.
#[error("Notsup: Not supported, or operation not supported on socket")]
Notsup,
/// Errno::Overflow: Value too large to be stored in data type.
#[error("Overflow: Value too large to be stored in data type")]
Overflow,
/// Errno::Range: Result too large
#[error("Range: Result too large")]
Range,
/// Errno::Spipe: Invalid seek
#[error("Spipe: Invalid seek")]
Spipe,
/// Errno::NotCapable: Not capable
#[error("Not capable")]
NotCapable,
}
pub trait ErrorExt {
fn trap(msg: impl Into<String>) -> Self;
fn too_big() -> Self;
fn badf() -> Self;
fn exist() -> Self;
fn illegal_byte_sequence() -> Self;
fn invalid_argument() -> Self;
fn io() -> Self;
fn name_too_long() -> Self;
fn not_dir() -> Self;
fn not_supported() -> Self;
fn overflow() -> Self;
fn range() -> Self;
fn seek_pipe() -> Self;
fn not_capable() -> Self;
}
impl ErrorExt for Error {
fn trap(msg: impl Into<String>) -> Self {
anyhow::anyhow!(msg.into())
}
fn too_big() -> Self {
ErrorKind::TooBig.into()
}
fn badf() -> Self {
ErrorKind::Badf.into()
}
fn exist() -> Self {
ErrorKind::Exist.into()
}
fn illegal_byte_sequence() -> Self {
ErrorKind::Ilseq.into()
}
fn invalid_argument() -> Self {
ErrorKind::Inval.into()
}
fn io() -> Self {
ErrorKind::Io.into()
}
fn name_too_long() -> Self {
ErrorKind::Nametoolong.into()
}
fn not_dir() -> Self {
ErrorKind::Notdir.into()
}
fn not_supported() -> Self {
ErrorKind::Notsup.into()
}
fn overflow() -> Self {
ErrorKind::Overflow.into()
}
fn range() -> Self {
ErrorKind::Range.into()
}
fn seek_pipe() -> Self {
ErrorKind::Spipe.into()
}
fn not_capable() -> Self {
ErrorKind::NotCapable.into()
}
}

View File

@@ -0,0 +1,172 @@
use crate::{Error, ErrorExt, SystemTimeSpec};
use bitflags::bitflags;
use std::any::Any;
use std::cell::{Ref, RefMut};
use std::ops::{Deref, DerefMut};
pub trait WasiFile {
fn as_any(&self) -> &dyn Any;
fn datasync(&self) -> Result<(), Error>; // write op
fn sync(&self) -> Result<(), Error>; // file op
fn get_filetype(&self) -> Result<FileType, Error>; // file op
fn get_fdflags(&self) -> Result<FdFlags, Error>; // file op
fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op
fn get_filestat(&self) -> Result<Filestat, Error>; // split out get_length as a read & write op, rest is a file op
fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op
fn advise(
&self,
offset: u64,
len: u64,
advice: system_interface::fs::Advice,
) -> Result<(), Error>; // file op
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error>;
fn read_vectored(&self, bufs: &mut [std::io::IoSliceMut]) -> Result<u64, Error>; // read op
fn read_vectored_at(&self, bufs: &mut [std::io::IoSliceMut], offset: u64)
-> Result<u64, Error>; // file op
fn write_vectored(&self, bufs: &[std::io::IoSlice]) -> Result<u64, Error>; // write op
fn write_vectored_at(&self, bufs: &[std::io::IoSlice], offset: u64) -> Result<u64, Error>; // file op
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
}
#[derive(Debug, Copy, Clone)]
pub enum FileType {
Unknown,
BlockDevice,
CharacterDevice,
Directory,
RegularFile,
SocketDgram,
SocketStream,
SymbolicLink,
Pipe,
}
bitflags! {
pub struct FdFlags: u32 {
const APPEND = 0b1;
const DSYNC = 0b10;
const NONBLOCK = 0b100;
const RSYNC = 0b1000;
const SYNC = 0b10000;
}
}
bitflags! {
pub struct OFlags: u32 {
const CREATE = 0b1;
const DIRECTORY = 0b10;
const EXCLUSIVE = 0b100;
const TRUNCATE = 0b1000;
}
}
#[derive(Debug, Clone)]
pub struct Filestat {
pub device_id: u64,
pub inode: u64,
pub filetype: FileType,
pub nlink: u64,
pub size: u64, // this is a read field, the rest are file fields
pub atim: Option<std::time::SystemTime>,
pub mtim: Option<std::time::SystemTime>,
pub ctim: Option<std::time::SystemTime>,
}
pub(crate) trait TableFileExt {
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error>;
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error>;
}
impl TableFileExt for crate::table::Table {
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error> {
self.get(fd)
}
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error> {
self.get_mut(fd)
}
}
pub(crate) struct FileEntry {
caps: FileCaps,
file: Box<dyn WasiFile>,
}
impl FileEntry {
pub fn new(caps: FileCaps, file: Box<dyn WasiFile>) -> Self {
FileEntry { caps, file }
}
pub fn capable_of(&self, caps: FileCaps) -> Result<(), Error> {
if self.caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
}
}
pub fn drop_caps_to(&mut self, caps: FileCaps) -> Result<(), Error> {
self.capable_of(caps)?;
self.caps = caps;
Ok(())
}
pub fn get_fdstat(&self) -> Result<FdStat, Error> {
Ok(FdStat {
filetype: self.file.get_filetype()?,
caps: self.caps,
flags: self.file.get_fdflags()?,
})
}
}
pub trait FileEntryExt<'a> {
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error>;
}
impl<'a> FileEntryExt<'a> for Ref<'a, FileEntry> {
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error> {
self.capable_of(caps)?;
Ok(Ref::map(self, |r| r.file.deref()))
}
}
pub trait FileEntryMutExt<'a> {
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error>;
}
impl<'a> FileEntryMutExt<'a> for RefMut<'a, FileEntry> {
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error> {
self.capable_of(caps)?;
Ok(RefMut::map(self, |r| r.file.deref_mut()))
}
}
bitflags! {
pub struct FileCaps : u32 {
const DATASYNC = 0b1;
const READ = 0b10;
const SEEK = 0b100;
const FDSTAT_SET_FLAGS = 0b1000;
const SYNC = 0b10000;
const TELL = 0b100000;
const WRITE = 0b1000000;
const ADVISE = 0b10000000;
const ALLOCATE = 0b100000000;
const FILESTAT_GET = 0b1000000000;
const FILESTAT_SET_SIZE = 0b10000000000;
const FILESTAT_SET_TIMES = 0b100000000000;
const POLL_READWRITE = 0b1000000000000;
}
}
#[derive(Debug, Clone)]
pub struct FdStat {
pub filetype: FileType,
pub caps: FileCaps,
pub flags: FdFlags,
}

View File

@@ -0,0 +1,20 @@
#![cfg_attr(feature = "nightly", feature(windows_by_handle))]
pub mod clocks;
mod ctx;
pub mod dir;
mod error;
pub mod file;
pub mod pipe;
pub mod random;
pub mod sched;
pub mod snapshots;
mod string_array;
pub mod table;
pub use clocks::SystemTimeSpec;
pub use ctx::{WasiCtx, WasiCtxBuilder};
pub use dir::{DirCaps, ReaddirCursor, ReaddirEntity, WasiDir};
pub use error::{Error, ErrorExt, ErrorKind};
pub use file::{FdFlags, FileCaps, Filestat, OFlags, WasiFile};
pub use string_array::StringArrayError;

View File

@@ -0,0 +1,306 @@
// This is mostly stubs
#![allow(unused_variables, dead_code)]
//! Virtual pipes.
//!
//! These types provide easy implementations of `WasiFile` that mimic much of the behavior of Unix
//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other
//! than OS files.
//!
//! Some convenience constructors are included for common backing types like `Vec<u8>` and `String`,
//! but the virtual pipes can be instantiated with any `Read` or `Write` type.
//!
use crate::{
file::{FdFlags, FileType, Filestat, WasiFile},
Error, ErrorExt, SystemTimeSpec,
};
use std::any::Any;
use std::convert::TryInto;
use std::io::{self, Read, Write};
use std::sync::{Arc, RwLock};
use system_interface::fs::Advice;
/// A virtual pipe read end.
///
/// A variety of `From` impls are provided so that common pipe types are easy to create. For example:
///
/// ```
/// # use wasi_c2::WasiCtx;
/// # use wasi_c2::virt::pipe::ReadPipe;
/// let stdin = ReadPipe::from("hello from stdin!");
/// let ctx = WasiCtx::builder().stdin(Box::new(stdin)).build();
/// ```
#[derive(Debug)]
pub struct ReadPipe<R: Read> {
reader: Arc<RwLock<R>>,
}
impl<R: Read> Clone for ReadPipe<R> {
fn clone(&self) -> Self {
Self {
reader: self.reader.clone(),
}
}
}
impl<R: Read> ReadPipe<R> {
/// Create a new pipe from a `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn new(r: R) -> Self {
Self::from_shared(Arc::new(RwLock::new(r)))
}
/// Create a new pipe from a shareable `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn from_shared(reader: Arc<RwLock<R>>) -> Self {
Self { reader }
}
/// Try to convert this `ReadPipe<R>` back to the underlying `R` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `R` exist.
pub fn try_into_inner(mut self) -> Result<R, Self> {
match Arc::try_unwrap(self.reader) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(reader) => {
self.reader = reader;
Err(self)
}
}
}
fn borrow(&self) -> std::sync::RwLockWriteGuard<R> {
RwLock::write(&self.reader).unwrap()
}
}
impl From<Vec<u8>> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: Vec<u8>) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&[u8]> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: &[u8]) -> Self {
Self::from(r.to_vec())
}
}
impl From<String> for ReadPipe<io::Cursor<String>> {
fn from(r: String) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&str> for ReadPipe<io::Cursor<String>> {
fn from(r: &str) -> Self {
Self::from(r.to_string())
}
}
impl<R: Read + Any> WasiFile for ReadPipe<R> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(()) // trivial: no implementation needed
}
fn sync(&self) -> Result<(), Error> {
Ok(()) // trivial
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Read :(
atim: None,
mtim: None,
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
let n = self.borrow().read_vectored(bufs)?;
Ok(n.try_into()?)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}
/// A virtual pipe write end.
///
/// ```
/// # use wasi_c2::WasiCtx;
/// # use wasi_c2::virt::pipe::WritePipe;
/// let stdout = WritePipe::new_in_memory();
/// let ctx = WasiCtx::builder().stdout(Box::new(stdout.clone())).build();
/// // use ctx in an instance, then make sure it is dropped:
/// drop(ctx);
/// let contents: Vec<u8> = stdout.try_into_inner().expect("sole remaining reference to WritePipe").into_inner();
/// println!("contents of stdout: {:?}", contents);
/// ```
#[derive(Debug)]
pub struct WritePipe<W: Write> {
writer: Arc<RwLock<W>>,
}
impl<W: Write> Clone for WritePipe<W> {
fn clone(&self) -> Self {
Self {
writer: self.writer.clone(),
}
}
}
impl<W: Write> WritePipe<W> {
/// Create a new pipe from a `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn new(w: W) -> Self {
Self::from_shared(Arc::new(RwLock::new(w)))
}
/// Create a new pipe from a shareable `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn from_shared(writer: Arc<RwLock<W>>) -> Self {
Self { writer }
}
/// Try to convert this `WritePipe<W>` back to the underlying `W` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `W` exist.
pub fn try_into_inner(mut self) -> Result<W, Self> {
match Arc::try_unwrap(self.writer) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(writer) => {
self.writer = writer;
Err(self)
}
}
}
fn borrow(&self) -> std::sync::RwLockWriteGuard<W> {
RwLock::write(&self.writer).unwrap()
}
}
impl WritePipe<io::Cursor<Vec<u8>>> {
/// Create a new writable virtual pipe backed by a `Vec<u8>` buffer.
pub fn new_in_memory() -> Self {
Self::new(io::Cursor::new(vec![]))
}
}
impl<W: Write + Any> WasiFile for WritePipe<W> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Write :(
atim: None,
mtim: None,
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
Err(Error::badf())
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
let n = self.borrow().write_vectored(bufs)?;
Ok(n.try_into()?)
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}

View File

@@ -0,0 +1,46 @@
use cap_rand::RngCore;
/// Implement `WasiRandom` using a deterministic cycle of bytes.
pub struct Deterministic {
cycle: std::iter::Cycle<std::vec::IntoIter<u8>>,
}
impl Deterministic {
pub fn new(bytes: Vec<u8>) -> Self {
Deterministic {
cycle: bytes.into_iter().cycle(),
}
}
}
impl RngCore for Deterministic {
fn next_u32(&mut self) -> u32 {
todo!()
}
fn next_u64(&mut self) -> u64 {
todo!()
}
fn fill_bytes(&mut self, buf: &mut [u8]) {
for b in buf.iter_mut() {
*b = self.cycle.next().expect("infinite sequence");
}
}
fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), cap_rand::Error> {
self.fill_bytes(buf);
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn deterministic() {
let mut det = Deterministic::new(vec![1, 2, 3, 4]);
let mut buf = vec![0; 1024];
det.try_fill_bytes(&mut buf).expect("get randomness");
for (ix, b) in buf.iter().enumerate() {
assert_eq!(*b, (ix % 4) as u8 + 1)
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use cap_std::time::{Duration, Instant};
use std::cell::Ref;
pub mod subscription;
use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult};
pub trait WasiSched {
fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>;
fn sched_yield(&self) -> Result<(), Error>;
}
pub struct Userdata(u64);
impl From<u64> for Userdata {
fn from(u: u64) -> Userdata {
Userdata(u)
}
}
impl From<Userdata> for u64 {
fn from(u: Userdata) -> u64 {
u.0
}
}
pub struct Poll<'a> {
subs: Vec<(Subscription<'a>, Userdata)>,
}
impl<'a> Poll<'a> {
pub fn new() -> Self {
Self { subs: Vec::new() }
}
pub fn subscribe_monotonic_clock(
&mut self,
clock: &'a dyn WasiMonotonicClock,
deadline: Instant,
precision: Duration,
ud: Userdata,
) {
self.subs.push((
Subscription::MonotonicClock(MonotonicClockSubscription {
clock,
deadline,
precision,
}),
ud,
));
}
pub fn subscribe_read(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
self.subs
.push((Subscription::Read(RwSubscription::new(file)), ud));
}
pub fn subscribe_write(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
self.subs
.push((Subscription::Write(RwSubscription::new(file)), ud));
}
pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> {
self.subs
.into_iter()
.filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud)))
.collect()
}
pub fn is_empty(&self) -> bool {
self.subs.is_empty()
}
pub fn earliest_clock_deadline(&'a self) -> Option<&MonotonicClockSubscription<'a>> {
let mut subs = self
.subs
.iter()
.filter_map(|(s, _ud)| match s {
Subscription::MonotonicClock(t) => Some(t),
_ => None,
})
.collect::<Vec<&MonotonicClockSubscription<'a>>>();
subs.sort_by(|a, b| a.deadline.cmp(&b.deadline));
subs.into_iter().next() // First element is earliest
}
pub fn rw_subscriptions(&'a self) -> impl Iterator<Item = &Subscription<'a>> {
self.subs.iter().filter_map(|(s, _ud)| match s {
Subscription::Read { .. } | Subscription::Write { .. } => Some(s),
_ => None,
})
}
}

View File

@@ -0,0 +1,79 @@
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use bitflags::bitflags;
use cap_std::time::{Duration, Instant};
use std::cell::{Cell, Ref};
bitflags! {
pub struct RwEventFlags: u32 {
const HANGUP = 0b1;
}
}
pub struct RwSubscription<'a> {
pub file: Ref<'a, dyn WasiFile>,
status: Cell<Option<Result<(u64, RwEventFlags), Error>>>,
}
impl<'a> RwSubscription<'a> {
pub fn new(file: Ref<'a, dyn WasiFile>) -> Self {
Self {
file,
status: Cell::new(None),
}
}
pub fn complete(&self, size: u64, flags: RwEventFlags) {
self.status.set(Some(Ok((size, flags))))
}
pub fn error(&self, error: Error) {
self.status.set(Some(Err(error)))
}
pub fn result(self) -> Option<Result<(u64, RwEventFlags), Error>> {
self.status.into_inner()
}
}
pub struct MonotonicClockSubscription<'a> {
pub clock: &'a dyn WasiMonotonicClock,
pub deadline: Instant,
pub precision: Duration,
}
impl<'a> MonotonicClockSubscription<'a> {
pub fn now(&self) -> Instant {
self.clock.now(self.precision)
}
pub fn duration_until(&self) -> Option<Duration> {
self.deadline.checked_duration_since(self.now())
}
pub fn result(&self) -> Option<Result<(), Error>> {
if self.now().checked_duration_since(self.deadline).is_some() {
Some(Ok(()))
} else {
None
}
}
}
pub enum Subscription<'a> {
Read(RwSubscription<'a>),
Write(RwSubscription<'a>),
MonotonicClock(MonotonicClockSubscription<'a>),
}
pub enum SubscriptionResult {
Read(Result<(u64, RwEventFlags), Error>),
Write(Result<(u64, RwEventFlags), Error>),
MonotonicClock(Result<(), Error>),
}
impl SubscriptionResult {
pub fn from_subscription(s: Subscription) -> Option<SubscriptionResult> {
match s {
Subscription::Read(s) => s.result().map(SubscriptionResult::Read),
Subscription::Write(s) => s.result().map(SubscriptionResult::Write),
Subscription::MonotonicClock(s) => s.result().map(SubscriptionResult::MonotonicClock),
}
}
}

View File

@@ -0,0 +1 @@
pub mod preview_1;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
use crate::{Error, ErrorExt};
use wiggle::GuestPtr;
pub struct StringArray {
elems: Vec<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum StringArrayError {
#[error("Number of elements exceeds 2^32")]
NumberElements,
#[error("Element size exceeds 2^32")]
ElementSize,
#[error("Cumulative size exceeds 2^32")]
CumulativeSize,
}
impl StringArray {
pub fn new() -> Self {
StringArray { elems: Vec::new() }
}
pub fn push(&mut self, elem: String) -> Result<(), StringArrayError> {
if self.elems.len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::NumberElements);
}
if elem.as_bytes().len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::ElementSize);
}
if self.cumulative_size() as usize + elem.as_bytes().len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::CumulativeSize);
}
self.elems.push(elem);
Ok(())
}
pub fn number_elements(&self) -> u32 {
self.elems.len() as u32
}
pub fn cumulative_size(&self) -> u32 {
self.elems
.iter()
.map(|e| e.as_bytes().len() + 1)
.sum::<usize>() as u32
}
pub fn write_to_guest<'a>(
&self,
buffer: &GuestPtr<'a, u8>,
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
) -> Result<(), Error> {
let element_heads = element_heads.as_array(self.number_elements());
let buffer = buffer.as_array(self.cumulative_size());
let mut cursor = 0;
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
let bytes = elem.as_bytes();
let len = bytes.len() as u32;
{
let elem_buffer = buffer
.get_range(cursor..(cursor + len))
.ok_or(Error::invalid_argument())?; // Elements don't fit in buffer provided
elem_buffer.copy_from_slice(bytes)?;
}
buffer
.get(cursor + len)
.ok_or(Error::invalid_argument())?
.write(0)?; // 0 terminate
head?.write(buffer.get(cursor).expect("already validated"))?;
cursor += len + 1;
}
Ok(())
}
}

View File

@@ -0,0 +1,105 @@
use crate::{Error, ErrorExt};
use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
pub struct Table {
map: HashMap<u32, RefCell<Box<dyn Any>>>,
next_key: u32,
}
impl Table {
pub fn new() -> Self {
Table {
map: HashMap::new(),
next_key: 3, // 0, 1 and 2 are reserved for stdio
}
}
pub fn insert_at(&mut self, key: u32, a: Box<dyn Any>) {
self.map.insert(key, RefCell::new(a));
}
pub fn push(&mut self, a: Box<dyn Any>) -> Result<u32, Error> {
loop {
let key = self.next_key;
// XXX this is not correct. The table may still have empty entries, but our
// linear search strategy is quite bad
self.next_key = self
.next_key
.checked_add(1)
.ok_or_else(|| Error::trap("out of keys in table"))?;
if self.map.contains_key(&key) {
continue;
}
self.map.insert(key, RefCell::new(a));
return Ok(key);
}
}
pub fn contains_key(&self, key: u32) -> bool {
self.map.contains_key(&key)
}
pub fn is<T: Any + Sized>(&self, key: u32) -> bool {
if let Some(refcell) = self.map.get(&key) {
if let Ok(refmut) = refcell.try_borrow_mut() {
refmut.is::<T>()
} else {
false
}
} else {
false
}
}
pub fn get<T: Any + Sized>(&self, key: u32) -> Result<Ref<T>, Error> {
if let Some(refcell) = self.map.get(&key) {
if let Ok(r) = refcell.try_borrow() {
if r.is::<T>() {
Ok(Ref::map(r, |r| r.downcast_ref::<T>().unwrap()))
} else {
Err(Error::badf().context("element is a different type"))
}
} else {
Err(Error::trap("table get of mutably borrowed element"))
}
} else {
Err(Error::badf().context("key not in table"))
}
}
pub fn get_mut<T: Any + Sized>(&self, key: u32) -> Result<RefMut<T>, Error> {
if let Some(refcell) = self.map.get(&key) {
if let Ok(r) = refcell.try_borrow_mut() {
if r.is::<T>() {
Ok(RefMut::map(r, |r| r.downcast_mut::<T>().unwrap()))
} else {
Err(Error::badf().context("element is a different type"))
}
} else {
Err(Error::trap("table get_mut of borrowed element"))
}
} else {
Err(Error::badf().context("key not in table"))
}
}
pub fn delete(&mut self, key: u32) -> Option<Box<dyn Any>> {
self.map.remove(&key).map(|rc| RefCell::into_inner(rc))
}
pub fn update_in_place<T, F>(&mut self, key: u32, f: F) -> Result<(), Error>
where
T: Any + Sized,
F: FnOnce(T) -> Result<T, Error>,
{
let entry = self.delete(key).ok_or(Error::badf())?;
let downcast = entry
.downcast::<T>()
.map_err(|_| Error::exist().context("element is a different type"))?;
let new = f(*downcast)?;
self.insert_at(key, Box::new(new));
Ok(())
}
}