diff --git a/Cargo.lock b/Cargo.lock index 542947bb6..3ff5cb718 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if 1.0.0", ] @@ -976,7 +976,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -997,7 +997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch 0.9.18", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -1021,7 +1021,7 @@ version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -1041,7 +1041,7 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -1057,9 +1057,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" @@ -2778,6 +2778,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.3.9" @@ -3806,7 +3812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque 0.8.5", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -4680,6 +4686,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simdutf8" version = "0.1.4" @@ -4999,18 +5011,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -7686,15 +7698,31 @@ checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zip" -version = "1.2.3" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700ea425e148de30c29c580c1f9508b93ca57ad31c9f4e96b83c194c37a7a8f" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" dependencies = [ "arbitrary", "crc32fast", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", "displaydoc", "flate2", "indexmap 2.2.6", + "memchr", "thiserror", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log 0.4.21", + "once_cell", + "simd-adler32", ] diff --git a/docs/journal.md b/docs/journal.md index 70c1d8978..12e27624f 100644 --- a/docs/journal.md +++ b/docs/journal.md @@ -146,3 +146,6 @@ in line as the events are generated Filters out a specific set of log events and drops the rest, this capturer can be useful for restoring to a previous call point but retaining the memory changes (e.g. WCGI runner). + +## Improvements +https://codesandbox.io/blog/how-we-scale-our-microvm-infrastructure-using-low-latency-memory-decompression \ No newline at end of file diff --git a/lib/backend-api/src/query.rs b/lib/backend-api/src/query.rs index e8774a178..0c7d0dd43 100644 --- a/lib/backend-api/src/query.rs +++ b/lib/backend-api/src/query.rs @@ -164,7 +164,15 @@ pub async fn tag_package_release( .map(|r| r.tag_package_release) } -/// Get the currently logged in used, together with all accessible namespaces. +/// Get the currently logged in user. +pub async fn current_user(client: &WasmerClient) -> Result, anyhow::Error> { + client + .run_graphql(types::GetCurrentUser::build(())) + .await + .map(|x| x.viewer) +} + +/// Get the currently logged in user, together with all accessible namespaces. /// /// You can optionally filter the namespaces by the user role. pub async fn current_user_with_namespaces( @@ -172,9 +180,9 @@ pub async fn current_user_with_namespaces( namespace_role: Option, ) -> Result { client - .run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars { - namespace_role, - })) + .run_graphql(types::GetCurrentUserWithNamespaces::build( + types::GetCurrentUserWithNamespacesVars { namespace_role }, + )) .await? .viewer .context("not logged in") @@ -544,9 +552,11 @@ pub async fn user_accessible_apps( // Get all aps in user-accessible namespaces. let namespace_res = client - .run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars { - namespace_role: None, - })) + .run_graphql(types::GetCurrentUserWithNamespaces::build( + types::GetCurrentUserWithNamespacesVars { + namespace_role: None, + }, + )) .await?; let active_user = namespace_res.viewer.context("not logged in")?; let namespace_names = active_user @@ -655,9 +665,11 @@ pub async fn user_namespaces( client: &WasmerClient, ) -> Result, anyhow::Error> { let user = client - .run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars { - namespace_role: None, - })) + .run_graphql(types::GetCurrentUserWithNamespaces::build( + types::GetCurrentUserWithNamespacesVars { + namespace_role: None, + }, + )) .await? .viewer .context("not logged in")?; diff --git a/lib/backend-api/src/types.rs b/lib/backend-api/src/types.rs index 9f381c87d..6167c4e85 100644 --- a/lib/backend-api/src/types.rs +++ b/lib/backend-api/src/types.rs @@ -41,14 +41,20 @@ mod queries { Viewer, } + #[derive(cynic::QueryFragment, Debug)] + #[cynic(graphql_type = "Query")] + pub struct GetCurrentUser { + pub viewer: Option, + } + #[derive(cynic::QueryVariables, Debug)] - pub struct GetCurrentUserVars { + pub struct GetCurrentUserWithNamespacesVars { pub namespace_role: Option, } #[derive(cynic::QueryFragment, Debug)] - #[cynic(graphql_type = "Query", variables = "GetCurrentUserVars")] - pub struct GetCurrentUser { + #[cynic(graphql_type = "Query", variables = "GetCurrentUserWithNamespacesVars")] + pub struct GetCurrentUserWithNamespaces { pub viewer: Option, } @@ -447,7 +453,7 @@ mod queries { } #[derive(cynic::QueryFragment, Debug, Clone, Serialize)] - #[cynic(graphql_type = "User", variables = "GetCurrentUserVars")] + #[cynic(graphql_type = "User", variables = "GetCurrentUserWithNamespacesVars")] pub struct UserWithNamespaces { pub id: cynic::Id, pub username: String, diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index ede489f50..8549e0f03 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -229,7 +229,7 @@ tun-tap = { version = "0.1.3", features = ["tokio"], optional = true } clap_complete = "4.5.2" clap_mangen = "0.2.20" -zip = { version = "1.2.3", default-features = false, features = ["deflate"] } +zip = { version = "2.1.3", default-features = false, features = ["deflate"] } # NOTE: Must use different features for clap because the "color" feature does not # work on wasi due to the anstream dependency not compiling. diff --git a/lib/cli/src/commands/app/util.rs b/lib/cli/src/commands/app/util.rs index 514f0c286..c4f93c94b 100644 --- a/lib/cli/src/commands/app/util.rs +++ b/lib/cli/src/commands/app/util.rs @@ -23,7 +23,7 @@ pub enum AppIdent { /// Backend app VERSION id like "dav_xxysw34234" AppVersionId(String), NamespacedName(String, String), - Alias(String), + Name(String), } impl AppIdent { @@ -40,9 +40,18 @@ impl AppIdent { .with_context(|| format!("Could not query for app version id '{}'", id))?; Ok(app) } - AppIdent::Alias(name) => wasmer_api::query::get_app_by_alias(client, name.clone()) - .await? - .with_context(|| format!("Could not find app with name '{name}'")), + AppIdent::Name(name) => { + // The API only allows to query by owner + name, + // so default to the current user as the owner. + // To to so the username must first be retrieved. + let user = wasmer_api::query::current_user(client) + .await? + .context("not logged in")?; + + wasmer_api::query::get_app(client, user.username, name.clone()) + .await? + .with_context(|| format!("Could not find app with name '{name}'")) + } AppIdent::NamespacedName(owner, name) => { wasmer_api::query::get_app(client, owner.clone(), name.clone()) .await? @@ -80,7 +89,7 @@ impl std::str::FromStr for AppIdent { } } } else { - Ok(Self::Alias(s.to_string())) + Ok(Self::Name(s.to_string())) } } } @@ -160,8 +169,10 @@ impl AppIdentOpts { let ident = if let Some(id) = &config.app_id { AppIdent::AppId(id.clone()) + } else if let Some(owner) = &config.owner { + AppIdent::NamespacedName(owner.clone(), config.name.clone()) } else { - AppIdent::Alias(config.name.clone()) + AppIdent::Name(config.name.clone()) }; Ok(ResolvedAppIdent::Config { @@ -197,7 +208,7 @@ mod tests { ); assert_eq!( AppIdent::from_str("lala").unwrap(), - AppIdent::Alias("lala".to_string()), + AppIdent::Name("lala".to_string()), ); assert_eq!( diff --git a/lib/config/src/app/http.rs b/lib/config/src/app/http.rs index 15e85ade5..b3d9adc4b 100644 --- a/lib/config/src/app/http.rs +++ b/lib/config/src/app/http.rs @@ -12,6 +12,10 @@ pub struct HttpRequest { #[serde(skip_serializing_if = "Option::is_none")] pub method: Option, + /// HTTP headers added to the request. + #[serde(skip_serializing_if = "Option::is_none")] + pub headers: Option>, + /// Request body as a string. #[serde(skip_serializing_if = "Option::is_none")] pub body: Option, @@ -26,6 +30,15 @@ pub struct HttpRequest { pub expect: Option, } +/// Definition for an HTTP header. +#[derive( + schemars::JsonSchema, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Debug, +)] +pub struct HttpHeader { + pub name: String, + pub value: String, +} + /// Validation checks for an [`HttpRequest`]. #[derive( schemars::JsonSchema, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Debug, diff --git a/lib/config/src/app/mod.rs b/lib/config/src/app/mod.rs index 5052e655b..4298622ce 100644 --- a/lib/config/src/app/mod.rs +++ b/lib/config/src/app/mod.rs @@ -195,6 +195,13 @@ pub struct AppConfigCapabilityMapV1 { /// Enables app bootstrapping with startup snapshots. #[serde(skip_serializing_if = "Option::is_none")] pub instaboot: Option, + + /// Additional unknown capabilities. + /// + /// This provides a small bit of forwards compatibility for newly added + /// capabilities. + #[serde(flatten)] + pub other: HashMap, } /// Memory capability settings. @@ -234,7 +241,17 @@ pub struct AppConfigCapabilityInstaBootV1 { /// /// NOTE: if no requests are configured, then a single HTTP /// request to '/' will be performed instead. + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub requests: Vec, + + /// Maximum age of snapshots. + /// + /// Format: 5m, 1h, 2d, ... + /// + /// After the specified time new snapshots will be created, and the old + /// ones discarded. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_age: Option, } #[cfg(test)] diff --git a/lib/wasix/src/bin_factory/exec.rs b/lib/wasix/src/bin_factory/exec.rs index fcdc72172..2dd675796 100644 --- a/lib/wasix/src/bin_factory/exec.rs +++ b/lib/wasix/src/bin_factory/exec.rs @@ -29,16 +29,22 @@ pub async fn spawn_exec( env: WasiEnv, runtime: &Arc, ) -> Result { - // Load the WASM - let wasm = spawn_load_wasm(&env, &binary, name).await?; - - // Load the module - let module = spawn_load_module(&env, name, wasm, runtime).await?; - - // Spawn union the file system spawn_union_fs(&env, &binary).await?; - // Now run the module + let wasm = spawn_load_wasm(&env, &binary, name).await?; + + spawn_exec_wasm(wasm, name, env, runtime).await +} + +#[tracing::instrument(level = "trace", skip_all, fields(%name))] +pub async fn spawn_exec_wasm( + wasm: &[u8], + name: &str, + env: WasiEnv, + runtime: &Arc, +) -> Result { + let module = spawn_load_module(&env, name, wasm, runtime).await?; + spawn_exec_module(module, env, runtime) } diff --git a/lib/wasix/src/bin_factory/mod.rs b/lib/wasix/src/bin_factory/mod.rs index 16311cd4f..0cb8cee8e 100644 --- a/lib/wasix/src/bin_factory/mod.rs +++ b/lib/wasix/src/bin_factory/mod.rs @@ -8,6 +8,7 @@ use std::{ }; use anyhow::Context; +use exec::spawn_exec_wasm; use virtual_fs::{AsyncReadExt, FileSystem}; use wasmer::FunctionEnvMut; use wasmer_wasix_types::wasi::Errno; @@ -52,54 +53,18 @@ impl BinFactory { cache.insert(name.to_string(), Some(binary)); } - // TODO: remove allow once BinFactory is refactored - // currently fine because a BinFactory is only used by a single process tree #[allow(clippy::await_holding_lock)] pub async fn get_binary( &self, name: &str, fs: Option<&dyn FileSystem>, ) -> Option { - let name = name.to_string(); - - // Fast path - { - let cache = self.local.read().unwrap(); - if let Some(data) = cache.get(&name) { - return data.clone(); - } - } - - // Slow path - let mut cache = self.local.write().unwrap(); - - // Check the cache - if let Some(data) = cache.get(&name) { - return data.clone(); - } - - // Check the filesystem for the file - if name.starts_with('/') { - if let Some(fs) = fs { - match load_package_from_filesystem(fs, name.as_ref(), self.runtime()).await { - Ok(pkg) => { - cache.insert(name, Some(pkg.clone())); - return Some(pkg); - } - Err(e) => { - tracing::warn!( - path = name, - error = &*e, - "Unable to load the package from disk" - ); - } - } - } - } - - // NAK - cache.insert(name, None); - None + self.get_executable(name, fs) + .await + .and_then(|executable| match executable { + Executable::Wasm(_) => None, + Executable::BinaryPackage(pkg) => Some(pkg), + }) } pub fn spawn<'a>( @@ -111,7 +76,7 @@ impl BinFactory { Box::pin(async move { // Find the binary (or die trying) and make the spawn type let res = self - .get_binary(name.as_str(), Some(env.fs_root())) + .get_executable(name.as_str(), Some(env.fs_root())) .await .ok_or_else(|| SpawnError::BinaryNotFound { binary: name.clone(), @@ -119,10 +84,17 @@ impl BinFactory { if res.is_err() { env.on_exit(Some(Errno::Noent.into())).await; } - let binary = res?; + let executable = res?; // Execute - spawn_exec(binary, name.as_str(), store, env, &self.runtime).await + match executable { + Executable::Wasm(bytes) => { + spawn_exec_wasm(&bytes, name.as_str(), env, &self.runtime).await + } + Executable::BinaryPackage(pkg) => { + spawn_exec(pkg, name.as_str(), store, env, &self.runtime).await + } + } }) } @@ -145,13 +117,71 @@ impl BinFactory { } Err(SpawnError::BinaryNotFound { binary: name }) } + + // TODO: remove allow once BinFactory is refactored + // currently fine because a BinFactory is only used by a single process tree + #[allow(clippy::await_holding_lock)] + pub async fn get_executable( + &self, + name: &str, + fs: Option<&dyn FileSystem>, + ) -> Option { + let name = name.to_string(); + + // Fast path + { + let cache = self.local.read().unwrap(); + if let Some(data) = cache.get(&name) { + data.clone().map(Executable::BinaryPackage); + } + } + + // Slow path + let mut cache = self.local.write().unwrap(); + + // Check the cache + if let Some(data) = cache.get(&name) { + return data.clone().map(Executable::BinaryPackage); + } + + // Check the filesystem for the file + if name.starts_with('/') { + if let Some(fs) = fs { + match load_executable_from_filesystem(fs, name.as_ref(), self.runtime()).await { + Ok(executable) => { + if let Executable::BinaryPackage(pkg) = &executable { + cache.insert(name, Some(pkg.clone())); + } + + return Some(executable); + } + Err(e) => { + tracing::warn!( + path = name, + error = &*e, + "Unable to load the package from disk" + ); + } + } + } + } + + // NAK + cache.insert(name, None); + None + } } -async fn load_package_from_filesystem( +pub enum Executable { + Wasm(bytes::Bytes), + BinaryPackage(BinaryPackage), +} + +async fn load_executable_from_filesystem( fs: &dyn FileSystem, path: &Path, rt: &(dyn Runtime + Send + Sync), -) -> Result { +) -> Result { let mut f = fs .new_open_options() .read(true) @@ -161,10 +191,15 @@ async fn load_package_from_filesystem( let mut data = Vec::with_capacity(f.size() as usize); f.read_to_end(&mut data).await.context("Read failed")?; - let container = Container::from_bytes(data).context("Unable to parse the WEBC file")?; - let pkg = BinaryPackage::from_webc(&container, rt) - .await - .context("Unable to load the package")?; + let bytes: bytes::Bytes = data.into(); - Ok(pkg) + if let Ok(container) = Container::from_bytes(bytes.clone()) { + let pkg = BinaryPackage::from_webc(&container, rt) + .await + .context("Unable to load the package")?; + + Ok(Executable::BinaryPackage(pkg)) + } else { + Ok(Executable::Wasm(bytes)) + } } diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs index 936fad505..731a4e4aa 100644 --- a/lib/wasix/src/lib.rs +++ b/lib/wasix/src/lib.rs @@ -531,6 +531,7 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::), "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), + "proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), @@ -652,6 +653,7 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::), "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), + "proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), diff --git a/lib/wasix/src/syscalls/mod.rs b/lib/wasix/src/syscalls/mod.rs index 90e75752f..2e0b5c5d6 100644 --- a/lib/wasix/src/syscalls/mod.rs +++ b/lib/wasix/src/syscalls/mod.rs @@ -94,7 +94,10 @@ pub(crate) use self::types::{ }, *, }; -use self::{state::WasiInstanceGuardMemory, utils::WasiDummyWaker}; +use self::{ + state::{conv_env_vars, WasiInstanceGuardMemory}, + utils::WasiDummyWaker, +}; pub(crate) use crate::os::task::{ process::{WasiProcessId, WasiProcessWait}, thread::{WasiThread, WasiThreadId}, @@ -1481,7 +1484,11 @@ where } // Function to prepare the WASI environment -pub(crate) fn _prepare_wasi(wasi_env: &mut WasiEnv, args: Option>) { +pub(crate) fn _prepare_wasi( + wasi_env: &mut WasiEnv, + args: Option>, + envs: Option>, +) { // Swap out the arguments with the new ones if let Some(args) = args { let mut wasi_state = wasi_env.state.fork(); @@ -1489,6 +1496,38 @@ pub(crate) fn _prepare_wasi(wasi_env: &mut WasiEnv, args: Option>) { wasi_env.state = Arc::new(wasi_state); } + // Update the env vars + if let Some(envs) = envs { + let mut guard = wasi_env.state.envs.lock().unwrap(); + + let mut existing_envs = guard + .iter() + .map(|b| { + let string = String::from_utf8_lossy(b); + let (key, val) = string.split_once('=').expect("env var is malformed"); + + (key.to_string(), val.to_string().as_bytes().to_vec()) + }) + .collect::>(); + + for (key, val) in envs { + let val = val.as_bytes().to_vec(); + match existing_envs + .iter_mut() + .find(|(existing_key, _)| existing_key == &key) + { + Some((_, existing_val)) => *existing_val = val, + None => existing_envs.push((key, val)), + } + } + + let envs = conv_env_vars(existing_envs); + + *guard = envs; + + drop(guard) + } + // Close any files after the STDERR that are not preopened let close_fds = { let preopen_fds = { diff --git a/lib/wasix/src/syscalls/wasi/path_open.rs b/lib/wasix/src/syscalls/wasi/path_open.rs index 65b994d56..e3aceea85 100644 --- a/lib/wasix/src/syscalls/wasi/path_open.rs +++ b/lib/wasix/src/syscalls/wasi/path_open.rs @@ -205,6 +205,8 @@ pub(crate) fn path_open_internal( open_options.options(minimum_rights.clone()); + let orig_path = path; + let inode = if let Ok(inode) = maybe_inode { // Happy path, we found the file we're trying to open let processing_inode = inode.clone(); @@ -228,7 +230,7 @@ pub(crate) fn path_open_internal( assert!(handle.is_some()); return Ok(Ok(*special_fd)); } - if o_flags.contains(Oflags::DIRECTORY) { + if o_flags.contains(Oflags::DIRECTORY) || orig_path.ends_with('/') { return Ok(Err(Errno::Notdir)); } @@ -330,11 +332,13 @@ pub(crate) fn path_open_internal( // once we got the data we need from the parent, we lookup the host file // todo: extra check that opening with write access is okay let handle = { + // We set create_new because the path already didn't resolve to an existing file, + // so it must be created. let open_options = open_options .read(minimum_rights.read) .append(minimum_rights.append) .write(minimum_rights.write) - .create_new(minimum_rights.create_new); + .create_new(true); if minimum_rights.read { open_flags |= Fd::READ; @@ -349,9 +353,19 @@ pub(crate) fn path_open_internal( open_flags |= Fd::TRUNCATE; } - Some(wasi_try_ok_ok!(open_options - .open(&new_file_host_path) - .map_err(|e| { fs_error_into_wasi_err(e) }))) + match open_options.open(&new_file_host_path) { + Ok(handle) => Some(handle), + Err(err) => { + // Even though the file does not exist, it still failed to create with + // `AlreadyExists` error. This can happen if the path resolves to a + // symlink that points outside the FS sandbox. + if err == FsError::AlreadyExists { + return Ok(Err(Errno::Perm)); + } + + return Ok(Err(fs_error_into_wasi_err(err))); + } + } }; let new_inode = { diff --git a/lib/wasix/src/syscalls/wasix/mod.rs b/lib/wasix/src/syscalls/wasix/mod.rs index 01b03aadc..520e024be 100644 --- a/lib/wasix/src/syscalls/wasix/mod.rs +++ b/lib/wasix/src/syscalls/wasix/mod.rs @@ -22,6 +22,7 @@ mod port_route_list; mod port_route_remove; mod port_unbridge; mod proc_exec; +mod proc_exec2; mod proc_fork; mod proc_id; mod proc_join; @@ -90,6 +91,7 @@ pub use port_route_list::*; pub use port_route_remove::*; pub use port_unbridge::*; pub use proc_exec::*; +pub use proc_exec2::*; pub use proc_fork::*; pub use proc_id::*; pub use proc_join::*; diff --git a/lib/wasix/src/syscalls/wasix/proc_exec.rs b/lib/wasix/src/syscalls/wasix/proc_exec.rs index 8e2e71f0c..d5c433298 100644 --- a/lib/wasix/src/syscalls/wasix/proc_exec.rs +++ b/lib/wasix/src/syscalls/wasix/proc_exec.rs @@ -25,230 +25,13 @@ pub fn proc_exec( args: WasmPtr, args_len: M::Offset, ) -> Result<(), WasiError> { - WasiEnv::process_signals_and_exit(&mut ctx)?; - - // If we were just restored the stack then we were woken after a deep sleep - if let Some(exit_code) = unsafe { handle_rewind::(&mut ctx) } { - // We should never get here as the process will be termined - // in the `WasiEnv::process_signals_and_exit()` call - let exit_code = ExitCode::from_native(exit_code); - ctx.data().process.terminate(exit_code); - return Err(WasiError::Exit(exit_code)); - } - - let memory = unsafe { ctx.data().memory_view(&ctx) }; - let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| { - warn!("failed to execve as the name could not be read - {}", err); - WasiError::Exit(Errno::Inval.into()) - })?; - Span::current().record("name", name.as_str()); - let args = args.read_utf8_string(&memory, args_len).map_err(|err| { - warn!("failed to execve as the args could not be read - {}", err); - WasiError::Exit(Errno::Inval.into()) - })?; - let args: Vec<_> = args - .split(&['\n', '\r']) - .map(|a| a.to_string()) - .filter(|a| !a.is_empty()) - .collect(); - - // Convert relative paths into absolute paths - if name.starts_with("./") { - name = ctx.data().state.fs.relative_path_to_absolute(name); - } - trace!(name); - - // Convert the preopen directories - let preopen = ctx.data().state.preopen.clone(); - - // Get the current working directory - let (_, cur_dir) = { - let (memory, state, inodes) = - unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) }; - match state.fs.get_current_dir(inodes, crate::VIRTUAL_ROOT_FD) { - Ok(a) => a, - Err(err) => { - warn!("failed to create subprocess for fork - {}", err); - return Err(WasiError::Exit(err.into())); - } - } - }; - - let new_store = ctx.data().runtime.new_store(); - - // If we are in a vfork we need to first spawn a subprocess of this type - // with the forked WasiEnv, then do a longjmp back to the vfork point. - if let Some(mut vfork) = ctx.data_mut().vfork.take() { - // We will need the child pid later - let child_process = ctx.data().process.clone(); - let child_pid = child_process.pid(); - let child_finished = child_process.finished; - - // Restore the WasiEnv to the point when we vforked - vfork.env.swap_inner(ctx.data_mut()); - std::mem::swap(vfork.env.as_mut(), ctx.data_mut()); - let mut wasi_env = *vfork.env; - wasi_env.owned_handles.push(vfork.handle); - _prepare_wasi(&mut wasi_env, Some(args)); - - // Recrod the stack offsets before we give up ownership of the wasi_env - let stack_lower = wasi_env.layout.stack_lower; - let stack_upper = wasi_env.layout.stack_upper; - - // Spawn a new process with this current execution environment - let mut err_exit_code: ExitCode = Errno::Success.into(); - - { - let bin_factory = Box::new(ctx.data().bin_factory.clone()); - let tasks = wasi_env.tasks().clone(); - - let mut new_store = Some(new_store); - let mut config = Some(wasi_env); - - match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut new_store, &mut config) { - Ok(a) => {} - Err(err) => { - if !err.is_not_found() { - error!("builtin failed - {}", err); - } - - let new_store = new_store.take().unwrap(); - let env = config.take().unwrap(); - - let name_inner = name.clone(); - __asyncify_light(ctx.data(), None, async { - let ret = bin_factory.spawn(name_inner, new_store, env).await; - match ret { - Ok(ret) => { - trace!(%child_pid, "spawned sub-process"); - } - Err(err) => { - err_exit_code = conv_spawn_err_to_exit_code(&err); - - debug!(%child_pid, "process failed with (err={})", err_exit_code); - child_finished.set_finished(Ok(err_exit_code)); - - warn!( - "failed to execve as the process could not be spawned (vfork) - {}", - err - ); - let _ = unsafe { - stderr_write( - &ctx, - format!( - "wasm execute failed [{}] - {}\n", - name.as_str(), - err - ) - .as_bytes(), - ) - } - .await; - } - } - - Ok(()) - }); - } - } - }; - - // Jump back to the vfork point and current on execution - // note: fork does not return any values hence passing `()` - let memory_stack = vfork.memory_stack.freeze(); - let rewind_stack = vfork.rewind_stack.freeze(); - let store_data = vfork.store_data; - unwind::(ctx, move |mut ctx, _, _| { - // Rewind the stack - match rewind::( - ctx, - memory_stack, - rewind_stack, - store_data, - ForkResult { - pid: child_pid.raw() as Pid, - ret: Errno::Success, - }, - ) { - Errno::Success => OnCalledAction::InvokeAgain, - err => { - warn!("fork failed - could not rewind the stack - errno={}", err); - OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) - } - } - })?; - Ok(()) - } - // Otherwise we need to unwind the stack to get out of the current executing - // callstack, steal the memory/WasiEnv and switch it over to a new thread - // on the new module - else { - // Prepare the environment - let mut wasi_env = ctx.data().clone(); - _prepare_wasi(&mut wasi_env, Some(args)); - - // Get a reference to the runtime - let bin_factory = ctx.data().bin_factory.clone(); - let tasks = wasi_env.tasks().clone(); - - // Create the process and drop the context - let bin_factory = Box::new(ctx.data().bin_factory.clone()); - - let mut new_store = Some(new_store); - let mut builder = Some(wasi_env); - - let process = match bin_factory.try_built_in( - name.clone(), - Some(&ctx), - &mut new_store, - &mut builder, - ) { - Ok(a) => Ok(a), - Err(err) => { - if !err.is_not_found() { - error!("builtin failed - {}", err); - } - - let new_store = new_store.take().unwrap(); - let env = builder.take().unwrap(); - - // Spawn a new process with this current execution environment - InlineWaker::block_on(bin_factory.spawn(name, new_store, env)) - } - }; - - match process { - Ok(mut process) => { - // If we support deep sleeping then we switch to deep sleep mode - let env = ctx.data(); - let thread = env.thread.clone(); - - // The poller will wait for the process to actually finish - let res = __asyncify_with_deep_sleep::(ctx, async move { - process - .wait_finished() - .await - .unwrap_or_else(|_| Errno::Child.into()) - .to_native() - })?; - match res { - AsyncifyAction::Finish(mut ctx, result) => { - // When we arrive here the process should already be terminated - let exit_code = ExitCode::from_native(result); - ctx.data().process.terminate(exit_code); - WasiEnv::process_signals_and_exit(&mut ctx)?; - Err(WasiError::Exit(Errno::Unknown.into())) - } - AsyncifyAction::Unwind => Ok(()), - } - } - Err(err) => { - warn!( - "failed to execve as the process could not be spawned (fork)[0] - {}", - err - ); - Err(WasiError::Exit(Errno::Noexec.into())) - } - } - } + proc_exec2( + ctx, + name, + name_len, + args, + args_len, + WasmPtr::null(), + M::ZERO, + ) } diff --git a/lib/wasix/src/syscalls/wasix/proc_exec2.rs b/lib/wasix/src/syscalls/wasix/proc_exec2.rs new file mode 100644 index 000000000..3d909b466 --- /dev/null +++ b/lib/wasix/src/syscalls/wasix/proc_exec2.rs @@ -0,0 +1,280 @@ +use wasmer::FromToNativeWasmType; + +use super::*; +use crate::{ + os::task::{OwnedTaskStatus, TaskStatus}, + syscalls::*, +}; + +/// Replaces the current process with a new process +/// +/// ## Parameters +/// +/// * `name` - Name of the process to be spawned +/// * `args` - List of the arguments to pass the process +/// (entries are separated by line feeds) +/// * `envs` - List of the environment variables to pass process +/// +/// ## Return +/// +/// Returns a bus process id that can be used to invoke calls +#[instrument(level = "debug", skip_all, fields(name = field::Empty, %args_len), ret)] +pub fn proc_exec2( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, + args: WasmPtr, + args_len: M::Offset, + envs: WasmPtr, + envs_len: M::Offset, +) -> Result<(), WasiError> { + WasiEnv::process_signals_and_exit(&mut ctx)?; + + // If we were just restored the stack then we were woken after a deep sleep + if let Some(exit_code) = unsafe { handle_rewind::(&mut ctx) } { + // We should never get here as the process will be termined + // in the `WasiEnv::process_signals_and_exit()` call + let exit_code = ExitCode::from_native(exit_code); + ctx.data().process.terminate(exit_code); + return Err(WasiError::Exit(exit_code)); + } + + let memory = unsafe { ctx.data().memory_view(&ctx) }; + let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| { + warn!("failed to execve as the name could not be read - {}", err); + WasiError::Exit(Errno::Inval.into()) + })?; + Span::current().record("name", name.as_str()); + let args = args.read_utf8_string(&memory, args_len).map_err(|err| { + warn!("failed to execve as the args could not be read - {}", err); + WasiError::Exit(Errno::Inval.into()) + })?; + let args: Vec<_> = args + .split(&['\n', '\r']) + .map(|a| a.to_string()) + .filter(|a| !a.is_empty()) + .collect(); + + let envs = if !envs.is_null() { + let envs = envs.read_utf8_string(&memory, envs_len).map_err(|err| { + warn!("failed to execve as the envs could not be read - {}", err); + WasiError::Exit(Errno::Inval.into()) + })?; + + let envs = envs + .split(&['\n', '\r']) + .map(|a| a.to_string()) + .filter(|a| !a.is_empty()); + + let mut vec = vec![]; + for env in envs { + let (key, value) = env.split_once('=').unwrap(); + + vec.push((key.to_string(), value.to_string())); + } + + Some(vec) + } else { + None + }; + + // Convert relative paths into absolute paths + if name.starts_with("./") { + name = ctx.data().state.fs.relative_path_to_absolute(name); + } + trace!(name); + + // Convert the preopen directories + let preopen = ctx.data().state.preopen.clone(); + + // Get the current working directory + let (_, cur_dir) = { + let (memory, state, inodes) = + unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + match state.fs.get_current_dir(inodes, crate::VIRTUAL_ROOT_FD) { + Ok(a) => a, + Err(err) => { + warn!("failed to create subprocess for fork - {}", err); + return Err(WasiError::Exit(err.into())); + } + } + }; + + let new_store = ctx.data().runtime.new_store(); + + // If we are in a vfork we need to first spawn a subprocess of this type + // with the forked WasiEnv, then do a longjmp back to the vfork point. + if let Some(mut vfork) = ctx.data_mut().vfork.take() { + // We will need the child pid later + let child_process = ctx.data().process.clone(); + let child_pid = child_process.pid(); + let child_finished = child_process.finished; + + // Restore the WasiEnv to the point when we vforked + vfork.env.swap_inner(ctx.data_mut()); + std::mem::swap(vfork.env.as_mut(), ctx.data_mut()); + let mut wasi_env = *vfork.env; + wasi_env.owned_handles.push(vfork.handle); + _prepare_wasi(&mut wasi_env, Some(args), envs); + + // Recrod the stack offsets before we give up ownership of the wasi_env + let stack_lower = wasi_env.layout.stack_lower; + let stack_upper = wasi_env.layout.stack_upper; + + // Spawn a new process with this current execution environment + let mut err_exit_code: ExitCode = Errno::Success.into(); + + { + let bin_factory = Box::new(ctx.data().bin_factory.clone()); + let tasks = wasi_env.tasks().clone(); + + let mut new_store = Some(new_store); + let mut config = Some(wasi_env); + + match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut new_store, &mut config) { + Ok(a) => {} + Err(err) => { + if !err.is_not_found() { + error!("builtin failed - {}", err); + } + + let new_store = new_store.take().unwrap(); + let env = config.take().unwrap(); + + let name_inner = name.clone(); + __asyncify_light(ctx.data(), None, async { + let ret = bin_factory.spawn(name_inner, new_store, env).await; + match ret { + Ok(ret) => { + trace!(%child_pid, "spawned sub-process"); + } + Err(err) => { + err_exit_code = conv_spawn_err_to_exit_code(&err); + + debug!(%child_pid, "process failed with (err={})", err_exit_code); + child_finished.set_finished(Ok(err_exit_code)); + + warn!( + "failed to execve as the process could not be spawned (vfork) - {}", + err + ); + let _ = unsafe { + stderr_write( + &ctx, + format!( + "wasm execute failed [{}] - {}\n", + name.as_str(), + err + ) + .as_bytes(), + ) + } + .await; + } + } + + Ok(()) + }); + } + } + }; + + // Jump back to the vfork point and current on execution + // note: fork does not return any values hence passing `()` + let memory_stack = vfork.memory_stack.freeze(); + let rewind_stack = vfork.rewind_stack.freeze(); + let store_data = vfork.store_data; + unwind::(ctx, move |mut ctx, _, _| { + // Rewind the stack + match rewind::( + ctx, + memory_stack, + rewind_stack, + store_data, + ForkResult { + pid: child_pid.raw() as Pid, + ret: Errno::Success, + }, + ) { + Errno::Success => OnCalledAction::InvokeAgain, + err => { + warn!("fork failed - could not rewind the stack - errno={}", err); + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) + } + } + })?; + Ok(()) + } + // Otherwise we need to unwind the stack to get out of the current executing + // callstack, steal the memory/WasiEnv and switch it over to a new thread + // on the new module + else { + // Prepare the environment + let mut wasi_env = ctx.data().clone(); + _prepare_wasi(&mut wasi_env, Some(args), envs); + + // Get a reference to the runtime + let bin_factory = ctx.data().bin_factory.clone(); + let tasks = wasi_env.tasks().clone(); + + // Create the process and drop the context + let bin_factory = Box::new(ctx.data().bin_factory.clone()); + + let mut new_store = Some(new_store); + let mut builder = Some(wasi_env); + + let process = match bin_factory.try_built_in( + name.clone(), + Some(&ctx), + &mut new_store, + &mut builder, + ) { + Ok(a) => Ok(a), + Err(err) => { + if !err.is_not_found() { + error!("builtin failed - {}", err); + } + + let new_store = new_store.take().unwrap(); + let env = builder.take().unwrap(); + + // Spawn a new process with this current execution environment + InlineWaker::block_on(bin_factory.spawn(name, new_store, env)) + } + }; + + match process { + Ok(mut process) => { + // If we support deep sleeping then we switch to deep sleep mode + let env = ctx.data(); + let thread = env.thread.clone(); + + // The poller will wait for the process to actually finish + let res = __asyncify_with_deep_sleep::(ctx, async move { + process + .wait_finished() + .await + .unwrap_or_else(|_| Errno::Child.into()) + .to_native() + })?; + match res { + AsyncifyAction::Finish(mut ctx, result) => { + // When we arrive here the process should already be terminated + let exit_code = ExitCode::from_native(result); + ctx.data().process.terminate(exit_code); + WasiEnv::process_signals_and_exit(&mut ctx)?; + Err(WasiError::Exit(Errno::Unknown.into())) + } + AsyncifyAction::Unwind => Ok(()), + } + } + Err(err) => { + warn!( + "failed to execve as the process could not be spawned (fork)[0] - {}", + err + ); + Err(WasiError::Exit(Errno::Noexec.into())) + } + } + } +} diff --git a/tests/wasi-fyi/fs_open_trailing_slash.rs b/tests/wasi-fyi/fs_open_trailing_slash.rs new file mode 100644 index 000000000..1493e145f --- /dev/null +++ b/tests/wasi-fyi/fs_open_trailing_slash.rs @@ -0,0 +1,57 @@ +#[link(wasm_import_module = "wasi_snapshot_preview1")] +extern "C" { + pub fn path_open( + fd: i32, + dirflags: i32, + path: i32, + path_len: i32, + oflags: i32, + fs_rights_base: i64, + fs_rights_inheriting: i64, + fdflags: i32, + result_fd: i32, + ) -> i32; +} + +const ERRNO_SUCCESS: i32 = 0; +const ERRNO_NOTDIR: i32 = 54; +const RIGHTS_FD_READ: i64 = 2; + +fn main() { + unsafe { + let fd = 5; + let path_ok = "fyi/fs_open_trailing_slash.dir/file"; + let path_bad = "fyi/fs_open_trailing_slash.dir/file/"; + let errno = path_open( + fd, + 0, + path_ok.as_ptr() as i32, + path_ok.len() as i32, + 0, + RIGHTS_FD_READ, + 0, + 0, + 1024, + ); + assert_eq!( + errno, ERRNO_SUCCESS, + "opening a file without a trailing slash works" + ); + + let errno = path_open( + fd, + 0, + path_bad.as_ptr() as i32, + path_bad.len() as i32, + 0, + RIGHTS_FD_READ, + 0, + 0, + 1024, + ); + assert_eq!( + errno, ERRNO_NOTDIR, + "opening a regular file with a trailing slash should fail" + ); + } +} diff --git a/tests/wasi-fyi/fs_sandbox_symlink.rs b/tests/wasi-fyi/fs_sandbox_symlink.rs new file mode 100644 index 000000000..0863ad7d6 --- /dev/null +++ b/tests/wasi-fyi/fs_sandbox_symlink.rs @@ -0,0 +1,53 @@ +#[link(wasm_import_module = "wasi_snapshot_preview1")] +extern "C" { + pub fn path_open( + fd: i32, + dirflags: i32, + path: i32, + path_len: i32, + oflags: i32, + fs_rights_base: i64, + fs_rights_inheriting: i64, + fdflags: i32, + result_fd: i32, + ) -> i32; +} + +const ERRNO_PERM: i32 = 63; +const LOOKUPFLAGS_SYMLINK_FOLLOW: i32 = 1; +const OFLAGS_CREAT: i32 = 1; +const RIGHTS_FD_WRITE: i64 = 64; + +fn main() { + let link_path = "fyi/fs_sandbox_symlink.dir/link"; + let link_path_non_existant = "fyi/fs_sandbox_symlink.dir/link-non-existant"; + let mut fd: i32 = 0; + + unsafe { + let errno = path_open( + 5, + LOOKUPFLAGS_SYMLINK_FOLLOW, + link_path.as_ptr() as i32, + link_path.len() as i32, + OFLAGS_CREAT, + RIGHTS_FD_WRITE, + 0, + 0, + &mut fd as *mut i32 as i32, + ); + assert_eq!(errno, ERRNO_PERM, "symlink cannot escape fs sandbox"); + + let errno = path_open( + 5, + LOOKUPFLAGS_SYMLINK_FOLLOW, + link_path_non_existant.as_ptr() as i32, + link_path_non_existant.len() as i32, + OFLAGS_CREAT, + RIGHTS_FD_WRITE, + 0, + 0, + &mut fd as *mut i32 as i32, + ); + assert_eq!(errno, ERRNO_PERM, "symlink cannot escape fs sandbox"); + } +} diff --git a/tests/wasi-fyi/test_fs/fyi/fs_open_trailing_slash.dir/file b/tests/wasi-fyi/test_fs/fyi/fs_open_trailing_slash.dir/file new file mode 100644 index 000000000..e69de29bb diff --git a/tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link b/tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link-non-existant b/tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link-non-existant new file mode 120000 index 000000000..453e39000 --- /dev/null +++ b/tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link-non-existant @@ -0,0 +1 @@ +../../non-existant \ No newline at end of file diff --git a/tests/wasix/cwd-to-home/expected b/tests/wasix/cwd-to-home/expected deleted file mode 100644 index c22708346..000000000 --- a/tests/wasix/cwd-to-home/expected +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/tests/wasix/cwd-to-home/run.sh b/tests/wasix/cwd-to-home/run.sh index 474bd170e..e3f5a6084 100755 --- a/tests/wasix/cwd-to-home/run.sh +++ b/tests/wasix/cwd-to-home/run.sh @@ -2,4 +2,4 @@ $WASMER -q run main.wasm --dir=. > output -diff -u output expected 1>/dev/null \ No newline at end of file +printf "0" | diff -u output - 1>/dev/null \ No newline at end of file diff --git a/tests/wasix/proc_exec/main.c b/tests/wasix/proc_exec/main.c new file mode 100644 index 000000000..e430c0ba4 --- /dev/null +++ b/tests/wasix/proc_exec/main.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc > 1 && argv[1] != NULL) + { + return 0; + } + + pid_t pid = fork(); + if (pid == -1) + { + exit(EXIT_FAILURE); + } + else if (pid == 0) + { + char *newargv[] = {argv[0], "child", NULL}; + + execv("/code/main.wasm", newargv); + + exit(EXIT_FAILURE); + } + else + { + int status; + waitpid(pid, &status, 0); + printf("%d", status); + } + + return 0; +} \ No newline at end of file diff --git a/tests/wasix/proc_exec/run.sh b/tests/wasix/proc_exec/run.sh new file mode 100755 index 000000000..2f9d5723c --- /dev/null +++ b/tests/wasix/proc_exec/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +$WASMER -q run main.wasm --mapdir=/code:. > output + +printf "0" | diff -u output - 1>/dev/null \ No newline at end of file diff --git a/tests/wasix/proc_exec2/main.c b/tests/wasix/proc_exec2/main.c new file mode 100644 index 000000000..b82c7117a --- /dev/null +++ b/tests/wasix/proc_exec2/main.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc > 1 && argv[1] != NULL) + { + char *bar = getenv("foo"); + + return (bar == NULL); + } + + pid_t pid = fork(); + if (pid == -1) + { + exit(EXIT_FAILURE); + } + else if (pid == 0) + { + char *newargv[] = {argv[0], "child", NULL}; + char *newenviron[] = {"foo=bar", NULL}; + + execve("/code/main.wasm", newargv, newenviron); + + exit(EXIT_FAILURE); + } + else + { + int status; + waitpid(pid, &status, 0); + printf("%d", status); + } + + return 0; +} \ No newline at end of file diff --git a/tests/wasix/proc_exec2/run.sh b/tests/wasix/proc_exec2/run.sh new file mode 100755 index 000000000..2f9d5723c --- /dev/null +++ b/tests/wasix/proc_exec2/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +$WASMER -q run main.wasm --mapdir=/code:. > output + +printf "0" | diff -u output - 1>/dev/null \ No newline at end of file diff --git a/tests/wasix/test.sh b/tests/wasix/test.sh index f3349e46c..1d85eba2b 100755 --- a/tests/wasix/test.sh +++ b/tests/wasix/test.sh @@ -77,6 +77,7 @@ while read dir; do cmd="cd $dir; \ $CC $CFLAGS $LDFLAGS -o main.wasm main.c; \ + wasm-opt -O4 --asyncify -g main.wasm -o main.wasm; \ ./run.sh" if bash -c "$cmd"; then @@ -85,6 +86,6 @@ while read dir; do printf "\rTesting $dir ❌\n" status=1 fi -done < <(find . -mindepth 1 -maxdepth 1 -type d) +done < <(find . -mindepth 1 -maxdepth 1 -type d | sort) exit $status \ No newline at end of file