diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs index 4a7ed6c30a..6daad6aaef 100644 --- a/crates/c-api/src/wasi.rs +++ b/crates/c-api/src/wasi.rs @@ -201,63 +201,77 @@ enum WasiInstance { Snapshot0(WasiSnapshot0), } -macro_rules! config_to_builder { - ($builder:ident, $config:ident) => {{ - let mut builder = $builder::new(); - - if $config.inherit_args { - builder.inherit_args(); - } else if !$config.args.is_empty() { - builder.args($config.args); - } - - if $config.inherit_env { - builder.inherit_env(); - } else if !$config.env.is_empty() { - builder.envs($config.env); - } - - if $config.inherit_stdin { - builder.inherit_stdin(); - } else if let Some(file) = $config.stdin { - builder.stdin(file); - } - - if $config.inherit_stdout { - builder.inherit_stdout(); - } else if let Some(file) = $config.stdout { - builder.stdout(file); - } - - if $config.inherit_stderr { - builder.inherit_stderr(); - } else if let Some(file) = $config.stderr { - builder.stderr(file); - } - - for preopen in $config.preopens { - builder.preopened_dir(preopen.0, preopen.1); - } - - builder - }}; -} - -fn create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result { +fn create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result { + let mut builder = WasiSnapshot0CtxBuilder::new(); + if config.inherit_args { + builder.inherit_args(); + } else if !config.args.is_empty() { + builder.args(config.args); + } + if config.inherit_env { + builder.inherit_env(); + } else if !config.env.is_empty() { + builder.envs(config.env); + } + if config.inherit_stdin { + builder.inherit_stdin(); + } else if let Some(file) = config.stdin { + builder.stdin(file); + } + if config.inherit_stdout { + builder.inherit_stdout(); + } else if let Some(file) = config.stdout { + builder.stdout(file); + } + if config.inherit_stderr { + builder.inherit_stderr(); + } else if let Some(file) = config.stderr { + builder.stderr(file); + } + for preopen in config.preopens { + builder.preopened_dir(preopen.0, preopen.1); + } Ok(WasiInstance::Snapshot0(WasiSnapshot0::new( store, - config_to_builder!(WasiSnapshot0CtxBuilder, config) - .build() - .map_err(|e| e.to_string())?, + builder.build()?, ))) } -fn create_preview1_instance(store: &Store, config: wasi_config_t) -> Result { +fn create_preview1_instance(store: &Store, config: wasi_config_t) -> Result { + use std::convert::TryFrom; + use wasi_common::OsFile; + let mut builder = WasiPreview1CtxBuilder::new(); + if config.inherit_args { + builder.inherit_args(); + } else if !config.args.is_empty() { + builder.args(config.args); + } + if config.inherit_env { + builder.inherit_env(); + } else if !config.env.is_empty() { + builder.envs(config.env); + } + if config.inherit_stdin { + builder.inherit_stdin(); + } else if let Some(file) = config.stdin { + builder.stdin(OsFile::try_from(file)?); + } + if config.inherit_stdout { + builder.inherit_stdout(); + } else if let Some(file) = config.stdout { + builder.stdout(OsFile::try_from(file)?); + } + if config.inherit_stderr { + builder.inherit_stderr(); + } else if let Some(file) = config.stderr { + builder.stderr(OsFile::try_from(file)?); + } + for preopen in config.preopens { + builder.preopened_dir(preopen.0, preopen.1); + } Ok(WasiInstance::Preview1(WasiPreview1::new( store, - config_to_builder!(WasiPreview1CtxBuilder, config) - .build() - .map_err(|e| e.to_string())?, + builder.build()?, ))) } @@ -286,8 +300,10 @@ pub unsafe extern "C" fn wasi_instance_new( let store = &store.store; let result = match CStr::from_ptr(name).to_str().unwrap_or("") { - "wasi_snapshot_preview1" => create_preview1_instance(store, *config), - "wasi_unstable" => create_snapshot0_instance(store, *config), + "wasi_snapshot_preview1" => { + create_preview1_instance(store, *config).map_err(|e| e.to_string()) + } + "wasi_unstable" => create_snapshot0_instance(store, *config).map_err(|e| e.to_string()), _ => Err("unsupported WASI version".into()), }; diff --git a/crates/test-programs/tests/wasm_tests/runtime.rs b/crates/test-programs/tests/wasm_tests/runtime.rs index 0b4ba305e0..f10e94456c 100644 --- a/crates/test-programs/tests/wasm_tests/runtime.rs +++ b/crates/test-programs/tests/wasm_tests/runtime.rs @@ -1,7 +1,8 @@ use anyhow::Context; +use std::convert::TryFrom; use std::fs::File; use std::path::Path; -use wasi_common::VirtualDirEntry; +use wasi_common::{OsOther, VirtualDirEntry}; use wasmtime::{Linker, Module, Store}; #[derive(Clone, Copy, Debug)] @@ -46,7 +47,9 @@ pub fn instantiate( // where `stdin` is never ready to be read. In some CI systems, however, // stdin is closed which causes tests to fail. let (reader, _writer) = os_pipe::pipe()?; - builder.stdin(reader_to_file(reader)); + let file = reader_to_file(reader); + let handle = OsOther::try_from(file).context("failed to create OsOther from PipeReader")?; + builder.stdin(handle); let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?); let mut linker = Linker::new(&store); diff --git a/crates/wasi-common/src/ctx.rs b/crates/wasi-common/src/ctx.rs index 895c96ad6f..3121cb1462 100644 --- a/crates/wasi-common/src/ctx.rs +++ b/crates/wasi-common/src/ctx.rs @@ -47,7 +47,7 @@ type WasiCtxBuilderResult = std::result::Result; enum PendingEntry { Thunk(fn() -> io::Result>), - OsHandle(File), + Handle(Box), } impl std::fmt::Debug for PendingEntry { @@ -58,7 +58,7 @@ impl std::fmt::Debug for PendingEntry { "PendingEntry::Thunk({:p})", f as *const fn() -> io::Result> ), - Self::OsHandle(f) => write!(fmt, "PendingEntry::OsHandle({:?})", f), + Self::Handle(handle) => write!(fmt, "PendingEntry::Handle({:p})", handle), } } } @@ -247,21 +247,21 @@ impl WasiCtxBuilder { self } - /// Provide a File to use as stdin - pub fn stdin(&mut self, file: File) -> &mut Self { - self.stdin = Some(PendingEntry::OsHandle(file)); + /// Provide a `Handle` to use as stdin + pub fn stdin(&mut self, handle: T) -> &mut Self { + self.stdin = Some(PendingEntry::Handle(Box::new(handle))); self } - /// Provide a File to use as stdout - pub fn stdout(&mut self, file: File) -> &mut Self { - self.stdout = Some(PendingEntry::OsHandle(file)); + /// Provide a `Handle` to use as stdout + pub fn stdout(&mut self, handle: T) -> &mut Self { + self.stdout = Some(PendingEntry::Handle(Box::new(handle))); self } - /// Provide a File to use as stderr - pub fn stderr(&mut self, file: File) -> &mut Self { - self.stderr = Some(PendingEntry::OsHandle(file)); + /// Provide a `Handle` to use as stderr + pub fn stderr(&mut self, handle: T) -> &mut Self { + self.stderr = Some(PendingEntry::Handle(Box::new(handle))); self } @@ -368,9 +368,8 @@ impl WasiCtxBuilder { .insert(entry) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? } - PendingEntry::OsHandle(f) => { - let handle = OsOther::try_from(f)?; - let handle = EntryHandle::new(handle); + PendingEntry::Handle(handle) => { + let handle = EntryHandle::from(handle); let entry = Entry::new(handle); entries .insert(entry) diff --git a/crates/wasi-common/src/entry.rs b/crates/wasi-common/src/entry.rs index eb311a7b10..ba9e90c160 100644 --- a/crates/wasi-common/src/entry.rs +++ b/crates/wasi-common/src/entry.rs @@ -8,6 +8,7 @@ use std::rc::Rc; pub(crate) struct EntryHandle(Rc); impl EntryHandle { + #[allow(dead_code)] pub(crate) fn new(handle: T) -> Self { Self(Rc::new(handle)) } diff --git a/crates/wasi-common/src/handle.rs b/crates/wasi-common/src/handle.rs index 574db3de11..8cdf6a4c57 100644 --- a/crates/wasi-common/src/handle.rs +++ b/crates/wasi-common/src/handle.rs @@ -6,38 +6,49 @@ use std::io::{self, SeekFrom}; /// Represents rights of a `Handle`, either already held or required. #[derive(Debug, Copy, Clone)] -pub(crate) struct HandleRights { +pub struct HandleRights { pub(crate) base: Rights, pub(crate) inheriting: Rights, } impl HandleRights { - pub(crate) fn new(base: Rights, inheriting: Rights) -> Self { + /// Creates new `HandleRights` instance from `base` and `inheriting` rights. + pub fn new(base: Rights, inheriting: Rights) -> Self { Self { base, inheriting } } - /// Create new `HandleRights` instance from `base` rights only, keeping + /// Creates new `HandleRights` instance from `base` rights only, keeping /// `inheriting` set to none. - pub(crate) fn from_base(base: Rights) -> Self { + pub fn from_base(base: Rights) -> Self { Self { base, inheriting: Rights::empty(), } } - /// Create new `HandleRights` instance with both `base` and `inheriting` + /// Creates new `HandleRights` instance with both `base` and `inheriting` /// rights set to none. - pub(crate) fn empty() -> Self { + pub 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 { + /// Checks if `other` is a subset of those rights. + pub fn contains(&self, other: &Self) -> bool { self.base.contains(&other.base) && self.inheriting.contains(&other.inheriting) } + + /// Returns base rights. + pub fn base(&self) -> Rights { + self.base + } + + /// Returns inheriting rights. + pub fn inheriting(&self) -> Rights { + self.inheriting + } } impl fmt::Display for HandleRights { @@ -50,7 +61,25 @@ impl fmt::Display for HandleRights { } } -pub(crate) trait Handle { +/// Generic interface for all WASI-compatible handles. We currently group these into two groups: +/// * OS-based resources (actual, real resources): `OsFile`, `OsDir`, `OsOther`, and `Stdio`, +/// * virtual files and directories: VirtualDir`, and `InMemoryFile`. +/// +/// # Constructing `Handle`s representing OS-based resources +/// +/// Each type of handle can either be constructed directly (see docs entry for a specific handle +/// type such as `OsFile`), or you can let the `wasi_common` crate's machinery work it out +/// automatically for you using `std::convert::TryInto` from `std::fs::File`: +/// +/// ```rust,no_run +/// use std::convert::TryInto; +/// use wasi_common::Handle; +/// use std::fs::OpenOptions; +/// +/// let some_file = OpenOptions::new().read(true).open("some_file").unwrap(); +/// let wasi_handle: Box = some_file.try_into().unwrap(); +/// ``` +pub trait Handle { fn as_any(&self) -> &dyn Any; fn try_clone(&self) -> io::Result>; fn get_file_type(&self) -> types::Filetype; diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 4c0ccf7b4e..f9c17841a9 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -36,5 +36,9 @@ mod virtfs; pub mod wasi; pub use ctx::{WasiCtx, WasiCtxBuilder, WasiCtxBuilderError}; +pub use handle::{Handle, HandleRights}; +pub use sys::osdir::OsDir; +pub use sys::osfile::OsFile; +pub use sys::osother::{OsOther, OsOtherExt}; pub use sys::preopen_dir; pub use virtfs::{FileContents, VirtualDirEntry}; diff --git a/crates/wasi-common/src/sys/osdir.rs b/crates/wasi-common/src/sys/osdir.rs index b1051b8862..6ba3cc63a9 100644 --- a/crates/wasi-common/src/sys/osdir.rs +++ b/crates/wasi-common/src/sys/osdir.rs @@ -10,7 +10,7 @@ use std::ops::Deref; // TODO could this be cleaned up? // The actual `OsDir` struct is OS-dependent, therefore we delegate // its definition to OS-specific modules. -pub(crate) use super::sys_impl::osdir::OsDir; +pub use super::sys_impl::osdir::OsDir; impl Deref for OsDir { type Target = RawOsHandle; diff --git a/crates/wasi-common/src/sys/osfile.rs b/crates/wasi-common/src/sys/osfile.rs index a3491f5432..f07ddf6240 100644 --- a/crates/wasi-common/src/sys/osfile.rs +++ b/crates/wasi-common/src/sys/osfile.rs @@ -9,7 +9,24 @@ use std::io::{self, Read, Seek, SeekFrom, Write}; use std::ops::Deref; #[derive(Debug)] -pub(crate) struct OsFile { +/// A file backed by the operating system's file system. Dereferences to a +/// `RawOsHandle`. Its impl of `Handle` uses Rust's `std` to implement all +/// file descriptor operations. +/// +/// # Constructing `OsFile` +/// +/// `OsFile` can currently only be constructed from `std::fs::File` using +/// the `std::convert::TryFrom` trait: +/// +/// ```rust,no_run +/// use std::fs::OpenOptions; +/// use std::convert::TryFrom; +/// use wasi_common::OsFile; +/// +/// let file = OpenOptions::new().read(true).open("some_file").unwrap(); +/// let os_file = OsFile::try_from(file).unwrap(); +/// ``` +pub struct OsFile { rights: Cell, handle: RawOsHandle, } diff --git a/crates/wasi-common/src/sys/osother.rs b/crates/wasi-common/src/sys/osother.rs index de30a4f2dd..314d91dea3 100644 --- a/crates/wasi-common/src/sys/osother.rs +++ b/crates/wasi-common/src/sys/osother.rs @@ -10,7 +10,9 @@ use std::fs::File; use std::io::{self, Read, Write}; use std::ops::Deref; -pub(crate) trait OsOtherExt { +/// Extra methods for `OsOther` that are only available when configured for +/// some operating systems. +pub trait OsOtherExt { /// Create `OsOther` as `dyn Handle` from null device. fn from_null() -> io::Result>; } @@ -20,8 +22,22 @@ pub(crate) trait OsOtherExt { /// sockets, streams, etc. As such, when redirecting stdio within `WasiCtxBuilder`, the redirected /// pipe should be encapsulated within this instance _and not_ `OsFile` which represents a regular /// OS file. +/// +/// # Constructing `OsOther` +/// +/// `OsOther` can currently only be constructed from `std::fs::File` using +/// the `std::convert::TryFrom` trait: +/// +/// ```rust,no_run +/// use std::fs::OpenOptions; +/// use std::convert::TryFrom; +/// use wasi_common::OsOther; +/// +/// let pipe = OpenOptions::new().read(true).open("a_pipe").unwrap(); +/// let os_other = OsOther::try_from(pipe).unwrap(); +/// ``` #[derive(Debug)] -pub(crate) struct OsOther { +pub struct OsOther { file_type: Filetype, rights: Cell, handle: RawOsHandle, diff --git a/crates/wasi-common/src/sys/unix/bsd/osdir.rs b/crates/wasi-common/src/sys/unix/bsd/osdir.rs index 0726ad2ef6..ed665329d6 100644 --- a/crates/wasi-common/src/sys/unix/bsd/osdir.rs +++ b/crates/wasi-common/src/sys/unix/bsd/osdir.rs @@ -6,7 +6,24 @@ use std::io; use yanix::dir::Dir; #[derive(Debug)] -pub(crate) struct OsDir { +/// A directory in the operating system's file system. Its impl of `Handle` is +/// in `sys::osdir`. This type is exposed to all other modules as +/// `sys::osdir::OsDir` when configured. +/// +/// # Constructing `OsDir` +/// +/// `OsDir` can currently only be constructed from `std::fs::File` using +/// the `std::convert::TryFrom` trait: +/// +/// ```rust,no_run +/// use std::fs::OpenOptions; +/// use std::convert::TryFrom; +/// use wasi_common::OsDir; +/// +/// let dir = OpenOptions::new().read(true).open("some_dir").unwrap(); +/// let os_dir = OsDir::try_from(dir).unwrap(); +/// ``` +pub struct OsDir { pub(crate) rights: Cell, pub(crate) handle: RawOsHandle, // When the client makes a `fd_readdir` syscall on this descriptor, @@ -39,7 +56,9 @@ impl OsDir { stream_ptr, }) } - /// Returns the `Dir` stream pointer associated with this `OsDir`. + /// Returns the `Dir` stream pointer associated with this `OsDir`. Duck + /// typing: sys::unix::fd::readdir expects the configured OsDir to have + /// this method. pub(crate) fn stream_ptr(&self) -> Result> { Ok(self.stream_ptr.borrow_mut()) } diff --git a/crates/wasi-common/src/sys/unix/linux/osdir.rs b/crates/wasi-common/src/sys/unix/linux/osdir.rs index 2fff99ec7b..c803178c6a 100644 --- a/crates/wasi-common/src/sys/unix/linux/osdir.rs +++ b/crates/wasi-common/src/sys/unix/linux/osdir.rs @@ -6,7 +6,24 @@ use std::io; use yanix::dir::Dir; #[derive(Debug)] -pub(crate) struct OsDir { +/// A directory in the operating system's file system. Its impl of `Handle` is +/// in `sys::osdir`. This type is exposed to all other modules as +/// `sys::osdir::OsDir` when configured. +/// +/// # Constructing `OsDir` +/// +/// `OsDir` can currently only be constructed from `std::fs::File` using +/// the `std::convert::TryFrom` trait: +/// +/// ```rust,no_run +/// use std::fs::OpenOptions; +/// use std::convert::TryFrom; +/// use wasi_common::OsDir; +/// +/// let dir = OpenOptions::new().read(true).open("some_dir").unwrap(); +/// let os_dir = OsDir::try_from(dir).unwrap(); +/// ``` +pub struct OsDir { pub(crate) rights: Cell, pub(crate) handle: RawOsHandle, } @@ -16,7 +33,8 @@ impl OsDir { let rights = Cell::new(rights); Ok(Self { rights, handle }) } - /// Returns the `Dir` stream pointer associated with this `OsDir`. + /// Returns the `Dir` stream pointer associated with this `OsDir`. Duck typing: + /// sys::unix::fd::readdir expects the configured OsDir to have this method. pub(crate) fn stream_ptr(&self) -> Result> { // We need to duplicate the handle, because `opendir(3)`: // After a successful call to fdopendir(), fd is used internally by the implementation, diff --git a/crates/wasi-common/src/sys/unix/osdir.rs b/crates/wasi-common/src/sys/unix/osdir.rs index 47b264208f..6c4ba35655 100644 --- a/crates/wasi-common/src/sys/unix/osdir.rs +++ b/crates/wasi-common/src/sys/unix/osdir.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io; use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd}; -pub(crate) use super::sys_impl::osdir::OsDir; +pub use super::sys_impl::osdir::OsDir; impl TryFrom for OsDir { type Error = io::Error; diff --git a/crates/wasi-common/src/sys/unix/oshandle.rs b/crates/wasi-common/src/sys/unix/oshandle.rs index 513e4b7787..b706a5bb0f 100644 --- a/crates/wasi-common/src/sys/unix/oshandle.rs +++ b/crates/wasi-common/src/sys/unix/oshandle.rs @@ -3,7 +3,7 @@ use std::io; use std::os::unix::prelude::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; #[derive(Debug)] -pub(crate) struct RawOsHandle(File); +pub struct RawOsHandle(File); impl RawOsHandle { /// Tries clone `self`. diff --git a/crates/wasi-common/src/sys/windows/osdir.rs b/crates/wasi-common/src/sys/windows/osdir.rs index 80cee50bce..ee32730525 100644 --- a/crates/wasi-common/src/sys/windows/osdir.rs +++ b/crates/wasi-common/src/sys/windows/osdir.rs @@ -8,7 +8,26 @@ use std::io; use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle}; #[derive(Debug)] -pub(crate) struct OsDir { +/// A directory in the operating system's file system. Its impl of `Handle` is +/// in `sys::osdir`. This type is exposed to all other modules as +/// `sys::osdir::OsDir` when configured. +/// +/// # Constructing `OsDir` +/// +/// `OsDir` can currently only be constructed from `std::fs::File` using +/// the `std::convert::TryFrom` trait: +/// +/// ```rust,no_run +/// use std::fs::OpenOptions; +/// use std::convert::TryFrom; +/// use std::os::windows::fs::OpenOptionsExt; +/// use wasi_common::OsDir; +/// use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS; +/// +/// let dir = OpenOptions::new().read(true).attributes(FILE_FLAG_BACKUP_SEMANTICS).open("some_dir").unwrap(); +/// let os_dir = OsDir::try_from(dir).unwrap(); +/// ``` +pub struct OsDir { pub(crate) rights: Cell, pub(crate) handle: RawOsHandle, } diff --git a/crates/wasi-common/src/sys/windows/oshandle.rs b/crates/wasi-common/src/sys/windows/oshandle.rs index 89884d03a8..74aacb8019 100644 --- a/crates/wasi-common/src/sys/windows/oshandle.rs +++ b/crates/wasi-common/src/sys/windows/oshandle.rs @@ -6,7 +6,7 @@ use std::mem::ManuallyDrop; use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; #[derive(Debug)] -pub(crate) struct RawOsHandle(Cell); +pub struct RawOsHandle(Cell); impl RawOsHandle { /// Tries cloning `self`. diff --git a/crates/wasi-common/src/virtfs.rs b/crates/wasi-common/src/virtfs.rs index 3d9b61b197..141a9cc693 100644 --- a/crates/wasi-common/src/virtfs.rs +++ b/crates/wasi-common/src/virtfs.rs @@ -11,12 +11,16 @@ use std::io::SeekFrom; use std::path::{Path, PathBuf}; use std::rc::Rc; +/// An entry in a virtual filesystem pub enum VirtualDirEntry { + /// The contents of a child directory Directory(HashMap), + /// A file File(Box), } impl VirtualDirEntry { + /// Construct an empty directory pub fn empty_directory() -> Self { Self::Directory(HashMap::new()) }