diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands/mod.rs similarity index 100% rename from lib/cli/src/commands.rs rename to lib/cli/src/commands/mod.rs diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run/mod.rs similarity index 96% rename from lib/cli/src/commands/run.rs rename to lib/cli/src/commands/run/mod.rs index 6e26340a30f..c7af2cc2c97 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run/mod.rs @@ -30,7 +30,7 @@ use wasmer_compiler::ArtifactBuild; use wasmer_registry::{wasmer_env::WasmerEnv, Package}; use wasmer_wasix::{ bin_factory::BinaryPackage, - runners::{MappedDirectory, Runner}, + runners::{MappedCommand, MappedDirectory, Runner}, runtime::{ module_cache::{CacheError, ModuleHash}, package_loader::PackageLoader, @@ -215,17 +215,7 @@ impl Run { uses: Vec, runtime: Arc, ) -> Result<(), Error> { - let mut runner = wasmer_wasix::runners::wasi::WasiRunner::new() - .with_args(self.args.clone()) - .with_envs(self.wasi.env_vars.clone()) - .with_mapped_directories(self.wasi.mapped_dirs.clone()) - .with_injected_packages(uses); - if self.wasi.forward_host_env { - runner.set_forward_host_env(); - } - - *runner.capabilities() = self.wasi.capabilities(); - + let mut runner = self.build_wasi_runner(&runtime)?; runner.run_command(command_name, pkg, runtime) } @@ -298,23 +288,41 @@ impl Run { Ok(()) } + fn build_wasi_runner( + &self, + runtime: &Arc, + ) -> Result { + let packages = self.load_injected_packages(runtime)?; + + let runner = WasiRunner::new() + .with_args(&self.args) + .with_injected_packages(packages) + .with_envs(self.wasi.env_vars.clone()) + .with_mapped_host_commands(self.wasi.build_mapped_commands()?) + .with_mapped_directories(self.wasi.build_mapped_directories()?) + .with_forward_host_env(self.wasi.forward_host_env) + .with_capabilities(self.wasi.capabilities()); + + Ok(runner) + } + #[tracing::instrument(skip_all)] fn execute_wasi_module( &self, wasm_path: &Path, module: &Module, runtime: Arc, - store: Store, + mut store: Store, ) -> Result<(), Error> { let program_name = wasm_path.display().to_string(); - let builder = self - .wasi - .prepare(module, program_name, self.args.clone(), runtime)?; - - builder.run_with_store_async(module.clone(), store)?; - - Ok(()) + let runner = self.build_wasi_runner(&runtime)?; + runner.run_wasm( + runtime, + &program_name, + module, + self.wasi.enable_async_threads, + ) } #[tracing::instrument(skip_all)] diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index ada07096471..a2862c60b70 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -20,7 +20,7 @@ use wasmer_wasix::{ http::HttpClient, os::{tty_sys::SysTty, TtyBridge}, rewind_ext, - runners::MappedDirectory, + runners::{MappedCommand, MappedDirectory}, runtime::{ module_cache::{FileSystemCache, ModuleCache}, package_loader::{BuiltinPackageLoader, PackageLoader}, @@ -77,11 +77,11 @@ pub struct Wasi { /// List of webc packages that are explicitly included for execution /// Note: these packages will be used instead of those in the registry #[clap(long = "include-webc", name = "WEBC")] - include_webcs: Vec, + pub(super) include_webcs: Vec, /// List of injected atoms #[clap(long = "map-command", name = "MAPCMD")] - map_commands: Vec, + pub(super) map_commands: Vec, /// Enable experimental IO devices #[cfg(feature = "experimental-io-devices")] @@ -125,6 +125,8 @@ pub struct RunProperties { #[allow(dead_code)] impl Wasi { + const MAPPED_CURRENT_DIR_DEFAULT_PATH: &'static str = "/mnt/host"; + pub fn map_dir(&mut self, alias: &str, target_on_disk: PathBuf) { self.mapped_dirs.push(MappedDirectory { guest: alias.to_string(), @@ -190,12 +192,93 @@ impl Wasi { .uses(uses) .map_commands(map_commands); - let mut builder = if wasmer_wasix::is_wasix_module(module) { + let mut builder = { // If we preopen anything from the host then shallow copy it over let root_fs = RootFileSystemBuilder::new() .with_tty(Box::new(DeviceFile::new(__WASI_STDIN_FILENO))) .build(); - if !self.mapped_dirs.is_empty() { + + let mut mapped_dirs = Vec::new(); + + // Process the --dirs flag and merge it with --mapdir. + let mut have_current_dir = false; + for dir in &self.pre_opened_directories { + let mapping = if dir == Path::new(".") { + if have_current_dir { + bail!("Cannot pre-open the current directory twice: --dir=. must only be specified once"); + } + have_current_dir = true; + + let current_dir = + std::env::current_dir().context("could not determine current directory")?; + + MappedDirectory { + host: current_dir, + guest: Self::MAPPED_CURRENT_DIR_DEFAULT_PATH.to_string(), + } + } else { + let resolved = dir.canonicalize().with_context(|| { + format!( + "could not canonicalize path for argument '--dir {}'", + dir.display() + ) + })?; + + if &resolved != dir { + bail!( + "Invalid argument '--dir {}': path must either be absolute, or '.'", + dir.display(), + ); + } + + let guest = resolved + .to_str() + .with_context(|| { + format!( + "invalid argument '--dir {}': path must be valid utf-8", + dir.display(), + ) + })? + .to_string(); + + MappedDirectory { + host: resolved, + guest, + } + }; + + mapped_dirs.push(mapping); + } + + for MappedDirectory { host, guest } in &self.mapped_dirs { + let resolved_host = host.canonicalize().with_context(|| { + format!( + "could not canonicalize path for argument '--mapdir {}:{}'", + host.display(), + guest, + ) + })?; + + let mapping = if guest == "." { + if have_current_dir { + bail!("Cannot pre-open the current directory twice: '--mapdir=?:.' / '--dir=.' must only be specified once"); + } + have_current_dir = true; + + MappedDirectory { + host: resolved_host, + guest: Self::MAPPED_CURRENT_DIR_DEFAULT_PATH.to_string(), + } + } else { + MappedDirectory { + host: resolved_host, + guest: guest.clone(), + } + }; + mapped_dirs.push(mapping); + } + + if !mapped_dirs.is_empty() { let fs_backing: Arc = Arc::new(PassthruFileSystem::new(default_fs_backing())); for MappedDirectory { host, guest } in self.mapped_dirs.clone() { @@ -209,20 +292,16 @@ impl Wasi { } // Open the root of the new filesystem - builder + let b = builder .sandbox_fs(root_fs) .preopen_dir(Path::new("/")) - .unwrap() - .map_dir(".", "/")? - } else { - builder - .fs(default_fs_backing()) - .preopen_dirs(self.pre_opened_directories.clone())? - .map_dirs( - self.mapped_dirs - .iter() - .map(|d| (d.guest.clone(), d.host.clone())), - )? + .unwrap(); + + if have_current_dir { + b.map_dir(".", Self::MAPPED_CURRENT_DIR_DEFAULT_PATH)? + } else { + b.map_dir(".", "/")? + } }; *builder.capabilities_mut() = self.capabilities(); @@ -238,6 +317,119 @@ impl Wasi { Ok(builder) } + pub fn build_mapped_directories(&self) -> Result, anyhow::Error> { + let mut mapped_dirs = Vec::new(); + + // Process the --dirs flag and merge it with --mapdir. + let mut have_current_dir = false; + for dir in &self.pre_opened_directories { + let mapping = if dir == Path::new(".") { + if have_current_dir { + bail!("Cannot pre-open the current directory twice: --dir=. must only be specified once"); + } + have_current_dir = true; + + let current_dir = + std::env::current_dir().context("could not determine current directory")?; + + MappedDirectory { + host: current_dir, + guest: Self::MAPPED_CURRENT_DIR_DEFAULT_PATH.to_string(), + } + } else { + let resolved = dir.canonicalize().with_context(|| { + format!( + "could not canonicalize path for argument '--dir {}'", + dir.display() + ) + })?; + + if &resolved != dir { + bail!( + "Invalid argument '--dir {}': path must either be absolute, or '.'", + dir.display(), + ); + } + + let guest = resolved + .to_str() + .with_context(|| { + format!( + "invalid argument '--dir {}': path must be valid utf-8", + dir.display(), + ) + })? + .to_string(); + + MappedDirectory { + host: resolved, + guest, + } + }; + + mapped_dirs.push(mapping); + } + + for MappedDirectory { host, guest } in &self.mapped_dirs { + let resolved_host = host.canonicalize().with_context(|| { + format!( + "could not canonicalize path for argument '--mapdir {}:{}'", + host.display(), + guest, + ) + })?; + + let mapping = if guest == "." { + if have_current_dir { + bail!("Cannot pre-open the current directory twice: '--mapdir=?:.' / '--dir=.' must only be specified once"); + } + have_current_dir = true; + + MappedDirectory { + host: resolved_host, + guest: Self::MAPPED_CURRENT_DIR_DEFAULT_PATH.to_string(), + } + } else { + MappedDirectory { + host: resolved_host, + guest: guest.clone(), + } + }; + mapped_dirs.push(mapping); + } + + Ok(mapped_dirs) + } + + pub fn build_mapped_commands(&self) -> Result, anyhow::Error> { + self.map_commands + .iter() + .map(|item| { + let (a, b) = item.split_once('=').with_context(|| { + format!( + "Invalid --map-command flag: expected =, got '{item}'" + ) + })?; + + let a = a.trim(); + let b = b.trim(); + + if a.is_empty() { + bail!("Invalid --map-command flag - alias cannot be empty: '{item}'"); + } + // TODO(theduke): check if host command exists, and canonicalize PathBuf. + if b.is_empty() { + bail!("Invalid --map-command flag - host path cannot be empty: '{item}'"); + } + + Ok(MappedCommand { + alias: a.to_string(), + target: b.to_string(), + }) + }) + .collect::, anyhow::Error>>() + } + pub fn capabilities(&self) -> Capabilities { let mut caps = Capabilities::default(); diff --git a/lib/wasix/src/os/console/mod.rs b/lib/wasix/src/os/console/mod.rs index 4bfc3f652f3..43503fcbe7e 100644 --- a/lib/wasix/src/os/console/mod.rs +++ b/lib/wasix/src/os/console/mod.rs @@ -225,7 +225,13 @@ impl Console { .with_stdin(Box::new(self.stdin.clone())) .with_stdout(Box::new(self.stdout.clone())) .with_stderr(Box::new(self.stderr.clone())) - .prepare_webc_env(prog, &wasi_opts, &pkg, self.runtime.clone(), Some(root_fs)) + .prepare_webc_env( + prog, + &wasi_opts, + Some(&pkg), + self.runtime.clone(), + Some(root_fs), + ) // TODO: better error conversion .map_err(|err| SpawnError::Other(err.into()))?; diff --git a/lib/wasix/src/runners/mod.rs b/lib/wasix/src/runners/mod.rs index dcd5f30d108..ed9fa74f006 100644 --- a/lib/wasix/src/runners/mod.rs +++ b/lib/wasix/src/runners/mod.rs @@ -7,7 +7,7 @@ mod wasi_common; #[cfg(feature = "webc_runner_rt_wcgi")] pub mod wcgi; -pub use self::runner::Runner; +pub use self::{runner::Runner, wasi_common::MappedCommand}; /// A directory that should be mapped from the host filesystem into a WASI /// instance (the "guest"). diff --git a/lib/wasix/src/runners/wasi.rs b/lib/wasix/src/runners/wasi.rs index b2c2292805b..f1461315066 100644 --- a/lib/wasix/src/runners/wasi.rs +++ b/lib/wasix/src/runners/wasi.rs @@ -1,10 +1,11 @@ //! WebC container support for running WASI modules -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use anyhow::{Context, Error}; use tracing::Instrument; use virtual_fs::{ArcBoxFile, TmpFileSystem, VirtualFile}; +use wasmer::Module; use webc::metadata::{annotations::Wasi, Command}; use crate::{ @@ -15,6 +16,8 @@ use crate::{ Runtime, WasiEnvBuilder, WasiRuntimeError, }; +use super::wasi_common::MappedCommand; + #[derive(Debug, Default, Clone)] pub struct WasiRunner { wasi: CommonWasiOptions, @@ -85,13 +88,13 @@ impl WasiRunner { } } - pub fn with_forward_host_env(mut self) -> Self { - self.set_forward_host_env(); + 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) { - self.wasi.forward_host_env = true; + pub fn set_forward_host_env(&mut self, forward: bool) { + self.wasi.forward_host_env = forward; } pub fn with_mapped_directories(mut self, dirs: I) -> Self @@ -105,6 +108,15 @@ impl WasiRunner { self } + pub fn set_current_dir(&mut self, dir: impl Into) { + 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 { self.wasi.injected_packages.push(pkg); @@ -135,7 +147,35 @@ impl WasiRunner { self } - pub fn capabilities(&mut self) -> &mut Capabilities { + pub fn add_mapped_host_command(&mut self, alias: impl Into, target: impl Into) { + 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, + commands: impl IntoIterator, + ) -> Self { + self.add_mapped_host_commands(commands); + self + } + + pub fn capabilities_mut(&mut self) -> &mut Capabilities { &mut self.wasi.capabilities } @@ -183,12 +223,19 @@ impl WasiRunner { &self, program_name: &str, wasi: &Wasi, - pkg: &BinaryPackage, + pkg: Option<&BinaryPackage>, runtime: Arc, root_fs: Option, ) -> Result { - let mut builder = WasiEnvBuilder::new(program_name); - let container_fs = Arc::clone(&pkg.webc_fs); + let mut builder = WasiEnvBuilder::new(program_name).runtime(runtime); + + let container_fs = if let Some(pkg) = pkg { + builder.add_webc(pkg.clone()); + Some(Arc::clone(&pkg.webc_fs)) + } else { + None + }; + self.wasi .prepare_webc_env(&mut builder, container_fs, wasi, root_fs)?; @@ -202,11 +249,28 @@ impl WasiRunner { builder.set_stderr(Box::new(stderr.clone())); } - builder.add_webc(pkg.clone()); - builder.set_runtime(runtime); - Ok(builder) } + + pub fn run_wasm( + &self, + runtime: Arc, + program_name: &str, + module: &Module, + asyncify: bool, + ) -> Result<(), Error> { + let wasi = webc::metadata::annotations::Wasi::new(program_name); + let mut store = runtime.new_store(); + let env = self.prepare_webc_env(program_name, &wasi, None, runtime, None)?; + + if asyncify { + env.run_with_store_async(module.clone(), store)?; + } else { + env.run_with_store(module.clone(), &mut store)?; + } + + Ok(()) + } } impl crate::runners::Runner for WasiRunner { @@ -231,13 +295,13 @@ impl crate::runners::Runner for WasiRunner { .annotation("wasi")? .unwrap_or_else(|| Wasi::new(command_name)); - let store = runtime.new_store(); - let env = self - .prepare_webc_env(command_name, &wasi, pkg, Arc::clone(&runtime), None) + .prepare_webc_env(command_name, &wasi, Some(pkg), Arc::clone(&runtime), None) .context("Unable to prepare the WASI environment")? .build()?; + let store = runtime.new_store(); + let command_name = command_name.to_string(); let tasks = runtime.task_manager().clone(); let pkg = pkg.clone(); diff --git a/lib/wasix/src/runners/wasi_common.rs b/lib/wasix/src/runners/wasi_common.rs index e76b26efde2..d02fa7d2185 100644 --- a/lib/wasix/src/runners/wasi_common.rs +++ b/lib/wasix/src/runners/wasi_common.rs @@ -14,21 +14,31 @@ use crate::{ WasiEnvBuilder, }; +#[derive(Debug, Clone)] +pub struct MappedCommand { + /// The new alias. + pub alias: String, + /// The original command. + pub target: String, +} + #[derive(Debug, Default, Clone)] pub(crate) struct CommonWasiOptions { pub(crate) args: Vec, pub(crate) env: HashMap, pub(crate) forward_host_env: bool, pub(crate) mapped_dirs: Vec, + pub(crate) mapped_host_commands: Vec, pub(crate) injected_packages: Vec, pub(crate) capabilities: Capabilities, + pub(crate) current_dir: Option, } impl CommonWasiOptions { pub(crate) fn prepare_webc_env( &self, builder: &mut WasiEnvBuilder, - container_fs: Arc, + container_fs: Option>, wasi: &WasiAnnotation, root_fs: Option, ) -> Result<(), anyhow::Error> { @@ -48,6 +58,12 @@ impl CommonWasiOptions { builder.add_webc(pkg.clone()); } + let mapped_cmds = self + .mapped_host_commands + .iter() + .map(|c| (c.alias.as_str(), c.target.as_str())); + builder.add_mapped_commands(mapped_cmds); + self.populate_env(wasi, builder); self.populate_args(wasi, builder); @@ -87,77 +103,85 @@ impl CommonWasiOptions { } } -type ContainerFs = - OverlayFileSystem>; 1]>; +// type ContainerFs = +// OverlayFileSystem>; 1]>; -fn prepare_filesystem( - root_fs: TmpFileSystem, - mapped_dirs: &[MappedDirectory], - container_fs: Arc, +fn build_directory_mappings( builder: &mut WasiEnvBuilder, -) -> Result { - if !mapped_dirs.is_empty() { - let host_fs: Arc = Arc::new(crate::default_fs_backing()); + root_fs: &mut TmpFileSystem, + host_fs: &Arc, + mapped_dirs: &[MappedDirectory], +) -> Result<(), anyhow::Error> { + for dir in mapped_dirs { + let MappedDirectory { + host: host_path, + guest: guest_path, + } = dir; + let mut guest_path = PathBuf::from(guest_path); + tracing::debug!( + guest=%guest_path.display(), + host=%host_path.display(), + "Mounting host folder", + ); - for dir in mapped_dirs { - let MappedDirectory { - host: host_path, - guest: guest_path, - } = dir; - let mut guest_path = PathBuf::from(guest_path); - tracing::debug!( - guest=%guest_path.display(), - host=%host_path.display(), - "Mounting host folder", - ); - - if guest_path.is_relative() { - guest_path = apply_relative_path_mounting_hack(&guest_path); - } + if guest_path.is_relative() { + guest_path = apply_relative_path_mounting_hack(&guest_path); + } - let host_path = std::fs::canonicalize(host_path).with_context(|| { - format!("Unable to canonicalize host path '{}'", host_path.display()) + let host_path = std::fs::canonicalize(host_path).with_context(|| { + format!("Unable to canonicalize host path '{}'", host_path.display()) + })?; + + let guest_path = root_fs + .canonicalize_unchecked(&guest_path) + .with_context(|| { + format!( + "Unable to canonicalize guest path '{}'", + guest_path.display() + ) })?; - let guest_path = root_fs - .canonicalize_unchecked(&guest_path) + if guest_path == Path::new("/") { + root_fs + .mount_directory_entries(&guest_path, host_fs, &host_path) + .with_context(|| format!("Unable to mount \"{}\" to root", host_path.display(),))?; + } else { + if let Some(parent) = guest_path.parent() { + create_dir_all(root_fs, parent).with_context(|| { + format!("Unable to create the \"{}\" directory", parent.display()) + })?; + } + + root_fs + .mount(guest_path.clone(), host_fs, host_path.clone()) .with_context(|| { format!( - "Unable to canonicalize guest path '{}'", + "Unable to mount \"{}\" to \"{}\"", + host_path.display(), guest_path.display() ) })?; - if guest_path == Path::new("/") { - root_fs - .mount_directory_entries(&guest_path, &host_fs, &host_path) - .with_context(|| { - format!("Unable to mount \"{}\" to root", host_path.display(),) - })?; - } else { - if let Some(parent) = guest_path.parent() { - create_dir_all(&root_fs, parent).with_context(|| { - format!("Unable to create the \"{}\" directory", parent.display()) - })?; - } - - root_fs - .mount(guest_path.clone(), &host_fs, host_path.clone()) - .with_context(|| { - format!( - "Unable to mount \"{}\" to \"{}\"", - host_path.display(), - guest_path.display() - ) - })?; - - builder - .add_preopen_dir(&guest_path) - .with_context(|| format!("Unable to preopen \"{}\"", guest_path.display()))?; - } + builder + .add_preopen_dir(&guest_path) + .with_context(|| format!("Unable to preopen \"{}\"", guest_path.display()))?; } } + Ok(()) +} + +fn prepare_filesystem( + mut root_fs: TmpFileSystem, + mapped_dirs: &[MappedDirectory], + container_fs: Option>, + builder: &mut WasiEnvBuilder, +) -> Result, Error> { + if !mapped_dirs.is_empty() { + let host_fs: Arc = Arc::new(crate::default_fs_backing()); + build_directory_mappings(builder, &mut root_fs, &host_fs, mapped_dirs)?; + } + // HACK(Michael-F-Bryan): The WebcVolumeFileSystem only accepts relative // paths, but our Python executable will try to access its standard library // with relative paths assuming that it is being run from the root @@ -166,8 +190,15 @@ fn prepare_filesystem( // Until the FileSystem trait figures out whether relative paths should be // supported or not, we'll add an adapter that automatically retries // operations using an absolute path if it failed using a relative path. - let container_fs = RelativeOrAbsolutePathHack(container_fs); - let fs = OverlayFileSystem::new(root_fs, [container_fs]); + + let fs = if let Some(container) = container_fs { + let container = RelativeOrAbsolutePathHack(container); + let fs = OverlayFileSystem::new(root_fs, [container]); + Box::new(fs) as Box + } else { + let fs = RelativeOrAbsolutePathHack(root_fs); + Box::new(fs) as Box + }; Ok(fs) } @@ -309,7 +340,7 @@ mod tests { "args".to_string(), ]); - args.prepare_webc_env(&mut builder, fs, &annotations, None) + args.prepare_webc_env(&mut builder, Some(fs), &annotations, None) .unwrap(); assert_eq!( @@ -341,7 +372,7 @@ mod tests { let mut annotations = WasiAnnotation::new("python"); annotations.env = Some(vec!["HARD_CODED=env-vars".to_string()]); - args.prepare_webc_env(&mut builder, fs, &annotations, None) + args.prepare_webc_env(&mut builder, Some(fs), &annotations, None) .unwrap(); assert_eq!( @@ -368,7 +399,8 @@ mod tests { let mut builder = WasiEnvBuilder::new(""); let root_fs = RootFileSystemBuilder::default().build(); - let fs = prepare_filesystem(root_fs, &mapping, Arc::new(webc_fs), &mut builder).unwrap(); + let fs = + prepare_filesystem(root_fs, &mapping, Some(Arc::new(webc_fs)), &mut builder).unwrap(); assert!(fs.metadata("/home/file.txt".as_ref()).unwrap().is_file()); assert!(fs.metadata("lib".as_ref()).unwrap().is_dir()); diff --git a/lib/wasix/src/runners/wcgi/runner.rs b/lib/wasix/src/runners/wcgi/runner.rs index 66bbf40b77c..200481c9425 100644 --- a/lib/wasix/src/runners/wcgi/runner.rs +++ b/lib/wasix/src/runners/wcgi/runner.rs @@ -67,7 +67,7 @@ impl WcgiRunner { let wasi_common = self.config.wasi.clone(); let rt = Arc::clone(&runtime); let setup_builder = move |builder: &mut WasiEnvBuilder| { - wasi_common.prepare_webc_env(builder, Arc::clone(&container_fs), &wasi, None)?; + wasi_common.prepare_webc_env(builder, Some(Arc::clone(&container_fs)), &wasi, None)?; builder.set_runtime(Arc::clone(&rt)); Ok(()) diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs index cbe113ee8cb..6cb937f2ccb 100644 --- a/lib/wasix/src/state/builder.rs +++ b/lib/wasix/src/state/builder.rs @@ -9,7 +9,7 @@ use std::{ use bytes::Bytes; use rand::Rng; use thiserror::Error; -use virtual_fs::{ArcFile, FsError, TmpFileSystem, VirtualFile}; +use virtual_fs::{ArcFile, FileSystem, FsError, TmpFileSystem, VirtualFile}; use wasmer::{AsStoreMut, Instance, Module, RuntimeError, Store}; use wasmer_wasix_types::wasi::{Errno, ExitCode}; @@ -62,6 +62,7 @@ pub struct WasiEnvBuilder { pub(super) stdin: Option>, pub(super) fs: Option, pub(super) runtime: Option>, + pub(super) current_dir: Option, /// List of webc dependencies to be injected. pub(super) uses: Vec, @@ -306,6 +307,17 @@ impl WasiEnvBuilder { /// Map an atom to a local binary #[cfg(feature = "sys")] pub fn map_command(mut self, name: Name, target: Target) -> Self + where + Name: AsRef, + Target: AsRef, + { + self.add_mapped_command(name, target); + self + } + + /// Map an atom to a local binary + #[cfg(feature = "sys")] + pub fn add_mapped_command(&mut self, name: Name, target: Target) where Name: AsRef, Target: AsRef, @@ -313,7 +325,6 @@ impl WasiEnvBuilder { let path_buf = PathBuf::from(target.as_ref().to_string()); self.map_commands .insert(name.as_ref().to_string(), path_buf); - self } /// Maps a series of atoms to the local binaries @@ -324,14 +335,23 @@ impl WasiEnvBuilder { Name: AsRef, Target: AsRef, { - map_commands.into_iter().for_each(|(name, target)| { - let path_buf = PathBuf::from(target.as_ref().to_string()); - self.map_commands - .insert(name.as_ref().to_string(), path_buf); - }); + self.add_mapped_commands(map_commands); self } + /// Maps a series of atoms to local binaries. + #[cfg(feature = "sys")] + pub fn add_mapped_commands(&mut self, map_commands: I) + where + I: IntoIterator, + Name: AsRef, + Target: AsRef, + { + for (alias, target) in map_commands { + self.add_mapped_command(alias, target); + } + } + /// Preopen a directory /// /// This opens the given directory at the virtual root, `/`, and allows @@ -480,6 +500,15 @@ impl WasiEnvBuilder { Ok(self) } + pub fn set_current_dir(&mut self, dir: impl Into) { + self.current_dir = Some(dir.into()); + } + + pub fn current_dir(mut self, dir: impl Into) -> Self { + self.set_current_dir(dir); + self + } + /// Overwrite the default WASI `stdout`, if you want to hold on to the /// original `stdout` use [`WasiFs::swap_file`] after building. pub fn stdout(mut self, new_file: Box) -> Self { @@ -651,6 +680,28 @@ impl WasiEnvBuilder { .take() .unwrap_or_else(|| WasiFsRoot::Sandbox(Arc::new(TmpFileSystem::new()))); + if let Some(dir) = &self.current_dir { + match fs_backing.read_dir(dir) { + Ok(_) => { + // All good + } + 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() + ))); + } + } + } + // self.preopens are checked in [`PreopenDirBuilder::build`] let inodes = crate::state::WasiInodes::new(); let wasi_fs = { @@ -682,6 +733,16 @@ impl WasiEnvBuilder { 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); + } + let envs = self .envs .into_iter()