Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement proper executable spawn #5127

Merged
merged 4 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ wasmer-config = { path = "./lib/config" }
wasmer-wasix = { path = "./lib/wasix" }

# Wasmer-owned crates
webc = { version = "6.0.1", default-features = false, features = ["package"] }
webc = { version = "6.1.0", default-features = false, features = ["package"] }
shared-buffer = "0.1.4"

# Third-party crates
Expand Down
21 changes: 1 addition & 20 deletions lib/cli/src/commands/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ impl Run {
) -> Result<(), Error> {
let id = match self.entrypoint.as_deref() {
Some(cmd) => cmd,
None => infer_webc_entrypoint(pkg)?,
None => pkg.infer_entrypoint()?,
};
let cmd = pkg
.get_command(id)
Expand Down Expand Up @@ -570,25 +570,6 @@ fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
Ok(value)
}

fn infer_webc_entrypoint(pkg: &BinaryPackage) -> Result<&str, Error> {
if let Some(entrypoint) = pkg.entrypoint_cmd.as_deref() {
return Ok(entrypoint);
}

match pkg.commands.as_slice() {
[] => bail!("The WEBC file doesn't contain any executable commands"),
[one] => Ok(one.name()),
[..] => {
let mut commands: Vec<_> = pkg.commands.iter().map(|cmd| cmd.name()).collect();
commands.sort();
bail!(
"Unable to determine the WEBC file's entrypoint. Please choose one of {:?}",
commands,
);
}
}
}

/// The input that was passed in via the command-line.
#[derive(Debug, Clone, PartialEq)]
enum PackageSource {
Expand Down
19 changes: 19 additions & 0 deletions lib/wasix/src/bin_factory/binary_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,25 @@ impl BinaryPackage {
}
})
}

