diff --git a/lib/cli/src/commands/run/mod.rs b/lib/cli/src/commands/run/mod.rs index 41ab50db1fc..edf7d615667 100644 --- a/lib/cli/src/commands/run/mod.rs +++ b/lib/cli/src/commands/run/mod.rs @@ -359,7 +359,9 @@ impl Run { ) -> Result { let packages = self.load_injected_packages(runtime)?; - let mut runner = WasiRunner::new() + let mut runner = WasiRunner::new(); + + runner .with_args(&self.args) .with_injected_packages(packages) .with_envs(self.wasi.env_vars.clone()) @@ -371,10 +373,10 @@ impl Run { #[cfg(feature = "journal")] { for trigger in self.wasi.snapshot_on.iter().cloned() { - runner.add_snapshot_trigger(trigger); + runner.with_snapshot_trigger(trigger); } if self.wasi.snapshot_on.is_empty() && !self.wasi.journals.is_empty() { - runner.add_default_snapshot_triggers(); + runner.with_default_snapshot_triggers(); } if let Some(period) = self.wasi.snapshot_interval { if self.wasi.journals.is_empty() { @@ -385,7 +387,7 @@ impl Run { runner.with_snapshot_interval(Duration::from_millis(period)); } for journal in self.wasi.build_journals()? { - runner.add_journal(journal); + runner.with_journal(journal); } } diff --git a/lib/wasix/src/runners/wasi.rs b/lib/wasix/src/runners/wasi.rs index d21bfaee71f..1493feb3d5f 100644 --- a/lib/wasix/src/runners/wasi.rs +++ b/lib/wasix/src/runners/wasi.rs @@ -5,7 +5,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{Context, Error}; use tracing::Instrument; use virtual_fs::{ArcBoxFile, FileSystem, TmpFileSystem, VirtualFile}; -use wasmer::Module; +use wasmer::{Extern, Module}; use webc::metadata::{annotations::Wasi, Command}; use crate::{ @@ -39,46 +39,22 @@ impl WasiRunner { } /// Builder method to provide CLI args to the runner - pub fn with_args(mut self, args: A) -> Self - where - A: IntoIterator, - S: Into, - { - self.set_args(args); - self - } - - /// Set the CLI args - pub fn set_args(&mut self, args: A) + pub fn with_args(&mut self, args: A) -> &mut Self where A: IntoIterator, S: Into, { self.wasi.args = args.into_iter().map(|s| s.into()).collect(); + self } /// Builder method to provide environment variables to the runner. pub fn with_env(mut self, key: impl Into, value: impl Into) -> Self { - self.set_env(key, value); - self - } - - /// Provide environment variables to the runner. - pub fn set_env(&mut self, key: impl Into, value: impl Into) { self.wasi.env.insert(key.into(), value.into()); - } - - pub fn with_envs(mut self, envs: I) -> Self - where - I: IntoIterator, - K: Into, - V: Into, - { - self.set_envs(envs); self } - pub fn set_envs(&mut self, envs: I) + pub fn with_envs(&mut self, envs: I) -> &mut Self where I: IntoIterator, K: Into, @@ -87,18 +63,15 @@ impl WasiRunner { for (key, value) in envs { self.wasi.env.insert(key.into(), value.into()); } - } - - pub fn with_forward_host_env(mut self, forward: bool) -> Self { - self.set_forward_host_env(forward); self } - pub fn set_forward_host_env(&mut self, forward: bool) { + pub fn with_forward_host_env(&mut self, forward: bool) -> &mut Self { self.wasi.forward_host_env = forward; + self } - pub fn with_mapped_directories(self, dirs: I) -> Self + pub fn with_mapped_directories(&mut self, dirs: I) -> &mut Self where I: IntoIterator, D: Into, @@ -106,7 +79,7 @@ impl WasiRunner { self.with_mounted_directories(dirs.into_iter().map(Into::into).map(MountedDirectory::from)) } - pub fn with_mounted_directories(mut self, dirs: I) -> Self + pub fn with_mounted_directories(&mut self, dirs: I) -> &mut Self where I: IntoIterator, D: Into, @@ -115,34 +88,26 @@ impl WasiRunner { self } - pub fn mount(&mut self, dest: String, fs: Arc) -> &mut Self { + /// Mount a [`FileSystem`] instance at a particular location. + pub fn with_mount(&mut self, dest: String, fs: Arc) -> &mut Self { self.wasi.mounts.push(MountedDirectory { guest: dest, fs }); self } - pub fn set_current_dir(&mut self, dir: impl Into) { + /// Override the directory the WASIX instance will start in. + pub fn with_current_dir(&mut self, dir: impl Into) -> &mut Self { self.wasi.current_dir = Some(dir.into()); - } - - pub fn with_current_dir(mut self, dir: impl Into) -> Self { - self.set_current_dir(dir); self } /// Add a package that should be available to the instance at runtime. - pub fn add_injected_package(&mut self, pkg: BinaryPackage) -> &mut Self { + pub fn with_injected_package(&mut self, pkg: BinaryPackage) -> &mut Self { self.wasi.injected_packages.push(pkg); self } - /// Add a package that should be available to the instance at runtime. - pub fn with_injected_package(mut self, pkg: BinaryPackage) -> Self { - self.add_injected_package(pkg); - self - } - /// Add packages that should be available to the instance at runtime. - pub fn add_injected_packages( + pub fn with_injected_packages( &mut self, packages: impl IntoIterator, ) -> &mut Self { @@ -150,40 +115,23 @@ impl WasiRunner { self } - /// Add packages that should be available to the instance at runtime. - pub fn with_injected_packages( - mut self, - packages: impl IntoIterator, - ) -> Self { - self.add_injected_packages(packages); - self - } - - pub fn add_mapped_host_command(&mut self, alias: impl Into, target: impl Into) { + pub fn with_mapped_host_command( + &mut self, + alias: impl Into, + target: impl Into, + ) -> &mut Self { self.wasi.mapped_host_commands.push(MappedCommand { alias: alias.into(), target: target.into(), }); - } - - pub fn with_mapped_host_command( - mut self, - alias: impl Into, - target: impl Into, - ) -> Self { - self.add_mapped_host_command(alias, target); self } - pub fn add_mapped_host_commands(&mut self, commands: impl IntoIterator) { - self.wasi.mapped_host_commands.extend(commands); - } - pub fn with_mapped_host_commands( - mut self, + &mut self, commands: impl IntoIterator, - ) -> Self { - self.add_mapped_host_commands(commands); + ) -> &mut Self { + self.wasi.mapped_host_commands.extend(commands); self } @@ -191,24 +139,20 @@ impl WasiRunner { &mut self.wasi.capabilities } - pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self { - self.set_capabilities(capabilities); - self - } - - pub fn set_capabilities(&mut self, capabilities: Capabilities) { + pub fn with_capabilities(&mut self, capabilities: Capabilities) -> &mut Self { self.wasi.capabilities = capabilities; + self } - pub fn add_snapshot_trigger(&mut self, on: SnapshotTrigger) -> &mut Self { + pub fn with_snapshot_trigger(&mut self, on: SnapshotTrigger) -> &mut Self { self.wasi.snapshot_on.push(on); self } - pub fn add_default_snapshot_triggers(&mut self) -> &mut Self { + pub fn with_default_snapshot_triggers(&mut self) -> &mut Self { for on in crate::journal::DEFAULT_SNAPSHOT_TRIGGERS { if !self.has_snapshot_trigger(on) { - self.add_snapshot_trigger(on); + self.with_snapshot_trigger(on); } } self @@ -220,44 +164,56 @@ impl WasiRunner { pub fn with_snapshot_interval(&mut self, period: std::time::Duration) -> &mut Self { if !self.has_snapshot_trigger(SnapshotTrigger::PeriodicInterval) { - self.add_snapshot_trigger(SnapshotTrigger::PeriodicInterval); + self.with_snapshot_trigger(SnapshotTrigger::PeriodicInterval); } self.wasi.snapshot_interval.replace(period); self } - pub fn add_journal(&mut self, journal: Arc) -> &mut Self { + pub fn with_journal(&mut self, journal: Arc) -> &mut Self { self.wasi.journals.push(journal); self } - pub fn with_stdin(mut self, stdin: Box) -> Self { - self.set_stdin(stdin); - self - } - - pub fn set_stdin(&mut self, stdin: Box) -> &mut Self { + pub fn with_stdin(&mut self, stdin: Box) -> &mut Self { self.stdin = Some(ArcBoxFile::new(stdin)); self } - pub fn with_stdout(mut self, stdout: Box) -> Self { - self.set_stdout(stdout); + pub fn with_stdout(&mut self, stdout: Box) -> &mut Self { + self.stdout = Some(ArcBoxFile::new(stdout)); self } - pub fn set_stdout(&mut self, stdout: Box) -> &mut Self { - self.stdout = Some(ArcBoxFile::new(stdout)); + pub fn with_stderr(&mut self, stderr: Box) -> &mut Self { + self.stderr = Some(ArcBoxFile::new(stderr)); self } - pub fn with_stderr(mut self, stderr: Box) -> Self { - self.set_stderr(stderr); - self + /// Add an item to the list of importable items provided to the instance. + pub fn with_import( + &mut self, + namespace: impl Into, + name: impl Into, + value: impl Into, + ) -> &mut Self { + self.with_imports([((namespace, name), value)]) } - pub fn set_stderr(&mut self, stderr: Box) -> &mut Self { - self.stderr = Some(ArcBoxFile::new(stderr)); + /// Add multiple import functions. + /// + /// This method will accept a [`&Imports`][wasmer::Imports] object. + pub fn with_imports(&mut self, imports: I) -> &mut Self + where + I: IntoIterator, + S1: Into, + S2: Into, + E: Into, + { + let imports = imports + .into_iter() + .map(|((ns, n), e)| ((ns.into(), n.into()), e.into())); + self.wasi.additional_imports.extend(imports); self } diff --git a/lib/wasix/src/runners/wasi_common.rs b/lib/wasix/src/runners/wasi_common.rs index 6fddde6a6d3..f2b2bb9bf36 100644 --- a/lib/wasix/src/runners/wasi_common.rs +++ b/lib/wasix/src/runners/wasi_common.rs @@ -8,6 +8,7 @@ use anyhow::{Context, Error}; use derivative::Derivative; use futures::future::BoxFuture; use virtual_fs::{FileSystem, FsError, OverlayFileSystem, RootFileSystemBuilder, TmpFileSystem}; +use wasmer::Imports; use webc::metadata::annotations::Wasi as WasiAnnotation; use crate::{ @@ -40,6 +41,7 @@ pub(crate) struct CommonWasiOptions { pub(crate) snapshot_on: Vec, pub(crate) snapshot_interval: Option, pub(crate) current_dir: Option, + pub(crate) additional_imports: Imports, } impl CommonWasiOptions { @@ -77,6 +79,8 @@ impl CommonWasiOptions { *builder.capabilities_mut() = self.capabilities.clone(); + builder.add_imports(&self.additional_imports); + Ok(()) } diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs index 3b382536f4c..ac73560ad30 100644 --- a/lib/wasix/src/state/builder.rs +++ b/lib/wasix/src/state/builder.rs @@ -9,7 +9,7 @@ use std::{ use rand::Rng; use thiserror::Error; use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile}; -use wasmer::{AsStoreMut, Instance, Module, Store}; +use wasmer::{AsStoreMut, Extern, Imports, Instance, Module, Store}; #[cfg(feature = "journal")] use crate::journal::{DynJournal, SnapshotTrigger}; @@ -74,6 +74,7 @@ pub struct WasiEnvBuilder { pub(super) map_commands: HashMap, pub(super) capabilites: Capabilities, + pub(super) additional_imports: Imports, #[cfg(feature = "journal")] pub(super) snapshot_on: Vec, @@ -642,6 +643,51 @@ impl WasiEnvBuilder { self.snapshot_interval.replace(interval); } + /// Add an item to the list of importable items provided to the instance. + pub fn import( + mut self, + namespace: impl Into, + name: impl Into, + value: impl Into, + ) -> Self { + self.add_imports([((namespace, name), value)]); + self + } + + /// Add an item to the list of importable items provided to the instance. + pub fn add_import( + &mut self, + namespace: impl Into, + name: impl Into, + value: impl Into, + ) { + self.add_imports([((namespace, name), value)]); + } + + pub fn add_imports(&mut self, imports: I) + where + I: IntoIterator, + S1: Into, + S2: Into, + E: Into, + { + let imports = imports + .into_iter() + .map(|((ns, n), e)| ((ns.into(), n.into()), e.into())); + self.additional_imports.extend(imports); + } + + pub fn imports(mut self, imports: I) -> Self + where + I: IntoIterator, + S1: Into, + S2: Into, + E: Into, + { + self.add_imports(imports); + self + } + /// Consumes the [`WasiEnvBuilder`] and produces a [`WasiEnvInit`], which /// can be used to construct a new [`WasiEnv`]. /// @@ -844,6 +890,7 @@ impl WasiEnvBuilder { extra_tracing: true, #[cfg(feature = "journal")] snapshot_on: self.snapshot_on, + additional_imports: self.additional_imports, }; Ok(init) diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 71c331dbf43..4101f354699 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -14,8 +14,8 @@ use rand::Rng; use virtual_fs::{FileSystem, FsError, StaticFile, VirtualFile}; use virtual_net::DynVirtualNetworking; use wasmer::{ - AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Instance, Memory, MemoryType, MemoryView, - Module, TypedFunction, + AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Imports, Instance, Memory, MemoryType, + MemoryView, Module, TypedFunction, }; use wasmer_wasix_types::{ types::Signal, @@ -237,6 +237,10 @@ pub struct WasiEnvInit { /// Indicates if extra tracing should be output pub extra_tracing: bool, + /// Additional functionality provided to the WASIX instance, besides the + /// normal WASIX syscalls. + pub additional_imports: Imports, + /// Indicates triggers that will cause a snapshot to be taken #[cfg(feature = "journal")] pub snapshot_on: Vec, @@ -278,6 +282,7 @@ impl WasiEnvInit { extra_tracing: false, #[cfg(feature = "journal")] snapshot_on: self.snapshot_on.clone(), + additional_imports: self.additional_imports.clone(), } } } @@ -556,6 +561,8 @@ impl WasiEnv { } } + let additional_imports = init.additional_imports.clone(); + let env = Self::from_init(init, module_hash)?; let pid = env.process.pid(); @@ -582,6 +589,14 @@ impl WasiEnv { let (mut import_object, instance_init_callback) = import_object_for_all_wasi_versions(&module, &mut store, &func_env.env); + for ((namespace, name), value) in &additional_imports { + // Note: We don't want to let downstream users override WASIX + // syscalls + if !import_object.exists(&namespace, &name) { + import_object.define(&namespace, &name, value); + } + } + let imported_memory = if let Some(memory) = memory { import_object.define("env", "memory", memory.clone()); Some(memory)