use crate::fdpool::FdPool;
use crate::old::snapshot_0::entry::Entry;
use crate::old::snapshot_0::wasi::{self, WasiError, WasiResult};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::ffi::{self, CString, OsString};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::{env, io, string};
#[derive(Debug, thiserror::Error)]
pub enum WasiCtxBuilderError {
#[error("general I/O error encountered: {0}")]
Io(#[from] io::Error),
#[error("provided sequence is not valid UTF-8: {0}")]
InvalidUtf8(#[from] string::FromUtf8Error),
#[error("provided sequence is not valid UTF-16: {0}")]
InvalidUtf16(#[from] string::FromUtf16Error),
#[error("provided sequence contained an unexpected NUL byte")]
UnexpectedNul(#[from] ffi::NulError),
#[error("preopened directory path {} is not a directory", .0.display())]
NotADirectory(PathBuf),
#[error("context object has too many opened files")]
TooManyFilesOpen,
}
type WasiCtxBuilderResult<T> = std::result::Result<T, WasiCtxBuilderError>;
enum PendingEntry {
Thunk(fn() -> io::Result<Entry>),
File(File),
}
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<Entry>
),
Self::File(f) => write!(fmt, "PendingEntry::File({:?})", f),
}
}
}
#[derive(Debug, Eq, Hash, PartialEq)]
enum PendingCString {
Bytes(Vec<u8>),
OsString(OsString),
}
impl From<Vec<u8>> for PendingCString {
fn from(bytes: Vec<u8>) -> Self {
Self::Bytes(bytes)
}
}
impl From<OsString> for PendingCString {
fn from(s: OsString) -> Self {
Self::OsString(s)
}
}
impl PendingCString {
fn into_string(self) -> WasiCtxBuilderResult<String> {
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<u16> = s.encode_wide().collect();
String::from_utf16(&bytes)?
}
};
Ok(res)
}
fn into_utf8_cstring(self) -> WasiCtxBuilderResult<CString> {
let s = self.into_string()?;
let s = CString::new(s)?;
Ok(s)
}
}
pub struct WasiCtxBuilder {
stdin: Option<PendingEntry>,
stdout: Option<PendingEntry>,
stderr: Option<PendingEntry>,
preopens: Option<Vec<(PathBuf, File)>>,
args: Option<Vec<PendingCString>>,
env: Option<HashMap<PendingCString, PendingCString>>,
}
impl WasiCtxBuilder {
pub fn new() -> Self {
Self {
stdin: Some(PendingEntry::Thunk(Entry::null)),
stdout: Some(PendingEntry::Thunk(Entry::null)),
stderr: Some(PendingEntry::Thunk(Entry::null)),
preopens: Some(Vec::new()),
args: Some(Vec::new()),
env: Some(HashMap::new()),
}
}
pub fn args<S: AsRef<[u8]>>(&mut self, args: impl IntoIterator<Item = S>) -> &mut Self {
self.args
.as_mut()
.unwrap()
.extend(args.into_iter().map(|a| a.as_ref().to_vec().into()));
self
}
pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
self.args
.as_mut()
.unwrap()
.push(arg.as_ref().to_vec().into());
self
}
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
}
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
self
}
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
self
}
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
self
}
pub fn inherit_stdio(&mut self) -> &mut Self {
self.stdin = Some(PendingEntry::Thunk(Entry::duplicate_stdin));
self.stdout = Some(PendingEntry::Thunk(Entry::duplicate_stdout));
self.stderr = Some(PendingEntry::Thunk(Entry::duplicate_stderr));
self
}
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
}
pub fn env<S: AsRef<[u8]>>(&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
}
pub fn envs<S: AsRef<[u8]>, T: Borrow<(S, S)>>(
&mut self,
envs: impl IntoIterator<Item = T>,
) -> &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
}
pub fn stdin(&mut self, file: File) -> &mut Self {
self.stdin = Some(PendingEntry::File(file));
self
}
pub fn stdout(&mut self, file: File) -> &mut Self {
self.stdout = Some(PendingEntry::File(file));
self
}
pub fn stderr(&mut self, file: File) -> &mut Self {
self.stderr = Some(PendingEntry::File(file));
self
}
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(), dir));
self
}
pub fn build(&mut self) -> WasiCtxBuilderResult<WasiCtx> {
let args = self
.args
.take()
.unwrap()
.into_iter()
.map(|arg| arg.into_utf8_cstring())
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
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());
let s = CString::new(pair)?;
Ok(s)
})
})
})
.collect::<WasiCtxBuilderResult<Vec<CString>>>()?;
let mut fd_pool = FdPool::new();
let mut entries: HashMap<wasi::__wasi_fd_t, Entry> = HashMap::new();
for pending in &mut [&mut self.stdin, &mut self.stdout, &mut self.stderr] {
let fd = fd_pool
.allocate()
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
tracing::debug!("WasiCtx inserting ({:?}, {:?})", fd, pending);
match pending.take().unwrap() {
PendingEntry::Thunk(f) => {
entries.insert(fd, f()?);
}
PendingEntry::File(f) => {
entries.insert(fd, Entry::from(f)?);
}
}
}
for (guest_path, dir) in self.preopens.take().unwrap() {
let preopen_fd = fd_pool
.allocate()
.ok_or(WasiCtxBuilderError::TooManyFilesOpen)?;
if !dir.metadata()?.is_dir() {
return Err(WasiCtxBuilderError::NotADirectory(guest_path));
}
let mut fe = Entry::from(dir)?;
fe.preopen_path = Some(guest_path);
tracing::debug!("WasiCtx inserting ({:?}, {:?})", preopen_fd, fe);
entries.insert(preopen_fd, fe);
tracing::debug!("WasiCtx entries = {:?}", entries);
}
Ok(WasiCtx {
args,
env,
fd_pool,
entries,
})
}
}
#[derive(Debug)]
pub struct WasiCtx {
fd_pool: FdPool,
entries: HashMap<wasi::__wasi_fd_t, Entry>,
pub(crate) args: Vec<CString>,
pub(crate) env: Vec<CString>,
}
impl WasiCtx {
pub fn new<S: AsRef<[u8]>>(args: impl IntoIterator<Item = S>) -> WasiCtxBuilderResult<Self> {
WasiCtxBuilder::new()
.args(args)
.inherit_stdio()
.inherit_env()
.build()
}
pub(crate) unsafe fn contains_entry(&self, fd: wasi::__wasi_fd_t) -> bool {
self.entries.contains_key(&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) unsafe fn get_entry_mut(&mut self, fd: wasi::__wasi_fd_t) -> WasiResult<&mut Entry> {
self.entries.get_mut(&fd).ok_or(WasiError::EBADF)
}
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_at(&mut self, fd: wasi::__wasi_fd_t, fe: Entry) -> Option<Entry> {
self.entries.insert(fd, fe)
}
pub(crate) fn remove_entry(&mut self, fd: wasi::__wasi_fd_t) -> WasiResult<Entry> {
let entry = self.entries.remove(&fd).ok_or(WasiError::EBADF)?;
self.fd_pool.deallocate(fd);
Ok(entry)
}
}