diff --git a/Cargo.lock b/Cargo.lock index f49dec42510..275ca45fd7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7377,9 +7377,9 @@ dependencies = [ [[package]] name = "webc" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48441419be082f8d2537c84d8b1f502624d77bc08fbbd09ab17cadfe7f0ac53" +checksum = "cdea84cf234555864ca9b7a5084c1a99dbdf2d148035f62a09b19ce5606532c1" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index f106fae036f..d8b8d13b216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ wasmer-config = { path = "./lib/config" } wasmer-wasix = { path = "./lib/wasix" } # Wasmer-owned crates -webc = { version = "6.0.1", default-features = false, features = ["package"] } +webc = { version = "6.1.0", default-features = false, features = ["package"] } shared-buffer = "0.1.4" # Third-party crates diff --git a/lib/cli/src/commands/run/mod.rs b/lib/cli/src/commands/run/mod.rs index eefc629f72a..4b68a7a1196 100644 --- a/lib/cli/src/commands/run/mod.rs +++ b/lib/cli/src/commands/run/mod.rs @@ -219,7 +219,7 @@ impl Run { ) -> Result<(), Error> { let id = match self.entrypoint.as_deref() { Some(cmd) => cmd, - None => infer_webc_entrypoint(pkg)?, + None => pkg.infer_entrypoint()?, }; let cmd = pkg .get_command(id) @@ -570,25 +570,6 @@ fn parse_value(s: &str, ty: wasmer_types::Type) -> Result { Ok(value) } -fn infer_webc_entrypoint(pkg: &BinaryPackage) -> Result<&str, Error> { - if let Some(entrypoint) = pkg.entrypoint_cmd.as_deref() { - return Ok(entrypoint); - } - - match pkg.commands.as_slice() { - [] => bail!("The WEBC file doesn't contain any executable commands"), - [one] => Ok(one.name()), - [..] => { - let mut commands: Vec<_> = pkg.commands.iter().map(|cmd| cmd.name()).collect(); - commands.sort(); - bail!( - "Unable to determine the WEBC file's entrypoint. Please choose one of {:?}", - commands, - ); - } - } -} - /// The input that was passed in via the command-line. #[derive(Debug, Clone, PartialEq)] enum PackageSource { diff --git a/lib/wasix/src/bin_factory/binary_package.rs b/lib/wasix/src/bin_factory/binary_package.rs index 4fcf0405411..051b9268c6a 100644 --- a/lib/wasix/src/bin_factory/binary_package.rs +++ b/lib/wasix/src/bin_factory/binary_package.rs @@ -224,6 +224,25 @@ impl BinaryPackage { } }) } + + pub fn infer_entrypoint(&self) -> Result<&str, anyhow::Error> { + if let Some(entrypoint) = self.entrypoint_cmd.as_deref() { + return Ok(entrypoint); + } + + match self.commands.as_slice() { + [] => anyhow::bail!("The package doesn't contain any executable commands"), + [one] => Ok(one.name()), + [..] => { + let mut commands: Vec<_> = self.commands.iter().map(|cmd| cmd.name()).collect(); + commands.sort(); + anyhow::bail!( + "Unable to determine the package's entrypoint. Please choose one of {:?}", + commands, + ); + } + } + } } #[cfg(test)] diff --git a/lib/wasix/src/bin_factory/mod.rs b/lib/wasix/src/bin_factory/mod.rs index 0cb8cee8e0d..fa90ffd8d7e 100644 --- a/lib/wasix/src/bin_factory/mod.rs +++ b/lib/wasix/src/bin_factory/mod.rs @@ -8,7 +8,6 @@ use std::{ }; use anyhow::Context; -use exec::spawn_exec_wasm; use virtual_fs::{AsyncReadExt, FileSystem}; use wasmer::FunctionEnvMut; use wasmer_wasix_types::wasi::Errno; @@ -20,7 +19,8 @@ mod exec; pub use self::{ binary_package::*, exec::{ - run_exec, spawn_exec, spawn_exec_module, spawn_load_module, spawn_load_wasm, spawn_union_fs, + run_exec, spawn_exec, spawn_exec_module, spawn_exec_wasm, spawn_load_module, + spawn_load_wasm, spawn_union_fs, }, }; use crate::{ @@ -92,6 +92,25 @@ impl BinFactory { spawn_exec_wasm(&bytes, name.as_str(), env, &self.runtime).await } Executable::BinaryPackage(pkg) => { + // Get the command that is going to be executed + let cmd = if let Some(cmd) = pkg.get_command(name.as_str()) { + cmd + } else if let Some(cmd) = pkg.get_entrypoint_command() { + cmd + } else { + tracing::error!( + command=name, + pkg=%pkg.id, + "Unable to spawn a command because its package has no entrypoint", + ); + env.on_exit(Some(Errno::Noexec.into())).await; + return Err(SpawnError::MissingEntrypoint { + package_id: pkg.id.clone(), + }); + }; + + env.prepare_spawn(cmd); + spawn_exec(pkg, name.as_str(), store, env, &self.runtime).await } } diff --git a/lib/wasix/src/os/command/builtins/cmd_wasmer.rs b/lib/wasix/src/os/command/builtins/cmd_wasmer.rs index c0a0536907d..20e4d26a769 100644 --- a/lib/wasix/src/os/command/builtins/cmd_wasmer.rs +++ b/lib/wasix/src/os/command/builtins/cmd_wasmer.rs @@ -1,12 +1,15 @@ -use std::{any::Any, sync::Arc}; +use std::{any::Any, path::PathBuf, sync::Arc}; use crate::{ + bin_factory::spawn_exec_wasm, os::task::{OwnedTaskStatus, TaskJoinHandle}, runtime::task_manager::InlineWaker, SpawnError, }; +use virtual_fs::{AsyncReadExt, FileSystem}; use wasmer::{FunctionEnvMut, Store}; use wasmer_wasix_types::wasi::Errno; +use webc::Container; use crate::{ bin_factory::{spawn_exec, BinaryPackage}, @@ -57,6 +60,11 @@ impl CmdWasmer { what: Option, mut args: Vec, ) -> Result { + pub enum Executable { + Wasm(bytes::Bytes), + BinaryPackage(BinaryPackage), + } + // If the first argument is a '--' then skip it if args.first().map(|a| a.as_str()) == Some("--") { args = args.into_iter().skip(1).collect(); @@ -69,25 +77,69 @@ impl CmdWasmer { // Set the arguments of the environment by replacing the state let mut state = env.state.fork(); args.insert(0, what.clone()); - state.args = args; + state.args = std::sync::Mutex::new(args); env.state = Arc::new(state); - if let Ok(binary) = self.get_package(&what).await { - // Now run the module - spawn_exec(binary, name, store, env, &self.runtime).await + let file_path = if what.starts_with('/') { + PathBuf::from(&what) } else { - let _ = unsafe { - stderr_write( - parent_ctx, - format!("package not found - {}\r\n", what).as_bytes(), - ) - } - .await; + // convert relative path to absolute path + let cwd = env.state.fs.current_dir.lock().unwrap().clone(); + + PathBuf::from(cwd).join(&what) + }; + + let fs = env.fs_root(); + let f = fs.new_open_options().read(true).open(&file_path); + let executable = if let Ok(mut file) = f { + let mut data = Vec::with_capacity(file.size() as usize); + file.read_to_end(&mut data).await.unwrap(); + + let bytes: bytes::Bytes = data.into(); - let handle = OwnedTaskStatus::new_finished_with_code(Errno::Noent.into()).handle(); - Ok(handle) + if let Ok(container) = Container::from_bytes(bytes.clone()) { + let pkg = BinaryPackage::from_webc(&container, &*self.runtime) + .await + .unwrap(); + + Executable::BinaryPackage(pkg) + } else { + Executable::Wasm(bytes) + } + } else if let Ok(pkg) = self.get_package(&what).await { + Executable::BinaryPackage(pkg) + } else { + let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await; + let handle = + OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); + return Ok(handle); + }; + + match executable { + Executable::BinaryPackage(binary) => { + // Infer the command that is going to be executed + let cmd_name: &str = + binary + .infer_entrypoint() + .map_err(|_| SpawnError::MissingEntrypoint { + package_id: binary.id.clone(), + })?; + + let cmd = binary + .get_command(cmd_name) + .ok_or_else(|| SpawnError::NotFound { + message: format!("{cmd_name} command in package: {}", binary.id), + })?; + + env.prepare_spawn(cmd); + + env.use_package_async(&binary).await.unwrap(); + + // Now run the module + spawn_exec(binary, name, store, env, &self.runtime).await + } + Executable::Wasm(bytes) => spawn_exec_wasm(&bytes, name, env, &self.runtime).await, } - // Get the binary } else { let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await; let handle = OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); @@ -130,7 +182,7 @@ impl VirtualCommand for CmdWasmer { ) -> Result { // Read the command we want to run let env_inner = env.as_ref().ok_or(SpawnError::UnknownError)?; - let args = env_inner.state.args.clone(); + let args = env_inner.state.args.lock().unwrap().clone(); let mut args = args.iter().map(|s| s.as_str()); let _alias = args.next(); let cmd = args.next(); diff --git a/lib/wasix/src/runners/wasi.rs b/lib/wasix/src/runners/wasi.rs index f775ef87e79..bf4818b5387 100644 --- a/lib/wasix/src/runners/wasi.rs +++ b/lib/wasix/src/runners/wasi.rs @@ -338,9 +338,15 @@ impl crate::runners::Runner for WasiRunner { .annotation("wasi")? .unwrap_or_else(|| Wasi::new(command_name)); + let exec_name = if let Some(exec_name) = wasi.exec_name.as_ref() { + exec_name + } else { + command_name + }; + #[allow(unused_mut)] let mut env = self - .prepare_webc_env(command_name, &wasi, Some(pkg), Arc::clone(&runtime), None) + .prepare_webc_env(exec_name, &wasi, Some(pkg), Arc::clone(&runtime), None) .context("Unable to prepare the WASI environment")?; #[cfg(feature = "journal")] diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs index b35bf444d16..e862ddc5312 100644 --- a/lib/wasix/src/state/builder.rs +++ b/lib/wasix/src/state/builder.rs @@ -867,7 +867,7 @@ impl WasiEnvBuilder { fs: wasi_fs, secret: rand::thread_rng().gen::<[u8; 32]>(), inodes, - args: self.args.clone(), + args: std::sync::Mutex::new(self.args.clone()), preopen: self.vfs_preopens.clone(), futexs: Default::default(), clock_offset: Default::default(), diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 452109aa718..15306311fe2 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -21,11 +21,12 @@ use wasmer_wasix_types::{ wasi::{Errno, ExitCode, Snapshot0Clockid}, wasix::ThreadStartType, }; +use webc::metadata::annotations::Wasi; #[cfg(feature = "journal")] use crate::journal::{DynJournal, JournalEffector, SnapshotTrigger}; use crate::{ - bin_factory::{BinFactory, BinaryPackage}, + bin_factory::{BinFactory, BinaryPackage, BinaryPackageCommand}, capabilities::Capabilities, fs::{WasiFsRoot, WasiInodes}, import_object_for_all_wasi_versions, @@ -42,7 +43,7 @@ use crate::{ use wasmer_types::ModuleHash; pub(crate) use super::handles::*; -use super::WasiState; +use super::{conv_env_vars, WasiState}; /// Various [`TypedFunction`] and [`Global`] handles for an active WASI(X) instance. /// @@ -295,7 +296,7 @@ impl WasiEnvInit { clock_offset: std::sync::Mutex::new( self.state.clock_offset.lock().unwrap().clone(), ), - args: self.state.args.clone(), + args: std::sync::Mutex::new(self.state.args.lock().unwrap().clone()), envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()), preopen: self.state.preopen.clone(), }, @@ -1079,6 +1080,10 @@ impl WasiEnv { (state, inodes) } + pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> { + InlineWaker::block_on(self.use_package_async(pkg)) + } + /// Make all the commands in a [`BinaryPackage`] available to the WASI /// instance. /// @@ -1090,13 +1095,16 @@ impl WasiEnv { /// /// [cmd-atom]: crate::bin_factory::BinaryPackageCommand::atom() /// [pkg-fs]: crate::bin_factory::BinaryPackage::webc_fs - pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> { + pub async fn use_package_async( + &self, + pkg: &BinaryPackage, + ) -> Result<(), WasiStateCreationError> { tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment"); let root_fs = &self.state.fs.root_fs; // We first need to merge the filesystem in the package into the // main file system, if it has not been merged already. - if let Err(e) = InlineWaker::block_on(self.state.fs.conditional_union(pkg)) { + if let Err(e) = self.state.fs.conditional_union(pkg).await { tracing::warn!( error = &e as &dyn std::error::Error, "Unable to merge the package's filesystem into the main one", @@ -1146,9 +1154,7 @@ impl WasiEnv { } WasiFsRoot::Backing(fs) => { let mut f = fs.new_open_options().create(true).write(true).open(path)?; - if let Err(e) = - InlineWaker::block_on(f.copy_reference(Box::new(StaticFile::new(atom)))) - { + if let Err(e) = f.copy_reference(Box::new(StaticFile::new(atom))).await { tracing::warn!( error = &e as &dyn std::error::Error, "Unable to copy file reference", @@ -1304,4 +1310,45 @@ impl WasiEnv { Box::pin(async {}) } } + + pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) { + if let Ok(Some(Wasi { + main_args, + env: env_vars, + exec_name, + .. + })) = cmd.metadata().wasi() + { + if let Some(env_vars) = env_vars { + let env_vars = env_vars + .into_iter() + .map(|env_var| { + let (k, v) = env_var.split_once('=').unwrap(); + + (k.to_string(), v.as_bytes().to_vec()) + }) + .collect::>(); + + let env_vars = conv_env_vars(env_vars); + + self.state + .envs + .lock() + .unwrap() + .extend_from_slice(env_vars.as_slice()); + } + + if let Some(args) = main_args { + self.state + .args + .lock() + .unwrap() + .extend_from_slice(args.as_slice()); + } + + if let Some(exec_name) = exec_name { + self.state.args.lock().unwrap()[0] = exec_name; + } + } + } } diff --git a/lib/wasix/src/state/mod.rs b/lib/wasix/src/state/mod.rs index 6d1dce47069..b37dc034c0c 100644 --- a/lib/wasix/src/state/mod.rs +++ b/lib/wasix/src/state/mod.rs @@ -133,7 +133,7 @@ pub(crate) struct WasiState { pub inodes: WasiInodes, pub futexs: Mutex, pub clock_offset: Mutex>, - pub args: Vec, + pub args: Mutex>, pub envs: Mutex>>, // TODO: should not be here, since this requires active work to resolve. @@ -255,7 +255,7 @@ impl WasiState { inodes: self.inodes.clone(), futexs: Default::default(), clock_offset: Mutex::new(self.clock_offset.lock().unwrap().clone()), - args: self.args.clone(), + args: Mutex::new(self.args.lock().unwrap().clone()), envs: Mutex::new(self.envs.lock().unwrap().clone()), preopen: self.preopen.clone(), } diff --git a/lib/wasix/src/syscalls/mod.rs b/lib/wasix/src/syscalls/mod.rs index 87cde9b845a..6289c9b7486 100644 --- a/lib/wasix/src/syscalls/mod.rs +++ b/lib/wasix/src/syscalls/mod.rs @@ -1495,7 +1495,7 @@ pub(crate) fn _prepare_wasi( // Swap out the arguments with the new ones if let Some(args) = args { let mut wasi_state = wasi_env.state.fork(); - wasi_state.args = args; + *wasi_state.args.lock().unwrap() = args; wasi_env.state = Arc::new(wasi_state); } diff --git a/lib/wasix/src/syscalls/wasi/args_get.rs b/lib/wasix/src/syscalls/wasi/args_get.rs index fe3f22363f9..70066237645 100644 --- a/lib/wasix/src/syscalls/wasi/args_get.rs +++ b/lib/wasix/src/syscalls/wasi/args_get.rs @@ -21,6 +21,8 @@ pub fn args_get( let args = state .args + .lock() + .unwrap() .iter() .map(|a| a.as_bytes().to_vec()) .collect::>(); @@ -30,6 +32,8 @@ pub fn args_get( "args:\n{}", state .args + .lock() + .unwrap() .iter() .enumerate() .map(|(i, v)| format!("{:>20}: {}", i, v)) diff --git a/lib/wasix/src/syscalls/wasi/args_sizes_get.rs b/lib/wasix/src/syscalls/wasi/args_sizes_get.rs index 1740e218fa9..28fef44a7a5 100644 --- a/lib/wasix/src/syscalls/wasi/args_sizes_get.rs +++ b/lib/wasix/src/syscalls/wasi/args_sizes_get.rs @@ -20,8 +20,14 @@ pub fn args_sizes_get( let argc = argc.deref(&memory); let argv_buf_size = argv_buf_size.deref(&memory); - let argc_val: M::Offset = wasi_try!(state.args.len().try_into().map_err(|_| Errno::Overflow)); - let argv_buf_size_val: usize = state.args.iter().map(|v| v.len() + 1).sum(); + let argc_val: M::Offset = wasi_try!(state + .args + .lock() + .unwrap() + .len() + .try_into() + .map_err(|_| Errno::Overflow)); + let argv_buf_size_val: usize = state.args.lock().unwrap().iter().map(|v| v.len() + 1).sum(); let argv_buf_size_val: M::Offset = wasi_try!(argv_buf_size_val.try_into().map_err(|_| Errno::Overflow)); wasi_try_mem!(argc.write(argc_val)); diff --git a/lib/wasix/src/syscalls/wasix/proc_spawn.rs b/lib/wasix/src/syscalls/wasix/proc_spawn.rs index d46d989d046..4d22eec4e7a 100644 --- a/lib/wasix/src/syscalls/wasix/proc_spawn.rs +++ b/lib/wasix/src/syscalls/wasix/proc_spawn.rs @@ -117,7 +117,7 @@ pub fn proc_spawn_internal( let child_process = child_env.process.clone(); if let Some(args) = args { let mut child_state = env.state.fork(); - child_state.args = args; + child_state.args = std::sync::Mutex::new(args); child_env.state = Arc::new(child_state); }