pub fn infer_entrypoint(&self) -> Result<&str, anyhow::Error> {
if let Some(entrypoint) = self.entrypoint_cmd.as_deref() {
return Ok(entrypoint);
}

match self.commands.as_slice() {
[] => anyhow::bail!("The WEBC file doesn't contain any executable commands"),
syrusakbary marked this conversation as resolved.
Show resolved Hide resolved
[one] => Ok(one.name()),
[..] => {
let mut commands: Vec<_> = self.commands.iter().map(|cmd| cmd.name()).collect();
commands.sort();
anyhow::bail!(
"Unable to determine the WEBC file's entrypoint. Please choose one of {:?}",
commands,
syrusakbary marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
}
}

#[cfg(test)]
Expand Down
23 changes: 21 additions & 2 deletions lib/wasix/src/bin_factory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use std::{
};

use anyhow::Context;
use exec::spawn_exec_wasm;
use virtual_fs::{AsyncReadExt, FileSystem};
use wasmer::FunctionEnvMut;
use wasmer_wasix_types::wasi::Errno;
Expand All @@ -20,7 +19,8 @@ mod exec;
pub use self::{
binary_package::*,
exec::{
run_exec, spawn_exec, spawn_exec_module, spawn_load_module, spawn_load_wasm, spawn_union_fs,
run_exec, spawn_exec, spawn_exec_module, spawn_exec_wasm, spawn_load_module,
spawn_load_wasm, spawn_union_fs,
},
};
use crate::{
Expand Down Expand Up @@ -92,6 +92,25 @@ impl BinFactory {
spawn_exec_wasm(&bytes, name.as_str(), env, &self.runtime).await
}
Executable::BinaryPackage(pkg) => {
// Get the command that is going to be executed
let cmd = if let Some(cmd) = pkg.get_command(name.as_str()) {
cmd
} else if let Some(cmd) = pkg.get_entrypoint_command() {
cmd
} else {
tracing::error!(
command=name,
pkg=%pkg.id,
"Unable to spawn a command because its package has no entrypoint",
);
env.on_exit(Some(Errno::Noexec.into())).await;
return Err(SpawnError::MissingEntrypoint {
package_id: pkg.id.clone(),
});
};

env.prepare_spawn(cmd);

spawn_exec(pkg, name.as_str(), store, env, &self.runtime).await
}
}
Expand Down
84 changes: 68 additions & 16 deletions lib/wasix/src/os/command/builtins/cmd_wasmer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::{any::Any, sync::Arc};
use std::{any::Any, path::PathBuf, sync::Arc};

use crate::{
bin_factory::spawn_exec_wasm,
os::task::{OwnedTaskStatus, TaskJoinHandle},
runtime::task_manager::InlineWaker,
SpawnError,
};
use virtual_fs::{AsyncReadExt, FileSystem};
use wasmer::{FunctionEnvMut, Store};
use wasmer_wasix_types::wasi::Errno;
use webc::Container;

use crate::{
bin_factory::{spawn_exec, BinaryPackage},
Expand Down Expand Up @@ -57,6 +60,11 @@ impl CmdWasmer {
what: Option<String>,
mut args: Vec<String>,
) -> Result<TaskJoinHandle, SpawnError> {
pub enum Executable {
Wasm(bytes::Bytes),
BinaryPackage(BinaryPackage),
}

// If the first argument is a '--' then skip it
if args.first().map(|a| a.as_str()) == Some("--") {
args = args.into_iter().skip(1).collect();
Expand All @@ -69,25 +77,69 @@ impl CmdWasmer {
// Set the arguments of the environment by replacing the state
let mut state = env.state.fork();
args.insert(0, what.clone());
state.args = args;
state.args = std::sync::Mutex::new(args);
env.state = Arc::new(state);

if let Ok(binary) = self.get_package(&what).await {
// Now run the module
spawn_exec(binary, name, store, env, &self.runtime).await
let file_path = if what.starts_with('/') {
PathBuf::from(&what)
} else {
let _ = unsafe {
stderr_write(
parent_ctx,
format!("package not found - {}\r\n", what).as_bytes(),
)
}
.await;
// convert relative path to absolute path
let cwd = env.state.fs.current_dir.lock().unwrap().clone();

PathBuf::from(cwd).join(&what)
};

let fs = env.fs_root();
let f = fs.new_open_options().read(true).open(&file_path);
let executable = if let Ok(mut file) = f {
let mut data = Vec::with_capacity(file.size() as usize);
file.read_to_end(&mut data).await.unwrap();

let bytes: bytes::Bytes = data.into();

let handle = OwnedTaskStatus::new_finished_with_code(Errno::Noent.into()).handle();
Ok(handle)
if let Ok(container) = Container::from_bytes(bytes.clone()) {
let pkg = BinaryPackage::from_webc(&container, &*self.runtime)
.await
.unwrap();

Executable::BinaryPackage(pkg)
} else {
Executable::Wasm(bytes)
}
} else if let Ok(pkg) = self.get_package(&what).await {
Executable::BinaryPackage(pkg)
} else {
let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await;
let handle =
OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
return Ok(handle);
};

match executable {
Executable::BinaryPackage(binary) => {
// Infer the command that is going to be executed
let cmd_name: &str =
binary
.infer_entrypoint()
.map_err(|_| SpawnError::MissingEntrypoint {
package_id: binary.id.clone(),
})?;

let cmd = binary
.get_command(cmd_name)
.ok_or_else(|| SpawnError::NotFound {
message: format!("{cmd_name} command in package: {}", binary.id),
})?;

env.prepare_spawn(cmd);

env.use_package_async(&binary).await.unwrap();

// Now run the module
spawn_exec(binary, name, store, env, &self.runtime).await
}
Executable::Wasm(bytes) => spawn_exec_wasm(&bytes, name, env, &self.runtime).await,
}
// Get the binary
} else {
let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await;
let handle = OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
Expand Down Expand Up @@ -130,7 +182,7 @@ impl VirtualCommand for CmdWasmer {
) -> Result<TaskJoinHandle, SpawnError> {
// Read the command we want to run
let env_inner = env.as_ref().ok_or(SpawnError::UnknownError)?;
let args = env_inner.state.args.clone();
let args = env_inner.state.args.lock().unwrap().clone();
let mut args = args.iter().map(|s| s.as_str());
let _alias = args.next();
let cmd = args.next();
Expand Down
8 changes: 7 additions & 1 deletion lib/wasix/src/runners/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,15 @@ impl crate::runners::Runner for WasiRunner {
.annotation("wasi")?
.unwrap_or_else(|| Wasi::new(command_name));

let exec_name = if let Some(exec_name) = wasi.exec_name.as_ref() {
exec_name
} else {
command_name
};

#[allow(unused_mut)]
let mut env = self
.prepare_webc_env(command_name, &wasi, Some(pkg), Arc::clone(&runtime), None)
.prepare_webc_env(exec_name, &wasi, Some(pkg), Arc::clone(&runtime), None)
.context("Unable to prepare the WASI environment")?;

#[cfg(feature = "journal")]
Expand Down
2 changes: 1 addition & 1 deletion lib/wasix/src/state/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,7 @@ impl WasiEnvBuilder {
fs: wasi_fs,
secret: rand::thread_rng().gen::<[u8; 32]>(),
inodes,
args: self.args.clone(),
args: std::sync::Mutex::new(self.args.clone()),
preopen: self.vfs_preopens.clone(),
futexs: Default::default(),
clock_offset: Default::default(),
Expand Down
63 changes: 55 additions & 8 deletions lib/wasix/src/state/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ use wasmer_wasix_types::{
wasi::{Errno, ExitCode, Snapshot0Clockid},
wasix::ThreadStartType,
};
use webc::metadata::annotations::Wasi;

#[cfg(feature = "journal")]
use crate::journal::{DynJournal, JournalEffector, SnapshotTrigger};
use crate::{
bin_factory::{BinFactory, BinaryPackage},
bin_factory::{BinFactory, BinaryPackage, BinaryPackageCommand},
capabilities::Capabilities,
fs::{WasiFsRoot, WasiInodes},
import_object_for_all_wasi_versions,
Expand All @@ -42,7 +43,7 @@ use crate::{
use wasmer_types::ModuleHash;

pub(crate) use super::handles::*;
use super::WasiState;
use super::{conv_env_vars, WasiState};

/// Various [`TypedFunction`] and [`Global`] handles for an active WASI(X) instance.
///
Expand Down Expand Up @@ -295,7 +296,7 @@ impl WasiEnvInit {
clock_offset: std::sync::Mutex::new(
self.state.clock_offset.lock().unwrap().clone(),
),
args: self.state.args.clone(),
args: std::sync::Mutex::new(self.state.args.lock().unwrap().clone()),
envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()),
preopen: self.state.preopen.clone(),
},
Expand Down Expand Up @@ -1079,6 +1080,10 @@ impl WasiEnv {
(state, inodes)
}

pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
InlineWaker::block_on(self.use_package_async(pkg))
}

/// Make all the commands in a [`BinaryPackage`] available to the WASI
/// instance.
///
Expand All @@ -1090,13 +1095,16 @@ impl WasiEnv {
///
/// [cmd-atom]: crate::bin_factory::BinaryPackageCommand::atom()
/// [pkg-fs]: crate::bin_factory::BinaryPackage::webc_fs
pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
pub async fn use_package_async(
&self,
pkg: &BinaryPackage,
) -> Result<(), WasiStateCreationError> {
tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment");
let root_fs = &self.state.fs.root_fs;

// We first need to merge the filesystem in the package into the
// main file system, if it has not been merged already.
if let Err(e) = InlineWaker::block_on(self.state.fs.conditional_union(pkg)) {
if let Err(e) = self.state.fs.conditional_union(pkg).await {
tracing::warn!(
error = &e as &dyn std::error::Error,
"Unable to merge the package's filesystem into the main one",
Expand Down Expand Up @@ -1146,9 +1154,7 @@ impl WasiEnv {
}
WasiFsRoot::Backing(fs) => {
let mut f = fs.new_open_options().create(true).write(true).open(path)?;
if let Err(e) =
InlineWaker::block_on(f.copy_reference(Box::new(StaticFile::new(atom))))
{
if let Err(e) = f.copy_reference(Box::new(StaticFile::new(atom))).await {
tracing::warn!(
error = &e as &dyn std::error::Error,
"Unable to copy file reference",
Expand Down Expand Up @@ -1304,4 +1310,45 @@ impl WasiEnv {
Box::pin(async {})
}
}

pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) {
if let Ok(Some(Wasi {
main_args,
env: env_vars,
exec_name,
..
})) = cmd.metadata().wasi()
{
if let Some(env_vars) = env_vars {
let env_vars = env_vars
.into_iter()
.map(|env_var| {
let (k, v) = env_var.split_once('=').unwrap();

(k.to_string(), v.as_bytes().to_vec())
})
.collect::<Vec<_>>();

let env_vars = conv_env_vars(env_vars);

self.state
.envs
.lock()
.unwrap()
.extend_from_slice(env_vars.as_slice());
}

if let Some(args) = main_args {
self.state
.args
.lock()
.unwrap()
.extend_from_slice(args.as_slice());
}

if let Some(exec_name) = exec_name {
self.state.args.lock().unwrap()[0] = exec_name;
}
}
}
}
Loading
Loading