mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-03 11:18:31 +00:00
Merge branch 'main' of github.com:wasmerio/wasmer
This commit is contained in:
60
Cargo.lock
generated
60
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -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<Option<types::User>, 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<types::GrapheneRole>,
|
||||
) -> Result<types::UserWithNamespaces, anyhow::Error> {
|
||||
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 {
|
||||
.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<Vec<types::Namespace>, anyhow::Error> {
|
||||
let user = client
|
||||
.run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars {
|
||||
.run_graphql(types::GetCurrentUserWithNamespaces::build(
|
||||
types::GetCurrentUserWithNamespacesVars {
|
||||
namespace_role: None,
|
||||
}))
|
||||
},
|
||||
))
|
||||
.await?
|
||||
.viewer
|
||||
.context("not logged in")?;
|
||||
|
||||
@@ -41,14 +41,20 @@ mod queries {
|
||||
Viewer,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query")]
|
||||
pub struct GetCurrentUser {
|
||||
pub viewer: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetCurrentUserVars {
|
||||
pub struct GetCurrentUserWithNamespacesVars {
|
||||
pub namespace_role: Option<GrapheneRole>,
|
||||
}
|
||||
|
||||
#[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<UserWithNamespaces>,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
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?
|
||||
.with_context(|| format!("Could not find app with name '{name}'")),
|
||||
.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!(
|
||||
|
||||
@@ -12,6 +12,10 @@ pub struct HttpRequest {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub method: Option<String>,
|
||||
|
||||
/// HTTP headers added to the request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub headers: Option<Vec<HttpHeader>>,
|
||||
|
||||
/// Request body as a string.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub body: Option<String>,
|
||||
@@ -26,6 +30,15 @@ pub struct HttpRequest {
|
||||
pub expect: Option<HttpRequestExpect>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -195,6 +195,13 @@ pub struct AppConfigCapabilityMapV1 {
|
||||
/// Enables app bootstrapping with startup snapshots.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub instaboot: Option<AppConfigCapabilityInstaBootV1>,
|
||||
|
||||
/// Additional unknown capabilities.
|
||||
///
|
||||
/// This provides a small bit of forwards compatibility for newly added
|
||||
/// capabilities.
|
||||
#[serde(flatten)]
|
||||
pub other: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
/// 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<HttpRequest>,
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -29,16 +29,22 @@ pub async fn spawn_exec(
|
||||
env: WasiEnv,
|
||||
runtime: &Arc<dyn Runtime + Send + Sync + 'static>,
|
||||
) -> Result<TaskJoinHandle, SpawnError> {
|
||||
// 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<dyn Runtime + Send + Sync + 'static>,
|
||||
) -> Result<TaskJoinHandle, SpawnError> {
|
||||
let module = spawn_load_module(&env, name, wasm, runtime).await?;
|
||||
|
||||
spawn_exec_module(module, env, runtime)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BinaryPackage> {
|
||||
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<Executable> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_package_from_filesystem(
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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<BinaryPackage, anyhow::Error> {
|
||||
) -> Result<Executable, anyhow::Error> {
|
||||
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 bytes: bytes::Bytes = data.into();
|
||||
|
||||
if let Ok(container) = Container::from_bytes(bytes.clone()) {
|
||||
let pkg = BinaryPackage::from_webc(&container, rt)
|
||||
.await
|
||||
.context("Unable to load the package")?;
|
||||
|
||||
Ok(pkg)
|
||||
Ok(Executable::BinaryPackage(pkg))
|
||||
} else {
|
||||
Ok(Executable::Wasm(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +531,7 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv<WasiEnv>)
|
||||
"proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::<Memory32>),
|
||||
"proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::<Memory32>),
|
||||
"proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::<Memory32>),
|
||||
"proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::<Memory32>),
|
||||
"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::<Memory32>),
|
||||
@@ -652,6 +653,7 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv<WasiEnv>)
|
||||
"proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::<Memory64>),
|
||||
"proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::<Memory64>),
|
||||
"proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::<Memory64>),
|
||||
"proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::<Memory64>),
|
||||
"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::<Memory64>),
|
||||
|
||||
@@ -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<Vec<String>>) {
|
||||
pub(crate) fn _prepare_wasi(
|
||||
wasi_env: &mut WasiEnv,
|
||||
args: Option<Vec<String>>,
|
||||
envs: Option<Vec<(String, String)>>,
|
||||
) {
|
||||
// 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<Vec<String>>) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -25,230 +25,13 @@ pub fn proc_exec<M: MemorySize>(
|
||||
args: WasmPtr<u8, M>,
|
||||
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::<M, i32>(&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::<M, _>(ctx, move |mut ctx, _, _| {
|
||||
// Rewind the stack
|
||||
match rewind::<M, _>(
|
||||
proc_exec2(
|
||||
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::<M, _, _>(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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
name,
|
||||
name_len,
|
||||
args,
|
||||
args_len,
|
||||
WasmPtr::null(),
|
||||
M::ZERO,
|
||||
)
|
||||
}
|
||||
|
||||
280
lib/wasix/src/syscalls/wasix/proc_exec2.rs
Normal file
280
lib/wasix/src/syscalls/wasix/proc_exec2.rs
Normal file
@@ -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<M: MemorySize>(
|
||||
mut ctx: FunctionEnvMut<'_, WasiEnv>,
|
||||
name: WasmPtr<u8, M>,
|
||||
name_len: M::Offset,
|
||||
args: WasmPtr<u8, M>,
|
||||
args_len: M::Offset,
|
||||
envs: WasmPtr<u8, M>,
|
||||
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::<M, i32>(&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::<M, _>(ctx, move |mut ctx, _, _| {
|
||||
// Rewind the stack
|
||||
match rewind::<M, _>(
|
||||
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::<M, _, _>(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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
tests/wasi-fyi/fs_open_trailing_slash.rs
Normal file
57
tests/wasi-fyi/fs_open_trailing_slash.rs
Normal file
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
53
tests/wasi-fyi/fs_sandbox_symlink.rs
Normal file
53
tests/wasi-fyi/fs_sandbox_symlink.rs
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
1
tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link
Symbolic link
1
tests/wasi-fyi/test_fs/fyi/fs_sandbox_symlink.dir/link
Symbolic link
@@ -0,0 +1 @@
|
||||
../../README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../non-existant
|
||||
@@ -1 +0,0 @@
|
||||
0
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
$WASMER -q run main.wasm --dir=. > output
|
||||
|
||||
diff -u output expected 1>/dev/null
|
||||
printf "0" | diff -u output - 1>/dev/null
|
||||
34
tests/wasix/proc_exec/main.c
Normal file
34
tests/wasix/proc_exec/main.c
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
5
tests/wasix/proc_exec/run.sh
Executable file
5
tests/wasix/proc_exec/run.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
$WASMER -q run main.wasm --mapdir=/code:. > output
|
||||
|
||||
printf "0" | diff -u output - 1>/dev/null
|
||||
37
tests/wasix/proc_exec2/main.c
Normal file
37
tests/wasix/proc_exec2/main.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
5
tests/wasix/proc_exec2/run.sh
Executable file
5
tests/wasix/proc_exec2/run.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
$WASMER -q run main.wasm --mapdir=/code:. > output
|
||||
|
||||
printf "0" | diff -u output - 1>/dev/null
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user