wasmer_cli/commands/run/
mod.rs

1#![allow(missing_docs, unused)]
2
3mod capabilities;
4mod wasi;
5
6use std::{
7    collections::{BTreeMap, hash_map::DefaultHasher},
8    fmt::{Binary, Display},
9    fs::File,
10    hash::{BuildHasherDefault, Hash, Hasher},
11    io::{ErrorKind, LineWriter, Read, Write},
12    net::SocketAddr,
13    path::{Path, PathBuf},
14    str::FromStr,
15    sync::{Arc, Mutex},
16    time::{Duration, SystemTime, UNIX_EPOCH},
17};
18
19use anyhow::{Context, Error, anyhow, bail};
20use clap::{Parser, ValueEnum};
21use futures::future::BoxFuture;
22use indicatif::{MultiProgress, ProgressBar};
23use is_terminal::IsTerminal as _;
24use once_cell::sync::Lazy;
25use tempfile::NamedTempFile;
26use url::Url;
27#[cfg(feature = "sys")]
28use wasmer::sys::NativeEngineExt;
29use wasmer::{
30    AsStoreMut, DeserializeError, Engine, Function, Imports, Instance, Module, Store, Type,
31    TypedFunction, Value,
32};
33
34use wasmer_types::{Features, target::Target};
35
36#[cfg(feature = "compiler")]
37use wasmer_compiler::ArtifactBuild;
38use wasmer_config::package::PackageSource as PackageSpecifier;
39use wasmer_package::utils::from_disk;
40use wasmer_types::ModuleHash;
41
42#[cfg(feature = "journal")]
43use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
44use wasmer_wasix::{
45    Runtime, SpawnError, WasiError,
46    bin_factory::{BinaryPackage, BinaryPackageCommand},
47    journal::CompactingLogFileJournal,
48    runners::{
49        MappedCommand, MappedDirectory, Runner,
50        dcgi::{DcgiInstanceFactory, DcgiRunner},
51        dproxy::DProxyRunner,
52        wasi::{RuntimeOrEngine, WasiRunner},
53        wcgi::{self, AbortHandle, NoOpWcgiCallbacks, WcgiRunner},
54    },
55    runtime::{
56        module_cache::{CacheError, HashedModuleData},
57        package_loader::PackageLoader,
58        resolver::QueryError,
59        task_manager::VirtualTaskManagerExt,
60    },
61};
62use webc::Container;
63use webc::metadata::Manifest;
64
65use crate::{
66    backend::RuntimeOptions, commands::run::wasi::Wasi, common::HashAlgorithm, config::WasmerEnv,
67    error::PrettyError, logging::Output,
68};
69
70const TICK: Duration = Duration::from_millis(250);
71
72/// The unstable `wasmer run` subcommand.
73#[derive(Debug, Parser)]
74pub struct Run {
75    #[clap(flatten)]
76    env: WasmerEnv,
77    #[clap(flatten)]
78    rt: RuntimeOptions,
79    #[clap(flatten)]
80    wasi: crate::commands::run::Wasi,
81    #[clap(flatten)]
82    wcgi: WcgiOptions,
83    /// Set the default stack size (default is 1048576)
84    #[clap(long = "stack-size")]
85    stack_size: Option<usize>,
86    /// The entrypoint module for webc packages.
87    #[clap(short, long, aliases = &["command", "command-name"])]
88    entrypoint: Option<String>,
89    /// The function to invoke.
90    #[clap(short, long)]
91    invoke: Option<String>,
92    /// Generate a coredump at this path if a WebAssembly trap occurs
93    #[clap(name = "COREDUMP_PATH", long)]
94    coredump_on_trap: Option<PathBuf>,
95    /// The file, URL, or package to run.
96    #[clap(value_parser = PackageSource::infer)]
97    input: PackageSource,
98    /// Command-line arguments passed to the package
99    args: Vec<String>,
100    /// Hashing algorithm to be used for module hash
101    #[clap(long, value_enum)]
102    hash_algorithm: Option<HashAlgorithm>,
103}
104
105impl Run {
106    pub fn execute(self, output: Output) -> ! {
107        let result = self.execute_inner(output);
108        exit_with_wasi_exit_code(result);
109    }
110
111    #[tracing::instrument(level = "debug", name = "wasmer_run", skip_all)]
112    fn execute_inner(mut self, output: Output) -> Result<(), Error> {
113        let pb = ProgressBar::new_spinner();
114        pb.set_draw_target(output.draw_target());
115        pb.enable_steady_tick(TICK);
116
117        pb.set_message("Initializing the WebAssembly VM");
118
119        let runtime = tokio::runtime::Builder::new_multi_thread()
120            .enable_all()
121            .build()?;
122        let handle = runtime.handle().clone();
123
124        // Check for the preferred webc version.
125        // Default to v3.
126        let webc_version_var = std::env::var("WASMER_WEBC_VERSION");
127        let preferred_webc_version = match webc_version_var.as_deref() {
128            Ok("2") => webc::Version::V2,
129            Ok("3") | Err(_) => webc::Version::V3,
130            Ok(other) => {
131                bail!("unknown webc version: '{other}'");
132            }
133        };
134
135        let _guard = handle.enter();
136
137        // Get the input file path
138        let mut wasm_bytes: Option<Vec<u8>> = None;
139
140        // Try to detect WebAssembly features before selecting a backend
141        tracing::info!("Input source: {:?}", self.input);
142        if let PackageSource::File(path) = &self.input {
143            tracing::info!("Input file path: {}", path.display());
144
145            // Try to read and detect any file that exists, regardless of extension
146            if path.exists() {
147                tracing::info!("Found file: {}", path.display());
148                match std::fs::read(path) {
149                    Ok(bytes) => {
150                        tracing::info!("Read {} bytes from file", bytes.len());
151
152                        // Check if it's a WebAssembly module by looking for magic bytes
153                        let magic = [0x00, 0x61, 0x73, 0x6D]; // "\0asm"
154                        if bytes.len() >= 4 && bytes[0..4] == magic {
155                            // Looks like a valid WebAssembly module, save the bytes for feature detection
156                            tracing::info!(
157                                "Valid WebAssembly module detected, magic header verified"
158                            );
159                            wasm_bytes = Some(bytes);
160                        } else {
161                            tracing::info!(
162                                "File does not have valid WebAssembly magic number, will try to run it anyway"
163                            );
164                            // Still provide the bytes so the engine can attempt to run it
165                            wasm_bytes = Some(bytes);
166                        }
167                    }
168                    Err(e) => {
169                        tracing::info!("Failed to read file for feature detection: {}", e);
170                    }
171                }
172            } else {
173                tracing::info!("File does not exist: {}", path.display());
174            }
175        } else {
176            tracing::info!("Input is not a file, skipping WebAssembly feature detection");
177        }
178
179        // Get engine with feature-based backend selection if possible
180        let mut engine = match &wasm_bytes {
181            Some(wasm_bytes) => {
182                tracing::info!("Attempting to detect WebAssembly features from binary");
183
184                self.rt
185                    .get_engine_for_module(wasm_bytes, &Target::default())?
186            }
187            None => {
188                // No WebAssembly file available for analysis, check if we have a webc package
189                if let PackageSource::Package(pkg_source) = &self.input {
190                    tracing::info!("Checking package for WebAssembly features: {}", pkg_source);
191                    self.rt.get_engine(&Target::default())?
192                } else {
193                    tracing::info!("No feature detection possible, using default engine");
194                    self.rt.get_engine(&Target::default())?
195                }
196            }
197        };
198
199        let engine_kind = engine.deterministic_id();
200        tracing::info!("Executing on backend {engine_kind:?}");
201
202        #[cfg(feature = "sys")]
203        if engine.is_sys() {
204            if self.stack_size.is_some() {
205                wasmer_vm::set_stack_size(self.stack_size.unwrap());
206            }
207            let hash_algorithm = self.hash_algorithm.unwrap_or_default().into();
208            engine.set_hash_algorithm(Some(hash_algorithm));
209        }
210
211        let engine = engine.clone();
212
213        let runtime = self.wasi.prepare_runtime(
214            engine,
215            &self.env,
216            &capabilities::get_capability_cache_path(&self.env, &self.input)?,
217            runtime,
218            preferred_webc_version,
219        )?;
220
221        // This is a slow operation, so let's temporarily wrap the runtime with
222        // something that displays progress
223        let monitoring_runtime = Arc::new(MonitoringRuntime::new(runtime, pb.clone()));
224        let runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime.runtime.clone();
225        let monitoring_runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime;
226
227        let target = self.input.resolve_target(&monitoring_runtime, &pb)?;
228
229        if let ExecutableTarget::Package(ref pkg) = target {
230            self.wasi
231                .mapped_dirs
232                .extend(pkg.additional_host_mapped_directories.clone());
233        }
234
235        pb.finish_and_clear();
236
237        // push the TTY state so we can restore it after the program finishes
238        let tty = runtime.tty().map(|tty| tty.tty_get());
239
240        let result = {
241            match target {
242                ExecutableTarget::WebAssembly {
243                    module,
244                    module_hash,
245                    path,
246                } => self.execute_wasm(&path, module, module_hash, runtime.clone()),
247                ExecutableTarget::Package(pkg) => {
248                    // Check if we should update the engine based on the WebC package features
249                    if let Some(cmd) = pkg.get_entrypoint_command() {
250                        if let Some(features) = cmd.wasm_features() {
251                            // Get the right engine for these features
252                            let backends = self.rt.get_available_backends()?;
253                            let available_engines = backends
254                                .iter()
255                                .map(|b| b.to_string())
256                                .collect::<Vec<_>>()
257                                .join(", ");
258
259                            let filtered_backends = RuntimeOptions::filter_backends_by_features(
260                                backends.clone(),
261                                &features,
262                                &Target::default(),
263                            );
264
265                            if !filtered_backends.is_empty() {
266                                let engine_id = filtered_backends[0].to_string();
267
268                                // Get a new engine that's compatible with the required features
269                                if let Ok(new_engine) = filtered_backends[0].get_engine(
270                                    &Target::default(),
271                                    &features,
272                                    &self.rt,
273                                ) {
274                                    tracing::info!(
275                                        "The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
276                                        cmd.name(),
277                                        features,
278                                        available_engines,
279                                        engine_id
280                                    );
281                                    // Create a new runtime with the updated engine
282                                    let capability_cache_path =
283                                        capabilities::get_capability_cache_path(
284                                            &self.env,
285                                            &self.input,
286                                        )?;
287                                    let new_runtime = self.wasi.prepare_runtime(
288                                        new_engine,
289                                        &self.env,
290                                        &capability_cache_path,
291                                        tokio::runtime::Builder::new_multi_thread()
292                                            .enable_all()
293                                            .build()?,
294                                        preferred_webc_version,
295                                    )?;
296
297                                    let new_runtime =
298                                        Arc::new(MonitoringRuntime::new(new_runtime, pb.clone()));
299                                    return self.execute_webc(&pkg, new_runtime);
300                                }
301                            }
302                        }
303                    }
304                    self.execute_webc(&pkg, runtime.clone())
305                }
306            }
307        };
308
309        // restore the TTY state as the execution may have changed it
310        if let Some(state) = tty {
311            if let Some(tty) = runtime.tty() {
312                tty.tty_set(state);
313            }
314        }
315
316        if let Err(e) = &result {
317            self.maybe_save_coredump(e);
318        }
319
320        result
321    }
322
323    #[tracing::instrument(skip_all)]
324    fn execute_wasm(
325        &self,
326        path: &Path,
327        module: Module,
328        module_hash: ModuleHash,
329        runtime: Arc<dyn Runtime + Send + Sync>,
330    ) -> Result<(), Error> {
331        if wasmer_wasix::is_wasi_module(&module) || wasmer_wasix::is_wasix_module(&module) {
332            self.execute_wasi_module(path, module, module_hash, runtime)
333        } else {
334            self.execute_pure_wasm_module(&module)
335        }
336    }
337
338    #[tracing::instrument(skip_all)]
339    fn execute_webc(
340        &self,
341        pkg: &BinaryPackage,
342        runtime: Arc<dyn Runtime + Send + Sync>,
343    ) -> Result<(), Error> {
344        let id = match self.entrypoint.as_deref() {
345            Some(cmd) => cmd,
346            None => pkg.infer_entrypoint()?,
347        };
348        let cmd = pkg
349            .get_command(id)
350            .with_context(|| format!("Unable to get metadata for the \"{id}\" command"))?;
351
352        let uses = self.load_injected_packages(&runtime)?;
353
354        if DcgiRunner::can_run_command(cmd.metadata())? {
355            self.run_dcgi(id, pkg, uses, runtime)
356        } else if DProxyRunner::can_run_command(cmd.metadata())? {
357            self.run_dproxy(id, pkg, runtime)
358        } else if WcgiRunner::can_run_command(cmd.metadata())? {
359            self.run_wcgi(id, pkg, uses, runtime)
360        } else if WasiRunner::can_run_command(cmd.metadata())? {
361            self.run_wasi(id, pkg, uses, runtime)
362        } else {
363            bail!(
364                "Unable to find a runner that supports \"{}\"",
365                cmd.metadata().runner
366            );
367        }
368    }
369
370    #[tracing::instrument(level = "debug", skip_all)]
371    fn load_injected_packages(
372        &self,
373        runtime: &Arc<dyn Runtime + Send + Sync>,
374    ) -> Result<Vec<BinaryPackage>, Error> {
375        let mut dependencies = Vec::new();
376
377        for name in &self.wasi.uses {
378            let specifier = PackageSpecifier::from_str(name)
379                .with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
380            let pkg = {
381                let specifier = specifier.clone();
382                let inner_runtime = runtime.clone();
383                runtime
384                    .task_manager()
385                    .spawn_and_block_on(async move {
386                        BinaryPackage::from_registry(&specifier, inner_runtime.as_ref()).await
387                    })
388                    .with_context(|| format!("Unable to load \"{name}\""))??
389            };
390            dependencies.push(pkg);
391        }
392
393        Ok(dependencies)
394    }
395
396    fn run_wasi(
397        &self,
398        command_name: &str,
399        pkg: &BinaryPackage,
400        uses: Vec<BinaryPackage>,
401        runtime: Arc<dyn Runtime + Send + Sync>,
402    ) -> Result<(), Error> {
403        let mut runner = self.build_wasi_runner(&runtime)?;
404        Runner::run_command(&mut runner, command_name, pkg, runtime)
405    }
406
407    fn run_wcgi(
408        &self,
409        command_name: &str,
410        pkg: &BinaryPackage,
411        uses: Vec<BinaryPackage>,
412        runtime: Arc<dyn Runtime + Send + Sync>,
413    ) -> Result<(), Error> {
414        let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(NoOpWcgiCallbacks);
415        self.config_wcgi(runner.config(), uses)?;
416        runner.run_command(command_name, pkg, runtime)
417    }
418
419    fn config_wcgi(
420        &self,
421        config: &mut wcgi::Config,
422        uses: Vec<BinaryPackage>,
423    ) -> Result<(), Error> {
424        config
425            .args(self.args.clone())
426            .addr(self.wcgi.addr)
427            .envs(self.wasi.env_vars.clone())
428            .map_directories(self.wasi.mapped_dirs.clone())
429            .callbacks(Callbacks::new(self.wcgi.addr))
430            .inject_packages(uses);
431        *config.capabilities() = self.wasi.capabilities();
432        if self.wasi.forward_host_env {
433            config.forward_host_env();
434        }
435
436        #[cfg(feature = "journal")]
437        {
438            for trigger in self.wasi.snapshot_on.iter().cloned() {
439                config.add_snapshot_trigger(trigger);
440            }
441            if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
442                config.add_default_snapshot_triggers();
443            }
444            if let Some(period) = self.wasi.snapshot_interval {
445                if self.wasi.writable_journals.is_empty() {
446                    return Err(anyhow::format_err!(
447                        "If you specify a snapshot interval then you must also specify a writable journal file"
448                    ));
449                }
450                config.with_snapshot_interval(Duration::from_millis(period));
451            }
452            if self.wasi.stop_after_snapshot {
453                config.with_stop_running_after_snapshot(true);
454            }
455            let (r, w) = self.wasi.build_journals()?;
456            for journal in r {
457                config.add_read_only_journal(journal);
458            }
459            for journal in w {
460                config.add_writable_journal(journal);
461            }
462        }
463
464        Ok(())
465    }
466
467    fn run_dcgi(
468        &self,
469        command_name: &str,
470        pkg: &BinaryPackage,
471        uses: Vec<BinaryPackage>,
472        runtime: Arc<dyn Runtime + Send + Sync>,
473    ) -> Result<(), Error> {
474        let factory = DcgiInstanceFactory::new();
475        let mut runner = wasmer_wasix::runners::dcgi::DcgiRunner::new(factory);
476        self.config_wcgi(runner.config().inner(), uses);
477        runner.run_command(command_name, pkg, runtime)
478    }
479
480    fn run_dproxy(
481        &self,
482        command_name: &str,
483        pkg: &BinaryPackage,
484        runtime: Arc<dyn Runtime + Send + Sync>,
485    ) -> Result<(), Error> {
486        let mut inner = self.build_wasi_runner(&runtime)?;
487        let mut runner = wasmer_wasix::runners::dproxy::DProxyRunner::new(inner, pkg);
488        runner.run_command(command_name, pkg, runtime)
489    }
490
491    #[tracing::instrument(skip_all)]
492    fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
493        /// The rest of the execution happens in the main thread, so we can create the
494        /// store here.
495        let mut store = self.rt.get_store()?;
496        let imports = Imports::default();
497        let instance = Instance::new(&mut store, module, &imports)
498            .context("Unable to instantiate the WebAssembly module")?;
499
500        let entry_function  = match &self.invoke {
501            Some(entry) => {
502                instance.exports
503                    .get_function(entry)
504                    .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
505            },
506            None => {
507                instance.exports.get_function("_start")
508                    .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
509            }
510        };
511
512        let return_values = invoke_function(&instance, &mut store, entry_function, &self.args)?;
513
514        println!(
515            "{}",
516            return_values
517                .iter()
518                .map(|val| val.to_string())
519                .collect::<Vec<String>>()
520                .join(" ")
521        );
522
523        Ok(())
524    }
525
526    fn build_wasi_runner(
527        &self,
528        runtime: &Arc<dyn Runtime + Send + Sync>,
529    ) -> Result<WasiRunner, anyhow::Error> {
530        let packages = self.load_injected_packages(runtime)?;
531
532        let mut runner = WasiRunner::new();
533
534        let (is_home_mapped, is_tmp_mapped, mapped_diretories) =
535            self.wasi.build_mapped_directories()?;
536
537        runner
538            .with_args(&self.args)
539            .with_injected_packages(packages)
540            .with_envs(self.wasi.env_vars.clone())
541            .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
542            .with_mapped_directories(mapped_diretories)
543            .with_home_mapped(is_home_mapped)
544            .with_tmp_mapped(is_tmp_mapped)
545            .with_forward_host_env(self.wasi.forward_host_env)
546            .with_capabilities(self.wasi.capabilities());
547
548        if let Some(ref entry_function) = self.invoke {
549            runner.with_entry_function(entry_function);
550        }
551
552        #[cfg(feature = "journal")]
553        {
554            for trigger in self.wasi.snapshot_on.iter().cloned() {
555                runner.with_snapshot_trigger(trigger);
556            }
557            if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
558                runner.with_default_snapshot_triggers();
559            }
560            if let Some(period) = self.wasi.snapshot_interval {
561                if self.wasi.writable_journals.is_empty() {
562                    return Err(anyhow::format_err!(
563                        "If you specify a snapshot interval then you must also specify a writable journal file"
564                    ));
565                }
566                runner.with_snapshot_interval(Duration::from_millis(period));
567            }
568            if self.wasi.stop_after_snapshot {
569                runner.with_stop_running_after_snapshot(true);
570            }
571            let (r, w) = self.wasi.build_journals()?;
572            for journal in r {
573                runner.with_read_only_journal(journal);
574            }
575            for journal in w {
576                runner.with_writable_journal(journal);
577            }
578            runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
579        }
580
581        Ok(runner)
582    }
583
584    #[tracing::instrument(skip_all)]
585    fn execute_wasi_module(
586        &self,
587        wasm_path: &Path,
588        module: Module,
589        module_hash: ModuleHash,
590        runtime: Arc<dyn Runtime + Send + Sync>,
591    ) -> Result<(), Error> {
592        let program_name = wasm_path.display().to_string();
593
594        let runner = self.build_wasi_runner(&runtime)?;
595        runner.run_wasm(
596            RuntimeOrEngine::Runtime(runtime),
597            &program_name,
598            module,
599            module_hash,
600        )
601    }
602
603    #[allow(unused_variables)]
604    fn maybe_save_coredump(&self, e: &Error) {
605        #[cfg(feature = "coredump")]
606        if let Some(coredump) = &self.coredump_on_trap {
607            if let Err(e) = generate_coredump(e, self.input.to_string(), coredump) {
608                tracing::warn!(
609                    error = &*e as &dyn std::error::Error,
610                    coredump_path=%coredump.display(),
611                    "Unable to generate a coredump",
612                );
613            }
614        }
615    }
616
617    /// Create Run instance for arguments/env, assuming we're being run from a
618    /// CFP binfmt interpreter.
619    pub fn from_binfmt_args() -> Self {
620        Run::from_binfmt_args_fallible().unwrap_or_else(|e| {
621            crate::error::PrettyError::report::<()>(
622                Err(e).context("Failed to set up wasmer binfmt invocation"),
623            )
624        })
625    }
626
627    fn from_binfmt_args_fallible() -> Result<Self, Error> {
628        if cfg!(not(target_os = "linux")) {
629            bail!("binfmt_misc is only available on linux.");
630        }
631
632        let argv = std::env::args().collect::<Vec<_>>();
633        let (_interpreter, executable, original_executable, args) = match &argv[..] {
634            [a, b, c, rest @ ..] => (a, b, c, rest),
635            _ => {
636                bail!(
637                    "Wasmer binfmt interpreter needs at least three arguments (including $0) - must be registered as binfmt interpreter with the CFP flags. (Got arguments: {argv:?})"
638                );
639            }
640        };
641        let rt = RuntimeOptions::default();
642        Ok(Run {
643            env: WasmerEnv::default(),
644            rt,
645            wasi: Wasi::for_binfmt_interpreter()?,
646            wcgi: WcgiOptions::default(),
647            stack_size: None,
648            entrypoint: Some(original_executable.to_string()),
649            invoke: None,
650            coredump_on_trap: None,
651            input: PackageSource::infer(executable)?,
652            args: args.to_vec(),
653            hash_algorithm: None,
654        })
655    }
656}
657
658fn invoke_function(
659    instance: &Instance,
660    store: &mut Store,
661    func: &Function,
662    args: &[String],
663) -> Result<Box<[Value]>, Error> {
664    let func_ty = func.ty(store);
665    let required_arguments = func_ty.params().len();
666    let provided_arguments = args.len();
667
668    anyhow::ensure!(
669        required_arguments == provided_arguments,
670        "Function expected {required_arguments} arguments, but received {provided_arguments}"
671    );
672
673    let invoke_args = args
674        .iter()
675        .zip(func_ty.params().iter())
676        .map(|(arg, param_type)| {
677            parse_value(arg, *param_type)
678                .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
679        })
680        .collect::<Result<Vec<_>, _>>()?;
681
682    let return_values = func.call(store, &invoke_args)?;
683
684    Ok(return_values)
685}
686
687fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
688    let value = match ty {
689        Type::I32 => Value::I32(s.parse()?),
690        Type::I64 => Value::I64(s.parse()?),
691        Type::F32 => Value::F32(s.parse()?),
692        Type::F64 => Value::F64(s.parse()?),
693        Type::V128 => Value::V128(s.parse()?),
694        _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
695    };
696    Ok(value)
697}
698
699/// The input that was passed in via the command-line.
700#[derive(Debug, Clone, PartialEq)]
701enum PackageSource {
702    /// A file on disk (`*.wasm`, `*.webc`, etc.).
703    File(PathBuf),
704    /// A directory containing a `wasmer.toml` file
705    Dir(PathBuf),
706    /// A package to be downloaded (a URL, package name, etc.)
707    Package(PackageSpecifier),
708}
709
710impl PackageSource {
711    fn infer(s: &str) -> Result<PackageSource, Error> {
712        let path = Path::new(s);
713        if path.is_file() {
714            return Ok(PackageSource::File(path.to_path_buf()));
715        } else if path.is_dir() {
716            return Ok(PackageSource::Dir(path.to_path_buf()));
717        }
718
719        if let Ok(pkg) = PackageSpecifier::from_str(s) {
720            return Ok(PackageSource::Package(pkg));
721        }
722
723        Err(anyhow::anyhow!(
724            "Unable to resolve \"{s}\" as a URL, package name, or file on disk"
725        ))
726    }
727
728    /// Try to resolve the [`PackageSource`] to an executable artifact.
729    ///
730    /// This will try to automatically download and cache any resources from the
731    /// internet.
732    #[tracing::instrument(level = "debug", skip_all)]
733    fn resolve_target(
734        &self,
735        rt: &Arc<dyn Runtime + Send + Sync>,
736        pb: &ProgressBar,
737    ) -> Result<ExecutableTarget, Error> {
738        match self {
739            PackageSource::File(path) => ExecutableTarget::from_file(path, rt, pb),
740            PackageSource::Dir(d) => ExecutableTarget::from_dir(d, rt, pb),
741            PackageSource::Package(pkg) => {
742                pb.set_message("Loading from the registry");
743                let inner_pck = pkg.clone();
744                let inner_rt = rt.clone();
745                let pkg = rt.task_manager().spawn_and_block_on(async move {
746                    BinaryPackage::from_registry(&inner_pck, inner_rt.as_ref()).await
747                })??;
748                Ok(ExecutableTarget::Package(Box::new(pkg)))
749            }
750        }
751    }
752}
753
754impl Display for PackageSource {
755    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
756        match self {
757            PackageSource::File(path) | PackageSource::Dir(path) => write!(f, "{}", path.display()),
758            PackageSource::Package(p) => write!(f, "{p}"),
759        }
760    }
761}
762
763/// We've been given the path for a file... What does it contain and how should
764/// that be run?
765#[derive(Debug, Clone)]
766enum TargetOnDisk {
767    WebAssemblyBinary,
768    Wat,
769    LocalWebc,
770    Artifact,
771}
772
773impl TargetOnDisk {
774    fn from_file(path: &Path) -> Result<TargetOnDisk, Error> {
775        // Normally the first couple hundred bytes is enough to figure
776        // out what type of file this is.
777        let mut buffer = [0_u8; 512];
778
779        let mut f = File::open(path)
780            .with_context(|| format!("Unable to open \"{}\" for reading", path.display()))?;
781        let bytes_read = f.read(&mut buffer)?;
782
783        let leading_bytes = &buffer[..bytes_read];
784
785        if wasmer::is_wasm(leading_bytes) {
786            return Ok(TargetOnDisk::WebAssemblyBinary);
787        }
788
789        if webc::detect(leading_bytes).is_ok() {
790            return Ok(TargetOnDisk::LocalWebc);
791        }
792
793        #[cfg(feature = "compiler")]
794        if ArtifactBuild::is_deserializable(leading_bytes) {
795            return Ok(TargetOnDisk::Artifact);
796        }
797
798        // If we can't figure out the file type based on its content, fall back
799        // to checking the extension.
800
801        match path.extension().and_then(|s| s.to_str()) {
802            Some("wat") => Ok(TargetOnDisk::Wat),
803            Some("wasm") => Ok(TargetOnDisk::WebAssemblyBinary),
804            Some("webc") => Ok(TargetOnDisk::LocalWebc),
805            Some("wasmu") => Ok(TargetOnDisk::WebAssemblyBinary),
806            _ => bail!("Unable to determine how to execute \"{}\"", path.display()),
807        }
808    }
809}
810
811#[derive(Debug, Clone)]
812enum ExecutableTarget {
813    WebAssembly {
814        module: Module,
815        module_hash: ModuleHash,
816        path: PathBuf,
817    },
818    Package(Box<BinaryPackage>),
819}
820
821impl ExecutableTarget {
822    /// Try to load a Wasmer package from a directory containing a `wasmer.toml`
823    /// file.
824    #[tracing::instrument(level = "debug", skip_all)]
825    fn from_dir(
826        dir: &Path,
827        runtime: &Arc<dyn Runtime + Send + Sync>,
828        pb: &ProgressBar,
829    ) -> Result<Self, Error> {
830        pb.set_message(format!("Loading \"{}\" into memory", dir.display()));
831        pb.set_message("Resolving dependencies");
832        let inner_runtime = runtime.clone();
833        let pkg = runtime.task_manager().spawn_and_block_on({
834            let path = dir.to_path_buf();
835
836            async move { BinaryPackage::from_dir(&path, inner_runtime.as_ref()).await }
837        })??;
838
839        Ok(ExecutableTarget::Package(Box::new(pkg)))
840    }
841
842    /// Try to load a file into something that can be used to run it.
843    #[tracing::instrument(level = "debug", skip_all)]
844    fn from_file(
845        path: &Path,
846        runtime: &Arc<dyn Runtime + Send + Sync>,
847        pb: &ProgressBar,
848    ) -> Result<Self, Error> {
849        pb.set_message(format!("Loading from \"{}\"", path.display()));
850
851        match TargetOnDisk::from_file(path)? {
852            TargetOnDisk::WebAssemblyBinary | TargetOnDisk::Wat => {
853                let wasm = std::fs::read(path)?;
854                let module_data = HashedModuleData::new(wasm);
855                let module_hash = *module_data.hash();
856
857                pb.set_message("Compiling to WebAssembly");
858                let module = runtime
859                    .load_hashed_module_sync(module_data, None)
860                    .with_context(|| format!("Unable to compile \"{}\"", path.display()))?;
861
862                Ok(ExecutableTarget::WebAssembly {
863                    module,
864                    module_hash,
865                    path: path.to_path_buf(),
866                })
867            }
868            TargetOnDisk::Artifact => {
869                let engine = runtime.engine();
870                pb.set_message("Deserializing pre-compiled WebAssembly module");
871                let module = unsafe { Module::deserialize_from_file(&engine, path)? };
872
873                let module_hash = module.info().hash.ok_or_else(|| {
874                    anyhow::Error::msg("module hash is not present in the artifact")
875                })?;
876
877                Ok(ExecutableTarget::WebAssembly {
878                    module,
879                    module_hash,
880                    path: path.to_path_buf(),
881                })
882            }
883            TargetOnDisk::LocalWebc => {
884                let container = from_disk(path)?;
885                pb.set_message("Resolving dependencies");
886
887                let inner_runtime = runtime.clone();
888                let pkg = runtime.task_manager().spawn_and_block_on(async move {
889                    BinaryPackage::from_webc(&container, inner_runtime.as_ref()).await
890                })??;
891                Ok(ExecutableTarget::Package(Box::new(pkg)))
892            }
893        }
894    }
895}
896
897#[cfg(feature = "coredump")]
898fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
899    let err: &wasmer::RuntimeError = match err.downcast_ref() {
900        Some(e) => e,
901        None => {
902            log::warn!("no runtime error found to generate coredump with");
903            return Ok(());
904        }
905    };
906
907    let mut coredump_builder =
908        wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
909
910    let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
911
912    for frame in err.trace() {
913        let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
914            .codeoffset(frame.func_offset() as u32)
915            .funcidx(frame.func_index())
916            .build();
917        thread_builder.add_frame(coredump_frame);
918    }
919
920    coredump_builder.add_thread(thread_builder.build());
921
922    let coredump = coredump_builder
923        .serialize()
924        .map_err(Error::msg)
925        .context("Coredump serializing failed")?;
926
927    std::fs::write(coredump_path, &coredump).with_context(|| {
928        format!(
929            "Unable to save the coredump to \"{}\"",
930            coredump_path.display()
931        )
932    })?;
933
934    Ok(())
935}
936
937#[derive(Debug, Clone, Parser)]
938pub(crate) struct WcgiOptions {
939    /// The address to serve on.
940    #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
941    pub(crate) addr: SocketAddr,
942}
943
944impl Default for WcgiOptions {
945    fn default() -> Self {
946        Self {
947            addr: ([127, 0, 0, 1], 8000).into(),
948        }
949    }
950}
951
952#[derive(Debug)]
953struct Callbacks {
954    stderr: Mutex<LineWriter<std::io::Stderr>>,
955    addr: SocketAddr,
956}
957
958impl Callbacks {
959    fn new(addr: SocketAddr) -> Self {
960        Callbacks {
961            stderr: Mutex::new(LineWriter::new(std::io::stderr())),
962            addr,
963        }
964    }
965}
966
967impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
968    fn started(&self, _abort: AbortHandle) {
969        println!("WCGI Server running at http://{}/", self.addr);
970    }
971
972    fn on_stderr(&self, raw_message: &[u8]) {
973        if let Ok(mut stderr) = self.stderr.lock() {
974            // If the WCGI runner printed any log messages we want to make sure
975            // they get propagated to the user. Line buffering is important here
976            // because it helps prevent the output from becoming a complete
977            // mess.
978            let _ = stderr.write_all(raw_message);
979        }
980    }
981}
982
983/// Exit the current process, using the WASI exit code if the error contains
984/// one.
985fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
986    let exit_code = match result {
987        Ok(_) => 0,
988        Err(error) => {
989            match error.chain().find_map(get_exit_code) {
990                Some(exit_code) => exit_code.raw(),
991                None => {
992                    eprintln!("{:?}", PrettyError::new(error));
993                    // Something else happened
994                    1
995                }
996            }
997        }
998    };
999
1000    std::io::stdout().flush().ok();
1001    std::io::stderr().flush().ok();
1002
1003    std::process::exit(exit_code);
1004}
1005
1006fn get_exit_code(
1007    error: &(dyn std::error::Error + 'static),
1008) -> Option<wasmer_wasix::types::wasi::ExitCode> {
1009    if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
1010        return Some(*exit_code);
1011    }
1012    if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
1013        return error.as_exit_code();
1014    }
1015
1016    None
1017}
1018
1019#[derive(Debug)]
1020struct MonitoringRuntime<R> {
1021    runtime: Arc<R>,
1022    progress: ProgressBar,
1023}
1024
1025impl<R> MonitoringRuntime<R> {
1026    fn new(runtime: R, progress: ProgressBar) -> Self {
1027        MonitoringRuntime {
1028            runtime: Arc::new(runtime),
1029            progress,
1030        }
1031    }
1032}
1033
1034impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
1035    fn networking(&self) -> &virtual_net::DynVirtualNetworking {
1036        self.runtime.networking()
1037    }
1038
1039    fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
1040        self.runtime.task_manager()
1041    }
1042
1043    fn package_loader(
1044        &self,
1045    ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
1046        let inner = self.runtime.package_loader();
1047        Arc::new(MonitoringPackageLoader {
1048            inner,
1049            progress: self.progress.clone(),
1050        })
1051    }
1052
1053    fn module_cache(
1054        &self,
1055    ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
1056        self.runtime.module_cache()
1057    }
1058
1059    fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
1060        let inner = self.runtime.source();
1061        Arc::new(MonitoringSource {
1062            inner,
1063            progress: self.progress.clone(),
1064        })
1065    }
1066
1067    fn engine(&self) -> wasmer::Engine {
1068        self.runtime.engine()
1069    }
1070
1071    fn new_store(&self) -> wasmer::Store {
1072        self.runtime.new_store()
1073    }
1074
1075    fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
1076        self.runtime.http_client()
1077    }
1078
1079    fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
1080        self.runtime.tty()
1081    }
1082
1083    #[cfg(feature = "journal")]
1084    fn read_only_journals<'a>(
1085        &'a self,
1086    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
1087        self.runtime.read_only_journals()
1088    }
1089
1090    #[cfg(feature = "journal")]
1091    fn writable_journals<'a>(
1092        &'a self,
1093    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
1094        self.runtime.writable_journals()
1095    }
1096
1097    #[cfg(feature = "journal")]
1098    fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
1099        self.runtime.active_journal()
1100    }
1101
1102    fn load_hashed_module(
1103        &self,
1104        module: HashedModuleData,
1105        engine: Option<&Engine>,
1106    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1107        let hash = *module.hash();
1108        let fut = self.runtime.load_hashed_module(module, engine);
1109        Box::pin(compile_with_progress(fut, hash, None))
1110    }
1111
1112    fn load_hashed_module_sync(
1113        &self,
1114        wasm: HashedModuleData,
1115        engine: Option<&Engine>,
1116    ) -> Result<Module, wasmer_wasix::SpawnError> {
1117        let hash = *wasm.hash();
1118        compile_with_progress_sync(
1119            || self.runtime.load_hashed_module_sync(wasm, engine),
1120            &hash,
1121            None,
1122        )
1123    }
1124
1125    fn load_command_module(
1126        &self,
1127        cmd: &BinaryPackageCommand,
1128    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1129        let fut = self.runtime.load_command_module(cmd);
1130
1131        Box::pin(compile_with_progress(
1132            fut,
1133            *cmd.hash(),
1134            Some(cmd.name().to_owned()),
1135        ))
1136    }
1137
1138    fn load_command_module_sync(
1139        &self,
1140        cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand,
1141    ) -> Result<Module, wasmer_wasix::SpawnError> {
1142        compile_with_progress_sync(
1143            || self.runtime.load_command_module_sync(cmd),
1144            cmd.hash(),
1145            Some(cmd.name()),
1146        )
1147    }
1148}
1149
1150async fn compile_with_progress<'a, F, T>(fut: F, hash: ModuleHash, name: Option<String>) -> T
1151where
1152    F: std::future::Future<Output = T> + Send + 'a,
1153    T: Send + 'static,
1154{
1155    let mut pb = new_progressbar_compile(&hash, name.as_deref());
1156    let res = fut.await;
1157    pb.finish_and_clear();
1158    res
1159}
1160
1161fn compile_with_progress_sync<F, T>(f: F, hash: &ModuleHash, name: Option<&str>) -> T
1162where
1163    F: FnOnce() -> T,
1164{
1165    let mut pb = new_progressbar_compile(hash, name);
1166    let res = f();
1167    pb.finish_and_clear();
1168    res
1169}
1170
1171fn new_progressbar_compile(hash: &ModuleHash, name: Option<&str>) -> ProgressBar {
1172    // Only show a spinner if we're running in a TTY
1173    if std::io::stderr().is_terminal() {
1174        let msg = if let Some(name) = name {
1175            format!("Compiling WebAssembly module for command '{name}' ({hash})...")
1176        } else {
1177            format!("Compiling WebAssembly module {hash}...")
1178        };
1179        let pb = ProgressBar::new_spinner().with_message(msg);
1180        pb.enable_steady_tick(Duration::from_millis(100));
1181        pb
1182    } else {
1183        ProgressBar::hidden()
1184    }
1185}
1186
1187#[derive(Debug)]
1188struct MonitoringSource {
1189    inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
1190    progress: ProgressBar,
1191}
1192
1193#[async_trait::async_trait]
1194impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
1195    async fn query(
1196        &self,
1197        package: &PackageSpecifier,
1198    ) -> Result<Vec<wasmer_wasix::runtime::resolver::PackageSummary>, QueryError> {
1199        self.progress.set_message(format!("Looking up {package}"));
1200        self.inner.query(package).await
1201    }
1202}
1203
1204#[derive(Debug)]
1205struct MonitoringPackageLoader {
1206    inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
1207    progress: ProgressBar,
1208}
1209
1210#[async_trait::async_trait]
1211impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
1212    async fn load(
1213        &self,
1214        summary: &wasmer_wasix::runtime::resolver::PackageSummary,
1215    ) -> Result<Container, Error> {
1216        let pkg_id = summary.package_id();
1217        self.progress.set_message(format!("Downloading {pkg_id}"));
1218
1219        self.inner.load(summary).await
1220    }
1221
1222    async fn load_package_tree(
1223        &self,
1224        root: &Container,
1225        resolution: &wasmer_wasix::runtime::resolver::Resolution,
1226        root_is_local_dir: bool,
1227    ) -> Result<BinaryPackage, Error> {
1228        self.inner
1229            .load_package_tree(root, resolution, root_is_local_dir)
1230            .await
1231    }
1232}