use crate::{
clocks::{
host::{monotonic_clock, wall_clock},
HostMonotonicClock, HostWallClock,
},
filesystem::{Dir, OpenMode},
network::{SocketAddrCheck, SocketAddrUse},
pipe, random, stdio,
stdio::{StdinStream, StdoutStream},
DirPerms, FilePerms,
};
use anyhow::Result;
use cap_rand::{Rng, RngCore, SeedableRng};
use cap_std::ambient_authority;
use std::path::Path;
use std::sync::Arc;
use std::{future::Future, pin::Pin};
use std::{mem, net::SocketAddr};
use wasmtime::component::ResourceTable;
pub struct WasiCtxBuilder {
stdin: Box<dyn StdinStream>,
stdout: Box<dyn StdoutStream>,
stderr: Box<dyn StdoutStream>,
env: Vec<(String, String)>,
args: Vec<String>,
preopens: Vec<(Dir, String)>,
socket_addr_check: SocketAddrCheck,
random: Box<dyn RngCore + Send>,
insecure_random: Box<dyn RngCore + Send>,
insecure_random_seed: u128,
wall_clock: Box<dyn HostWallClock + Send>,
monotonic_clock: Box<dyn HostMonotonicClock + Send>,
allowed_network_uses: AllowedNetworkUses,
allow_blocking_current_thread: bool,
max_random_size: u64,
built: bool,
}
const DEFAULT_MAX_RANDOM_SIZE: u64 = 2 << 30;
impl WasiCtxBuilder {
pub fn new() -> Self {
let insecure_random = Box::new(
cap_rand::rngs::SmallRng::from_rng(cap_rand::thread_rng(cap_rand::ambient_authority()))
.unwrap(),
);
let insecure_random_seed =
cap_rand::thread_rng(cap_rand::ambient_authority()).gen::<u128>();
Self {
stdin: Box::new(pipe::ClosedInputStream),
stdout: Box::new(pipe::SinkOutputStream),
stderr: Box::new(pipe::SinkOutputStream),
env: Vec::new(),
args: Vec::new(),
preopens: Vec::new(),
socket_addr_check: SocketAddrCheck::default(),
random: random::thread_rng(),
insecure_random,
insecure_random_seed,
wall_clock: wall_clock(),
monotonic_clock: monotonic_clock(),
allowed_network_uses: AllowedNetworkUses::default(),
allow_blocking_current_thread: false,
max_random_size: DEFAULT_MAX_RANDOM_SIZE,
built: false,
}
}
pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self {
self.stdin = Box::new(stdin);
self
}
pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self {
self.stdout = Box::new(stdout);
self
}
pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self {
self.stderr = Box::new(stderr);
self
}
pub fn inherit_stdin(&mut self) -> &mut Self {
self.stdin(stdio::stdin())
}
pub fn inherit_stdout(&mut self) -> &mut Self {
self.stdout(stdio::stdout())
}
pub fn inherit_stderr(&mut self) -> &mut Self {
self.stderr(stdio::stderr())
}
pub fn inherit_stdio(&mut self) -> &mut Self {
self.inherit_stdin().inherit_stdout().inherit_stderr()
}
pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self {
self.allow_blocking_current_thread = enable;
self
}
pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
self.env.extend(
env.iter()
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())),
);
self
}
pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {
self.env
.push((k.as_ref().to_owned(), v.as_ref().to_owned()));
self
}
pub fn inherit_env(&mut self) -> &mut Self {
self.envs(&std::env::vars().collect::<Vec<(String, String)>>())
}
pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
self.args.extend(args.iter().map(|a| a.as_ref().to_owned()));
self
}
pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
self.args.push(arg.as_ref().to_owned());
self
}
pub fn inherit_args(&mut self) -> &mut Self {
self.args(&std::env::args().collect::<Vec<String>>())
}
pub fn preopened_dir(
&mut self,
host_path: impl AsRef<Path>,
guest_path: impl AsRef<str>,
dir_perms: DirPerms,
file_perms: FilePerms,
) -> Result<&mut Self> {
let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?;
let mut open_mode = OpenMode::empty();
if dir_perms.contains(DirPerms::READ) {
open_mode |= OpenMode::READ;
}
if dir_perms.contains(DirPerms::MUTATE) {
open_mode |= OpenMode::WRITE;
}
self.preopens.push((
Dir::new(
dir,
dir_perms,
file_perms,
open_mode,
self.allow_blocking_current_thread,
),
guest_path.as_ref().to_owned(),
));
Ok(self)
}
pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self {
self.random = Box::new(random);
self
}
pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {
self.insecure_random = Box::new(insecure_random);
self
}
pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {
self.insecure_random_seed = insecure_random_seed;
self
}
pub fn max_random_size(&mut self, max_size: u64) -> &mut Self {
self.max_random_size = max_size;
self
}
pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {
self.wall_clock = Box::new(clock);
self
}
pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {
self.monotonic_clock = Box::new(clock);
self
}
pub fn inherit_network(&mut self) -> &mut Self {
self.socket_addr_check(|_, _| Box::pin(async { true }))
}
pub fn socket_addr_check<F>(&mut self, check: F) -> &mut Self
where
F: Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
+ Send
+ Sync
+ 'static,
{
self.socket_addr_check = SocketAddrCheck(Arc::new(check));
self
}
pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
self.allowed_network_uses.ip_name_lookup = enable;
self
}
pub fn allow_udp(&mut self, enable: bool) -> &mut Self {
self.allowed_network_uses.udp = enable;
self
}
pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {
self.allowed_network_uses.tcp = enable;
self
}
pub fn build(&mut self) -> WasiCtx {
assert!(!self.built);
let Self {
stdin,
stdout,
stderr,
env,
args,
preopens,
socket_addr_check,
random,
insecure_random,
insecure_random_seed,
wall_clock,
monotonic_clock,
allowed_network_uses,
allow_blocking_current_thread,
max_random_size,
built: _,
} = mem::replace(self, Self::new());
self.built = true;
WasiCtx {
stdin,
stdout,
stderr,
env,
args,
preopens,
socket_addr_check,
random,
insecure_random,
insecure_random_seed,
wall_clock,
monotonic_clock,
allowed_network_uses,
allow_blocking_current_thread,
max_random_size,
}
}
#[cfg(feature = "preview1")]
pub fn build_p1(&mut self) -> crate::preview1::WasiP1Ctx {
let wasi = self.build();
crate::preview1::WasiP1Ctx::new(wasi)
}
}
pub trait WasiView: Send {
fn table(&mut self) -> &mut ResourceTable;
fn ctx(&mut self) -> &mut WasiCtx;
}
impl<T: ?Sized + WasiView> WasiView for &mut T {
fn table(&mut self) -> &mut ResourceTable {
T::table(self)
}
fn ctx(&mut self) -> &mut WasiCtx {
T::ctx(self)
}
}
impl<T: ?Sized + WasiView> WasiView for Box<T> {
fn table(&mut self) -> &mut ResourceTable {
T::table(self)
}
fn ctx(&mut self) -> &mut WasiCtx {
T::ctx(self)
}
}
#[repr(transparent)]
pub struct WasiImpl<T>(pub T);
impl<T: WasiView> WasiView for WasiImpl<T> {
fn table(&mut self) -> &mut ResourceTable {
T::table(&mut self.0)
}
fn ctx(&mut self) -> &mut WasiCtx {
T::ctx(&mut self.0)
}
}
pub struct WasiCtx {
pub(crate) random: Box<dyn RngCore + Send>,
pub(crate) insecure_random: Box<dyn RngCore + Send>,
pub(crate) insecure_random_seed: u128,
pub(crate) wall_clock: Box<dyn HostWallClock + Send>,
pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send>,
pub(crate) env: Vec<(String, String)>,
pub(crate) args: Vec<String>,
pub(crate) preopens: Vec<(Dir, String)>,
pub(crate) stdin: Box<dyn StdinStream>,
pub(crate) stdout: Box<dyn StdoutStream>,
pub(crate) stderr: Box<dyn StdoutStream>,
pub(crate) socket_addr_check: SocketAddrCheck,
pub(crate) allowed_network_uses: AllowedNetworkUses,
pub(crate) allow_blocking_current_thread: bool,
pub(crate) max_random_size: u64,
}
impl WasiCtx {
pub fn builder() -> WasiCtxBuilder {
WasiCtxBuilder::new()
}
}
pub struct AllowedNetworkUses {
pub ip_name_lookup: bool,
pub udp: bool,
pub tcp: bool,
}
impl Default for AllowedNetworkUses {
fn default() -> Self {
Self {
ip_name_lookup: false,
udp: true,
tcp: true,
}
}
}
impl AllowedNetworkUses {
pub(crate) fn check_allowed_udp(&self) -> std::io::Result<()> {
if !self.udp {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"UDP is not allowed",
));
}
Ok(())
}
pub(crate) fn check_allowed_tcp(&self) -> std::io::Result<()> {
if !self.tcp {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"TCP is not allowed",
));
}
Ok(())
}
}