use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
sync::Arc,
};
use rand::Rng;
use thiserror::Error;
use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile};
use wasmer::{AsStoreMut, Extern, Imports, Instance, Module, Store};
use wasmer_config::package::PackageId;
#[cfg(feature = "journal")]
use crate::journal::{DynJournal, SnapshotTrigger};
use crate::{
bin_factory::{BinFactory, BinaryPackage},
capabilities::Capabilities,
fs::{WasiFs, WasiFsRoot, WasiInodes},
os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane},
state::WasiState,
syscalls::{
rewind_ext2,
types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO},
},
utils::xxhash_random,
Runtime, WasiEnv, WasiError, WasiFunctionEnv, WasiRuntimeError,
};
use wasmer_types::ModuleHash;
use super::env::WasiEnvInit;
#[derive(Default)]
pub struct WasiEnvBuilder {
pub(super) entry_function: Option<String>,
pub(super) args: Vec<String>,
pub(super) envs: Vec<(String, Vec<u8>)>,
pub(super) preopens: Vec<PreopenedDir>,
vfs_preopens: Vec<String>,
#[allow(clippy::type_complexity)]
pub(super) setup_fs_fn:
Option<Box<dyn Fn(&WasiInodes, &mut WasiFs) -> Result<(), String> + Send>>,
pub(super) stdout: Option<Box<dyn VirtualFile + Send + Sync + 'static>>,
pub(super) stderr: Option<Box<dyn VirtualFile + Send + Sync + 'static>>,
pub(super) stdin: Option<Box<dyn VirtualFile + Send + Sync + 'static>>,
pub(super) fs: Option<WasiFsRoot>,
pub(super) runtime: Option<Arc<dyn crate::Runtime + Send + Sync + 'static>>,
pub(super) current_dir: Option<PathBuf>,
pub(super) uses: Vec<BinaryPackage>,
pub(super) included_packages: HashSet<PackageId>,
pub(super) module_hash: Option<ModuleHash>,
pub(super) map_commands: HashMap<String, PathBuf>,
pub(super) capabilites: Capabilities,
pub(super) additional_imports: Imports,
#[cfg(feature = "journal")]
pub(super) snapshot_on: Vec<SnapshotTrigger>,
#[cfg(feature = "journal")]
pub(super) snapshot_interval: Option<std::time::Duration>,
#[cfg(feature = "journal")]
pub(super) journals: Vec<Arc<DynJournal>>,
#[cfg(feature = "ctrlc")]
pub(super) attach_ctrl_c: bool,
}
impl std::fmt::Debug for WasiEnvBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WasiEnvBuilder")
.field("entry_function", &self.entry_function)
.field("args", &self.args)
.field("envs", &self.envs)
.field("preopens", &self.preopens)
.field("uses", &self.uses)
.field("setup_fs_fn exists", &self.setup_fs_fn.is_some())
.field("stdout_override exists", &self.stdout.is_some())
.field("stderr_override exists", &self.stderr.is_some())
.field("stdin_override exists", &self.stdin.is_some())
.field("runtime_override_exists", &self.runtime.is_some())
.finish()
}
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum WasiStateCreationError {
#[error("bad environment variable format: `{0}`")]
EnvironmentVariableFormatError(String),
#[error("argument contains null byte: `{0}`")]
ArgumentContainsNulByte(String),
#[error("preopened directory not found: `{0}`")]
PreopenedDirectoryNotFound(PathBuf),
#[error("preopened directory error: `{0}`")]
PreopenedDirectoryError(String),
#[error("mapped dir alias has wrong format: `{0}`")]
MappedDirAliasFormattingError(String),
#[error("wasi filesystem creation error: `{0}`")]
WasiFsCreationError(String),
#[error("wasi filesystem setup error: `{0}`")]
WasiFsSetupError(String),
#[error(transparent)]
FileSystemError(#[from] FsError),
#[error("wasi inherit error: `{0}`")]
WasiInheritError(String),
#[error("wasi include package: `{0}`")]
WasiIncludePackageError(String),
#[error("control plane error")]
ControlPlane(#[from] ControlPlaneError),
}
fn validate_mapped_dir_alias(alias: &str) -> Result<(), WasiStateCreationError> {
if !alias.bytes().all(|b| b != b'\0') {
return Err(WasiStateCreationError::MappedDirAliasFormattingError(
format!("Alias \"{}\" contains a nul byte", alias),
));
}
Ok(())
}
pub type SetupFsFn = Box<dyn Fn(&WasiInodes, &mut WasiFs) -> Result<(), String> + Send>;
impl WasiEnvBuilder {
pub fn new(program_name: impl Into<String>) -> Self {
WasiEnvBuilder {
args: vec![program_name.into()],
..WasiEnvBuilder::default()
}
}
pub fn env<Key, Value>(mut self, key: Key, value: Value) -> Self
where
Key: AsRef<[u8]>,
Value: AsRef<[u8]>,
{
self.add_env(key, value);
self
}
#[cfg(feature = "ctrlc")]
pub fn attach_ctrl_c(mut self) -> Self {
self.attach_ctrl_c = true;
self
}
pub fn add_env<Key, Value>(&mut self, key: Key, value: Value)
where
Key: AsRef<[u8]>,
Value: AsRef<[u8]>,
{
self.envs.push((
String::from_utf8_lossy(key.as_ref()).to_string(),
value.as_ref().to_vec(),
));
}
pub fn envs<I, Key, Value>(mut self, env_pairs: I) -> Self
where
I: IntoIterator<Item = (Key, Value)>,
Key: AsRef<[u8]>,
Value: AsRef<[u8]>,
{
self.add_envs(env_pairs);
self
}
pub fn add_envs<I, Key, Value>(&mut self, env_pairs: I)
where
I: IntoIterator<Item = (Key, Value)>,
Key: AsRef<[u8]>,
Value: AsRef<[u8]>,
{
for (key, value) in env_pairs {
self.add_env(key, value);
}
}
pub fn get_env(&self) -> &[(String, Vec<u8>)] {
&self.envs
}
pub fn get_env_mut(&mut self) -> &mut Vec<(String, Vec<u8>)> {
&mut self.envs
}
pub fn entry_function<S>(mut self, entry_function: S) -> Self
where
S: AsRef<str>,
{
self.set_entry_function(entry_function);
self
}
pub fn set_entry_function<S>(&mut self, entry_function: S)
where
S: AsRef<str>,
{
self.entry_function = Some(entry_function.as_ref().to_owned());
}
pub fn arg<V>(mut self, arg: V) -> Self
where
V: AsRef<[u8]>,
{
self.add_arg(arg);
self
}
pub fn add_arg<V>(&mut self, arg: V)
where
V: AsRef<[u8]>,
{
self.args
.push(String::from_utf8_lossy(arg.as_ref()).to_string());
}
pub fn args<I, Arg>(mut self, args: I) -> Self
where
I: IntoIterator<Item = Arg>,
Arg: AsRef<[u8]>,
{
self.add_args(args);
self
}
pub fn add_args<I, Arg>(&mut self, args: I)
where
I: IntoIterator<Item = Arg>,
Arg: AsRef<[u8]>,
{
for arg in args {
self.add_arg(arg);
}
}
pub fn get_args(&self) -> &[String] {
&self.args
}
pub fn get_args_mut(&mut self) -> &mut Vec<String> {
&mut self.args
}
pub fn use_webc(mut self, pkg: BinaryPackage) -> Self {
self.add_webc(pkg);
self
}
pub fn set_module_hash(&mut self, hash: ModuleHash) -> &mut Self {
self.module_hash.replace(hash);
self
}
pub fn add_webc(&mut self, pkg: BinaryPackage) -> &mut Self {
self.uses.push(pkg);
self
}
pub fn include_package(&mut self, pkg_id: PackageId) -> &mut Self {
self.included_packages.insert(pkg_id);
self
}
pub fn include_packages(&mut self, pkg_ids: impl IntoIterator<Item = PackageId>) -> &mut Self {
self.included_packages.extend(pkg_ids);
self
}
pub fn uses<I>(mut self, uses: I) -> Self
where
I: IntoIterator<Item = BinaryPackage>,
{
for pkg in uses {
self.add_webc(pkg);
}
self
}
pub fn map_command<Name, Target>(mut self, name: Name, target: Target) -> Self
where
Name: AsRef<str>,
Target: AsRef<str>,
{
self.add_mapped_command(name, target);
self
}
pub fn add_mapped_command<Name, Target>(&mut self, name: Name, target: Target)
where
Name: AsRef<str>,
Target: AsRef<str>,
{
let path_buf = PathBuf::from(target.as_ref().to_string());
self.map_commands
.insert(name.as_ref().to_string(), path_buf);
}
pub fn map_commands<I, Name, Target>(mut self, map_commands: I) -> Self
where
I: IntoIterator<Item = (Name, Target)>,
Name: AsRef<str>,
Target: AsRef<str>,
{
self.add_mapped_commands(map_commands);
self
}
pub fn add_mapped_commands<I, Name, Target>(&mut self, map_commands: I)
where
I: IntoIterator<Item = (Name, Target)>,
Name: AsRef<str>,
Target: AsRef<str>,
{
for (alias, target) in map_commands {
self.add_mapped_command(alias, target);
}
}
pub fn preopen_dir<P>(mut self, po_dir: P) -> Result<Self, WasiStateCreationError>
where
P: AsRef<Path>,
{
self.add_preopen_dir(po_dir)?;
Ok(self)
}
pub fn add_preopen_dir<P>(&mut self, po_dir: P) -> Result<(), WasiStateCreationError>
where
P: AsRef<Path>,
{
let mut pdb = PreopenDirBuilder::new();
let path = po_dir.as_ref();
pdb.directory(path).read(true).write(true).create(true);
let preopen = pdb.build()?;
self.preopens.push(preopen);
Ok(())
}
pub fn preopen_dirs<I, P>(mut self, dirs: I) -> Result<Self, WasiStateCreationError>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
for po_dir in dirs {
self.add_preopen_dir(po_dir)?;
}
Ok(self)
}
pub fn preopen_build<F>(mut self, inner: F) -> Result<Self, WasiStateCreationError>
where
F: Fn(&mut PreopenDirBuilder) -> &mut PreopenDirBuilder,
{
self.add_preopen_build(inner)?;
Ok(self)
}
pub fn add_preopen_build<F>(&mut self, inner: F) -> Result<(), WasiStateCreationError>
where
F: Fn(&mut PreopenDirBuilder) -> &mut PreopenDirBuilder,
{
let mut pdb = PreopenDirBuilder::new();
let po_dir = inner(&mut pdb).build()?;
self.preopens.push(po_dir);
Ok(())
}
pub fn preopen_vfs_dirs<I>(&mut self, po_dirs: I) -> Result<&mut Self, WasiStateCreationError>
where
I: IntoIterator<Item = String>,
{
for po_dir in po_dirs {
self.vfs_preopens.push(po_dir);
}
Ok(self)
}
pub fn map_dir<P>(mut self, alias: &str, po_dir: P) -> Result<Self, WasiStateCreationError>
where
P: AsRef<Path>,
{
self.add_map_dir(alias, po_dir)?;
Ok(self)
}
pub fn add_map_dir<P>(&mut self, alias: &str, po_dir: P) -> Result<(), WasiStateCreationError>
where
P: AsRef<Path>,
{
let mut pdb = PreopenDirBuilder::new();
let path = po_dir.as_ref();
pdb.directory(path)
.alias(alias)
.read(true)
.write(true)
.create(true);
let preopen = pdb.build()?;
self.preopens.push(preopen);
Ok(())
}
pub fn map_dirs<I, P>(mut self, mapped_dirs: I) -> Result<Self, WasiStateCreationError>
where
I: IntoIterator<Item = (String, P)>,
P: AsRef<Path>,
{
for (alias, dir) in mapped_dirs {
self.add_map_dir(&alias, dir)?;
}
Ok(self)
}
#[cfg(feature = "journal")]
pub fn add_journal(&mut self, journal: Arc<DynJournal>) {
self.journals.push(journal);
}
pub fn set_current_dir(&mut self, dir: impl Into<PathBuf>) {
self.current_dir = Some(dir.into());
}
pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.set_current_dir(dir);
self
}
pub fn stdout(mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) -> Self {
self.stdout = Some(new_file);
self
}
pub fn set_stdout(&mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) {
self.stdout = Some(new_file);
}
pub fn stderr(mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) -> Self {
self.set_stderr(new_file);
self
}
pub fn set_stderr(&mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) {
self.stderr = Some(new_file);
}
pub fn stdin(mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) -> Self {
self.stdin = Some(new_file);
self
}
pub fn set_stdin(&mut self, new_file: Box<dyn VirtualFile + Send + Sync + 'static>) {
self.stdin = Some(new_file);
}
pub fn fs(mut self, fs: Box<dyn virtual_fs::FileSystem + Send + Sync>) -> Self {
self.set_fs(fs);
self
}
pub fn set_fs(&mut self, fs: Box<dyn virtual_fs::FileSystem + Send + Sync>) {
self.fs = Some(WasiFsRoot::Backing(Arc::new(fs)));
}
pub fn sandbox_fs(mut self, fs: TmpFileSystem) -> Self {
self.fs = Some(WasiFsRoot::Sandbox(Arc::new(fs)));
self
}
pub fn setup_fs(mut self, setup_fs_fn: SetupFsFn) -> Self {
self.setup_fs_fn = Some(setup_fs_fn);
self
}
pub fn runtime(mut self, runtime: Arc<dyn Runtime + Send + Sync>) -> Self {
self.set_runtime(runtime);
self
}
pub fn set_runtime(&mut self, runtime: Arc<dyn Runtime + Send + Sync>) {
self.runtime = Some(runtime);
}
pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
self.set_capabilities(capabilities);
self
}
pub fn capabilities_mut(&mut self) -> &mut Capabilities {
&mut self.capabilites
}
pub fn set_capabilities(&mut self, capabilities: Capabilities) {
self.capabilites = capabilities;
}
#[cfg(feature = "journal")]
pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) {
self.snapshot_on.push(on);
}
#[cfg(feature = "journal")]
pub fn with_snapshot_interval(&mut self, interval: std::time::Duration) {
self.snapshot_interval.replace(interval);
}
pub fn import(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,
value: impl Into<Extern>,
) -> Self {
self.add_imports([((namespace, name), value)]);
self
}
pub fn add_import(
&mut self,
namespace: impl Into<String>,
name: impl Into<String>,
value: impl Into<Extern>,
) {
self.add_imports([((namespace, name), value)]);
}
pub fn add_imports<I, S1, S2, E>(&mut self, imports: I)
where
I: IntoIterator<Item = ((S1, S2), E)>,
S1: Into<String>,
S2: Into<String>,
E: Into<Extern>,
{
let imports = imports
.into_iter()
.map(|((ns, n), e)| ((ns.into(), n.into()), e.into()));
self.additional_imports.extend(imports);
}
pub fn imports<I, S1, S2, E>(mut self, imports: I) -> Self
where
I: IntoIterator<Item = ((S1, S2), E)>,
S1: Into<String>,
S2: Into<String>,
E: Into<Extern>,
{
self.add_imports(imports);
self
}
pub fn build_init(mut self) -> Result<WasiEnvInit, WasiStateCreationError> {
for arg in self.args.iter() {
for b in arg.as_bytes().iter() {
if *b == 0 {
return Err(WasiStateCreationError::ArgumentContainsNulByte(arg.clone()));
}
}
}
enum InvalidCharacter {
Nul,
Equal,
}
for (env_key, env_value) in self.envs.iter() {
match env_key.as_bytes().iter().find_map(|&ch| {
if ch == 0 {
Some(InvalidCharacter::Nul)
} else if ch == b'=' {
Some(InvalidCharacter::Equal)
} else {
None
}
}) {
Some(InvalidCharacter::Nul) => {
return Err(WasiStateCreationError::EnvironmentVariableFormatError(
format!("found nul byte in env var key \"{}\" (key=value)", env_key),
))
}
Some(InvalidCharacter::Equal) => {
return Err(WasiStateCreationError::EnvironmentVariableFormatError(
format!(
"found equal sign in env var key \"{}\" (key=value)",
env_key
),
))
}
None => (),
}
if env_value.iter().any(|&ch| ch == 0) {
return Err(WasiStateCreationError::EnvironmentVariableFormatError(
format!(
"found nul byte in env var value \"{}\" (key=value)",
String::from_utf8_lossy(env_value),
),
));
}
}
let stdin: Box<dyn VirtualFile + Send + Sync + 'static> = self
.stdin
.take()
.unwrap_or_else(|| Box::new(ArcFile::new(Box::<super::Stdin>::default())));
let fs_backing = self
.fs
.take()
.unwrap_or_else(|| WasiFsRoot::Sandbox(Arc::new(TmpFileSystem::new())));
if let Some(dir) = &self.current_dir {
match fs_backing.read_dir(dir) {
Ok(_) => {
}
Err(FsError::EntryNotFound) => {
fs_backing.create_dir(dir).map_err(|err| {
WasiStateCreationError::WasiFsSetupError(format!(
"Could not create specified current directory at '{}': {err}",
dir.display()
))
})?;
}
Err(err) => {
return Err(WasiStateCreationError::WasiFsSetupError(format!(
"Could check specified current directory at '{}': {err}",
dir.display()
)));
}
}
}
let inodes = crate::state::WasiInodes::new();
let wasi_fs = {
let mut wasi_fs =
WasiFs::new_with_preopen(&inodes, &self.preopens, &self.vfs_preopens, fs_backing)
.map_err(WasiStateCreationError::WasiFsCreationError)?;
wasi_fs
.swap_file(__WASI_STDIN_FILENO, stdin)
.map_err(WasiStateCreationError::FileSystemError)?;
if let Some(stdout_override) = self.stdout.take() {
wasi_fs
.swap_file(__WASI_STDOUT_FILENO, stdout_override)
.map_err(WasiStateCreationError::FileSystemError)?;
}
if let Some(stderr_override) = self.stderr.take() {
wasi_fs
.swap_file(__WASI_STDERR_FILENO, stderr_override)
.map_err(WasiStateCreationError::FileSystemError)?;
}
if let Some(f) = &self.setup_fs_fn {
f(&inodes, &mut wasi_fs).map_err(WasiStateCreationError::WasiFsSetupError)?;
}
wasi_fs
};
if let Some(dir) = &self.current_dir {
let s = dir.to_str().ok_or_else(|| {
WasiStateCreationError::WasiFsSetupError(format!(
"Specified current directory is not valid UTF-8: '{}'",
dir.display()
))
})?;
wasi_fs.set_current_dir(s);
}
for id in &self.included_packages {
wasi_fs.has_unioned.lock().unwrap().insert(id.clone());
}
let state = WasiState {
fs: wasi_fs,
secret: rand::thread_rng().gen::<[u8; 32]>(),
inodes,
args: std::sync::Mutex::new(self.args.clone()),
preopen: self.vfs_preopens.clone(),
futexs: Default::default(),
clock_offset: Default::default(),
envs: std::sync::Mutex::new(conv_env_vars(self.envs)),
};
let runtime = self.runtime.unwrap_or_else(|| {
#[cfg(feature = "sys-thread")]
{
#[allow(unused_mut)]
let mut runtime = crate::runtime::PluggableRuntime::new(Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::default()));
#[cfg(feature = "journal")]
for journal in self.journals.clone() {
runtime.add_journal(journal);
}
Arc::new(runtime)
}
#[cfg(not(feature = "sys-thread"))]
{
panic!("this build does not support a default runtime - specify one with WasiEnvBuilder::runtime()");
}
});
let uses = self.uses;
let map_commands = self.map_commands;
let bin_factory = BinFactory::new(runtime.clone());
let capabilities = self.capabilites;
let plane_config = ControlPlaneConfig {
max_task_count: capabilities.threading.max_threads,
enable_asynchronous_threading: capabilities.threading.enable_asynchronous_threading,
enable_exponential_cpu_backoff: capabilities.threading.enable_exponential_cpu_backoff,
};
let control_plane = WasiControlPlane::new(plane_config);
let init = WasiEnvInit {
state,
runtime,
webc_dependencies: uses,
mapped_commands: map_commands,
control_plane,
bin_factory,
capabilities,
memory_ty: None,
process: None,
thread: None,
#[cfg(feature = "journal")]
call_initialize: self.journals.is_empty(),
#[cfg(not(feature = "journal"))]
call_initialize: true,
can_deep_sleep: false,
extra_tracing: true,
#[cfg(feature = "journal")]
snapshot_on: self.snapshot_on,
additional_imports: self.additional_imports,
};
Ok(init)
}
#[allow(clippy::result_large_err)]
pub fn build(self) -> Result<WasiEnv, WasiRuntimeError> {
let module_hash = self.module_hash.unwrap_or_else(xxhash_random);
let init = self.build_init()?;
WasiEnv::from_init(init, module_hash)
}
#[doc(hidden)]
#[allow(clippy::result_large_err)]
pub fn finalize(
self,
store: &mut impl AsStoreMut,
) -> Result<WasiFunctionEnv, WasiRuntimeError> {
let module_hash = self.module_hash.unwrap_or_else(xxhash_random);
let init = self.build_init()?;
let env = WasiEnv::from_init(init, module_hash)?;
let func_env = WasiFunctionEnv::new(store, env);
Ok(func_env)
}
#[allow(clippy::result_large_err)]
pub fn instantiate(
self,
module: Module,
store: &mut impl AsStoreMut,
) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> {
self.instantiate_ext(module, xxhash_random(), store)
}
#[allow(clippy::result_large_err)]
pub fn instantiate_ext(
self,
module: Module,
module_hash: ModuleHash,
store: &mut impl AsStoreMut,
) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> {
let init = self.build_init()?;
WasiEnv::instantiate(init, module, module_hash, store)
}
#[allow(clippy::result_large_err)]
pub fn run(self, module: Module) -> Result<(), WasiRuntimeError> {
self.run_ext(module, xxhash_random())
}
#[allow(clippy::result_large_err)]
pub fn run_ext(self, module: Module, module_hash: ModuleHash) -> Result<(), WasiRuntimeError> {
let mut store = wasmer::Store::default();
self.run_with_store_ext(module, module_hash, &mut store)
}
#[allow(clippy::result_large_err)]
#[tracing::instrument(level = "debug", skip_all)]
pub fn run_with_store(self, module: Module, store: &mut Store) -> Result<(), WasiRuntimeError> {
self.run_with_store_ext(module, xxhash_random(), store)
}
#[allow(clippy::result_large_err)]
pub fn run_with_store_ext(
self,
module: Module,
module_hash: ModuleHash,
store: &mut Store,
) -> Result<(), WasiRuntimeError> {
#[cfg(feature = "sys-thread")]
let _guard = if tokio::runtime::Handle::try_current().is_err() {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
Some(runtime)
} else {
None
};
#[cfg(feature = "sys-thread")]
let _guard = _guard.as_ref().map(|r| r.enter());
if self.capabilites.threading.enable_asynchronous_threading {
tracing::warn!(
"The enable_asynchronous_threading capability is enabled. Use WasiEnvBuilder::run_with_store_async() to avoid spurious errors.",
);
}
let entry_function = self.entry_function.clone();
let (instance, env) = self.instantiate_ext(module, module_hash, store)?;
let rewind_state = unsafe { env.bootstrap(store)? };
if rewind_state.is_some() {
let mut ctx = env.env.clone().into_mut(store);
rewind_ext2(&mut ctx, rewind_state)
.map_err(|exit| WasiRuntimeError::Wasi(WasiError::Exit(exit)))?;
}
let start = instance
.exports
.get_function(entry_function.as_deref().unwrap_or("_start"))?;
env.data(&store).thread.set_status_running();
let result = crate::run_wasi_func_start(start, store);
let (result, exit_code) = super::wasi_exit_code(result);
let pid = env.data(&store).pid();
let tid = env.data(&store).tid();
tracing::trace!(
%pid,
%tid,
%exit_code,
error=result.as_ref().err().map(|e| e as &dyn std::error::Error),
"main exit",
);
env.on_exit(store, Some(exit_code));
result
}
#[allow(clippy::result_large_err)]
#[tracing::instrument(level = "debug", skip_all)]
pub fn run_with_store_async(
self,
module: Module,
module_hash: ModuleHash,
mut store: Store,
) -> Result<(), WasiRuntimeError> {
#[cfg(feature = "ctrlc")]
let attach_ctrl_c = self.attach_ctrl_c;
let (_, env) = self.instantiate_ext(module, module_hash, &mut store)?;
#[cfg(feature = "ctrlc")]
if attach_ctrl_c {
tokio::spawn({
let process = env.data(&store).process.clone();
async move {
while tokio::signal::ctrl_c().await.is_ok() {
process.signal_process(wasmer_wasix_types::wasi::Signal::Sigint);
}
}
});
}
env.run_async(store)?;
Ok(())
}
}
pub(crate) fn conv_env_vars(envs: Vec<(String, Vec<u8>)>) -> Vec<Vec<u8>> {
envs.into_iter()
.map(|(key, value)| {
let mut env = Vec::with_capacity(key.len() + value.len() + 1);
env.extend_from_slice(key.as_bytes());
env.push(b'=');
env.extend_from_slice(&value);
env
})
.collect()
}
#[derive(Debug, Default)]
pub struct PreopenDirBuilder {
path: Option<PathBuf>,
alias: Option<String>,
read: bool,
write: bool,
create: bool,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct PreopenedDir {
pub(crate) path: PathBuf,
pub(crate) alias: Option<String>,
pub(crate) read: bool,
pub(crate) write: bool,
pub(crate) create: bool,
}
impl PreopenDirBuilder {
pub(crate) fn new() -> Self {
PreopenDirBuilder::default()
}
pub fn directory<FilePath>(&mut self, po_dir: FilePath) -> &mut Self
where
FilePath: AsRef<Path>,
{
let path = po_dir.as_ref();
self.path = Some(path.to_path_buf());
self
}
pub fn alias(&mut self, alias: &str) -> &mut Self {
let alias = alias.trim_start_matches('/');
self.alias = Some(alias.to_string());
self
}
pub fn read(&mut self, toggle: bool) -> &mut Self {
self.read = toggle;
self
}
pub fn write(&mut self, toggle: bool) -> &mut Self {
self.write = toggle;
self
}
pub fn create(&mut self, toggle: bool) -> &mut Self {
self.create = toggle;
if toggle {
self.write = true;
}
self
}
pub(crate) fn build(&self) -> Result<PreopenedDir, WasiStateCreationError> {
if !(self.read || self.write || self.create) {
return Err(WasiStateCreationError::PreopenedDirectoryError("Preopened directories must have at least one of read, write, create permissions set".to_string()));
}
if self.path.is_none() {
return Err(WasiStateCreationError::PreopenedDirectoryError(
"Preopened directories must point to a host directory".to_string(),
));
}
let path = self.path.clone().unwrap();
if let Some(alias) = &self.alias {
validate_mapped_dir_alias(alias)?;
}
Ok(PreopenedDir {
path,
alias: self.alias.clone(),
read: self.read,
write: self.write,
create: self.create,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn env_var_errors() {
#[cfg(not(target_arch = "wasm32"))]
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
#[cfg(not(target_arch = "wasm32"))]
let handle = runtime.handle().clone();
#[cfg(not(target_arch = "wasm32"))]
let _guard = handle.enter();
assert!(
WasiEnv::builder("test_prog")
.env("HOM=E", "/home/home")
.build_init()
.is_err(),
"equal sign in key must be invalid"
);
assert!(
WasiEnvBuilder::new("test_prog")
.env("HOME\0", "/home/home")
.build_init()
.is_err(),
"nul in key must be invalid"
);
assert!(
WasiEnvBuilder::new("test_prog")
.env("HOME", "/home/home=home")
.build_init()
.is_ok(),
"equal sign in the value must be valid"
);
assert!(
WasiEnvBuilder::new("test_prog")
.env("HOME", "/home/home\0")
.build_init()
.is_err(),
"nul in value must be invalid"
);
}
#[test]
fn nul_character_in_args() {
let output = WasiEnvBuilder::new("test_prog")
.arg("--h\0elp")
.build_init();
let err = output.expect_err("should fail");
assert!(matches!(
err,
WasiStateCreationError::ArgumentContainsNulByte(_)
));
let output = WasiEnvBuilder::new("test_prog")
.args(["--help", "--wat\0"])
.build_init();
let err = output.expect_err("should fail");
assert!(matches!(
err,
WasiStateCreationError::ArgumentContainsNulByte(_)
));
}
}