use crate::entry::{Entry, EntryHandle}; use crate::fdpool::FdPool; use crate::handle::Handle; use crate::sys::osdir::OsDir; use crate::sys::stdio::NullDevice; 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}; use std::rc::Rc; use std::{env, io, string}; /// Possible errors when `WasiCtxBuilder` fails building /// `WasiCtx`. #[derive(Debug, thiserror::Error)] pub enum WasiCtxBuilderError { /// General I/O error was encountered. #[error("general I/O error encountered: {0}")] Io(#[from] io::Error), /// Provided sequence of bytes was not a valid UTF-8. #[error("provided sequence is not valid UTF-8: {0}")] InvalidUtf8(#[from] string::FromUtf8Error), /// Provided sequence of bytes was not a valid UTF-16. /// /// This error is expected to only occur on Windows hosts. #[error("provided sequence is not valid UTF-16: {0}")] InvalidUtf16(#[from] string::FromUtf16Error), /// Provided sequence of bytes contained an unexpected NUL byte. #[error("provided sequence contained an unexpected NUL byte")] UnexpectedNul(#[from] ffi::NulError), /// 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, } type WasiCtxBuilderResult = std::result::Result; enum PendingEntry { Thunk(fn() -> io::Result>), Handle(Box), } impl std::fmt::Debug for PendingEntry { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Thunk(f) => write!( fmt, "PendingEntry::Thunk({:p})", f as *const fn() -> io::Result> ), Self::Handle(handle) => write!(fmt, "PendingEntry::Handle({:p})", handle), } } } #[derive(Debug, Eq, Hash, PartialEq)] enum PendingCString { Bytes(Vec), OsString(OsString), } impl From> for PendingCString { fn from(bytes: Vec) -> Self { Self::Bytes(bytes) } } impl From for PendingCString { fn from(s: OsString) -> Self { Self::OsString(s) } } impl PendingCString { fn into_string(self) -> WasiCtxBuilderResult { let res = match self { Self::Bytes(v) => String::from_utf8(v)?, #[cfg(unix)] Self::OsString(s) => { use std::os::unix::ffi::OsStringExt; String::from_utf8(s.into_vec())? } #[cfg(windows)] Self::OsString(s) => { use std::os::windows::ffi::OsStrExt; let bytes: Vec = s.encode_wide().collect(); String::from_utf16(&bytes)? } }; Ok(res) } /// Create a `CString` containing valid UTF-8. fn into_utf8_cstring(self) -> WasiCtxBuilderResult { let s = self.into_string()?; let s = CString::new(s)?; Ok(s) } } struct PendingPreopen(Box WasiCtxBuilderResult>>); impl PendingPreopen { fn new(f: F) -> Self where F: FnOnce() -> WasiCtxBuilderResult> + 'static, { Self(Box::new(f)) } fn into(self) -> WasiCtxBuilderResult> { self.0() } } /// A builder allowing customizable construction of `WasiCtx` instances. pub struct WasiCtxBuilder { stdin: Option, stdout: Option, stderr: Option, preopens: Option>, args: Option>, env: Option>, } impl WasiCtxBuilder { /// Builder for a new `WasiCtx`. pub fn new() -> Self { let stdin = Some(PendingEntry::Handle(Box::new(NullDevice::new()))); let stdout = Some(PendingEntry::Handle(Box::new(NullDevice::new()))); let stderr = Some(PendingEntry::Handle(Box::new(NullDevice::new()))); Self { stdin, stdout, stderr, preopens: Some(Vec::new()), args: Some(Vec::new()), env: Some(HashMap::new()), } } /// Add arguments to the command-line arguments list. /// /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail. pub fn args>(&mut self, args: impl IntoIterator) -> &mut Self { self.args .as_mut() .unwrap() .extend(args.into_iter().map(|a| a.as_ref().to_vec().into())); self } /// Add an argument to the command-line arguments list. /// /// Arguments must be valid UTF-8 with no NUL bytes, or else `WasiCtxBuilder::build()` will fail. pub fn arg>(&mut self, arg: S) -> &mut Self { self.args .as_mut() .unwrap() .push(arg.as_ref().to_vec().into()); self } /// Inherit the command-line arguments from the host process. /// /// If any arguments from the host process contain invalid UTF-8, `WasiCtxBuilder::build()` will /// fail. pub fn inherit_args(&mut self) -> &mut Self { let args = self.args.as_mut().unwrap(); args.clear(); args.extend(env::args_os().map(PendingCString::OsString)); self } /// Inherit stdin from the host process. pub fn inherit_stdin(&mut self) -> &mut Self { 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(Stdout::stdout)); self } /// Inherit stderr from the host process. pub fn inherit_stderr(&mut self) -> &mut Self { 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(Stdin::stdin)); self.stdout = Some(PendingEntry::Thunk(Stdout::stdout)); self.stderr = Some(PendingEntry::Thunk(Stderr::stderr)); self } /// Inherit the environment variables from the host process. /// /// If any environment variables from the host process contain invalid Unicode (UTF-16 for /// Windows, UTF-8 for other platforms), `WasiCtxBuilder::build()` will fail. pub fn inherit_env(&mut self) -> &mut Self { let env = self.env.as_mut().unwrap(); env.clear(); env.extend(std::env::vars_os().map(|(k, v)| (k.into(), v.into()))); self } /// Add an entry to the environment. /// /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// `WasiCtxBuilder::build()` will fail. pub fn env>(&mut self, k: S, v: S) -> &mut Self { self.env .as_mut() .unwrap() .insert(k.as_ref().to_vec().into(), v.as_ref().to_vec().into()); self } /// Add entries to the environment. /// /// Environment variable keys and values must be valid UTF-8 with no NUL bytes, or else /// `WasiCtxBuilder::build()` will fail. pub fn envs, T: Borrow<(S, S)>>( &mut self, envs: impl IntoIterator, ) -> &mut Self { self.env.as_mut().unwrap().extend(envs.into_iter().map(|t| { let (k, v) = t.borrow(); (k.as_ref().to_vec().into(), v.as_ref().to_vec().into()) })); self } /// 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 `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 `Handle` to use as stderr pub fn stderr(&mut self, handle: T) -> &mut Self { self.stderr = Some(PendingEntry::Handle(Box::new(handle))); self } /// Add a preopened directory. pub fn preopened_dir>(&mut self, dir: File, guest_path: P) -> &mut Self { 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 } /// Add a preopened virtual directory. pub fn preopened_virt>( &mut self, dir: VirtualDirEntry, guest_path: P, ) -> &mut Self { fn populate_directory(virtentry: HashMap, dir: &mut VirtualDir) { for (path, entry) in virtentry.into_iter() { match entry { VirtualDirEntry::Directory(dir_entries) => { let mut subdir = VirtualDir::new(true); populate_directory(dir_entries, &mut subdir); dir.add_dir(subdir, path); } VirtualDirEntry::File(content) => { dir.add_file(content, path); } } } } 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(), preopen)); self } /// Build a `WasiCtx`, consuming this `WasiCtxBuilder`. /// /// If any of the arguments or environment variables in this builder cannot be converted into /// `CString`s, either due to NUL bytes or Unicode conversions, this will fail. pub fn build(&mut self) -> WasiCtxBuilderResult { // Process arguments and environment variables into `CString`s, failing quickly if they // contain any NUL bytes, or if conversion from `OsString` fails. let args = self .args .take() .unwrap() .into_iter() .map(|arg| arg.into_utf8_cstring()) .collect::>>()?; let env = self .env .take() .unwrap() .into_iter() .map(|(k, v)| { k.into_string().and_then(|mut pair| { v.into_string().and_then(|v| { pair.push('='); pair.push_str(v.as_str()); // We have valid UTF-8, but the keys and values have not yet been checked // for NULs, so we do a final check here. let s = CString::new(pair)?; Ok(s) }) }) }) .collect::>>()?; 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(), ] { tracing::debug!( pending = tracing::field::debug(&pending), "WasiCtx inserting entry" ); let fd = match pending { PendingEntry::Thunk(f) => { let handle = EntryHandle::from(f()?); let entry = Entry::new(handle); entries .insert(entry) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? } PendingEntry::Handle(handle) => { let handle = EntryHandle::from(handle); let entry = Entry::new(handle); entries .insert(entry) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)? } }; tracing::debug!(fd = tracing::field::debug(fd), "WasiCtx inserted"); } // Then add the preopen entries. 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) .ok_or(WasiCtxBuilderError::TooManyFilesOpen)?; tracing::debug!(fd = tracing::field::debug(fd), "WasiCtx inserted",); } Ok(WasiCtx { args, env, entries: RefCell::new(entries), }) } } struct EntryTable { fd_pool: FdPool, entries: HashMap>, } 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 { let fd = self.fd_pool.allocate()?; self.entries.insert(fd, Rc::new(entry)); Some(fd) } fn insert_at(&mut self, fd: &types::Fd, entry: Rc) { self.entries.insert(*fd, entry); } fn get(&self, fd: &types::Fd) -> Option> { self.entries.get(fd).map(Rc::clone) } fn remove(&mut self, fd: types::Fd) -> Option> { let entry = self.entries.remove(&fd)?; self.fd_pool.deallocate(fd); Some(entry) } } pub struct WasiCtx { entries: RefCell, pub(crate) args: Vec, pub(crate) env: Vec, } impl WasiCtx { /// Make a new `WasiCtx` with some default settings. /// /// - File descriptors 0, 1, and 2 inherit stdin, stdout, and stderr from the host process. /// /// - Environment variables are inherited from the host process. /// /// To override these behaviors, use `WasiCtxBuilder`. pub fn new>(args: impl IntoIterator) -> WasiCtxBuilderResult { WasiCtxBuilder::new() .args(args) .inherit_stdio() .inherit_env() .build() } /// Check if `WasiCtx` contains the specified raw WASI `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) fn get_entry(&self, fd: types::Fd) -> Result> { match self.entries.borrow().get(&fd) { Some(entry) => Ok(entry), None => Err(Errno::Badf), } } /// Insert the specified `Entry` into the `WasiCtx` object. /// /// 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(&self, entry: Entry) -> Result { 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(&self, fd: types::Fd, entry: Rc) { 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(&self, fd: types::Fd) -> Result> { self.entries.borrow_mut().remove(fd).ok_or(Errno::Badf) } }