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, mapped_diretories) = self.wasi.build_mapped_directories()?;
535
536        runner
537            .with_args(&self.args)
538            .with_injected_packages(packages)
539            .with_envs(self.wasi.env_vars.clone())
540            .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
541            .with_mapped_directories(mapped_diretories)
542            .with_home_mapped(is_home_mapped)
543            .with_forward_host_env(self.wasi.forward_host_env)
544            .with_capabilities(self.wasi.capabilities());
545
546        if let Some(cwd) = self.wasi.cwd.as_ref() {
547            if !cwd.starts_with("/") {
548                bail!("The argument to --cwd must be an absolute path");
549            }
550            runner.with_current_dir(cwd.clone());
551        }
552
553        if let Some(ref entry_function) = self.invoke {
554            runner.with_entry_function(entry_function);
555        }
556
557        #[cfg(feature = "journal")]
558        {
559            for trigger in self.wasi.snapshot_on.iter().cloned() {
560                runner.with_snapshot_trigger(trigger);
561            }
562            if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
563                runner.with_default_snapshot_triggers();
564            }
565            if let Some(period) = self.wasi.snapshot_interval {
566                if self.wasi.writable_journals.is_empty() {
567                    return Err(anyhow::format_err!(
568                        "If you specify a snapshot interval then you must also specify a writable journal file"
569                    ));
570                }
571                runner.with_snapshot_interval(Duration::from_millis(period));
572            }
573            if self.wasi.stop_after_snapshot {
574                runner.with_stop_running_after_snapshot(true);
575            }
576            let (r, w) = self.wasi.build_journals()?;
577            for journal in r {
578                runner.with_read_only_journal(journal);
579            }
580            for journal in w {
581                runner.with_writable_journal(journal);
582            }
583            runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
584        }
585
586        Ok(runner)
587    }
588
589    #[tracing::instrument(skip_all)]
590    fn execute_wasi_module(
591        &self,
592        wasm_path: &Path,
593        module: Module,
594        module_hash: ModuleHash,
595        runtime: Arc<dyn Runtime + Send + Sync>,
596    ) -> Result<(), Error> {
597        let program_name = wasm_path.display().to_string();
598
599        let runner = self.build_wasi_runner(&runtime)?;
600        runner.run_wasm(
601            RuntimeOrEngine::Runtime(runtime),
602            &program_name,
603            module,
604            module_hash,
605        )
606    }
607
608    #[allow(unused_variables)]
609    fn maybe_save_coredump(&self, e: &Error) {
610        #[cfg(feature = "coredump")]
611        if let Some(coredump) = &self.coredump_on_trap {
612            if let Err(e) = generate_coredump(e, self.input.to_string(), coredump) {
613                tracing::warn!(
614                    error = &*e as &dyn std::error::Error,
615                    coredump_path=%coredump.display(),
616                    "Unable to generate a coredump",
617                );
618            }
619        }
620    }
621}
622
623fn invoke_function(
624    instance: &Instance,
625    store: &mut Store,
626    func: &Function,
627    args: &[String],
628) -> Result<Box<[Value]>, Error> {
629    let func_ty = func.ty(store);
630    let required_arguments = func_ty.params().len();
631    let provided_arguments = args.len();
632
633    anyhow::ensure!(
634        required_arguments == provided_arguments,
635        "Function expected {required_arguments} arguments, but received {provided_arguments}"
636    );
637
638    let invoke_args = args
639        .iter()
640        .zip(func_ty.params().iter())
641        .map(|(arg, param_type)| {
642            parse_value(arg, *param_type)
643                .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
644        })
645        .collect::<Result<Vec<_>, _>>()?;
646
647    let return_values = func.call(store, &invoke_args)?;
648
649    Ok(return_values)
650}
651
652fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
653    let value = match ty {
654        Type::I32 => Value::I32(s.parse()?),
655        Type::I64 => Value::I64(s.parse()?),
656        Type::F32 => Value::F32(s.parse()?),
657        Type::F64 => Value::F64(s.parse()?),
658        Type::V128 => Value::V128(s.parse()?),
659        _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
660    };
661    Ok(value)
662}
663
664/// The input that was passed in via the command-line.
665#[derive(Debug, Clone, PartialEq)]
666enum PackageSource {
667    /// A file on disk (`*.wasm`, `*.webc`, etc.).
668    File(PathBuf),
669    /// A directory containing a `wasmer.toml` file
670    Dir(PathBuf),
671    /// A package to be downloaded (a URL, package name, etc.)
672    Package(PackageSpecifier),
673}
674
675impl PackageSource {
676    fn infer(s: &str) -> Result<PackageSource, Error> {
677        let path = Path::new(s);
678        if path.is_file() {
679            return Ok(PackageSource::File(path.to_path_buf()));
680        } else if path.is_dir() {
681            return Ok(PackageSource::Dir(path.to_path_buf()));
682        }
683
684        if let Ok(pkg) = PackageSpecifier::from_str(s) {
685            return Ok(PackageSource::Package(pkg));
686        }
687
688        Err(anyhow::anyhow!(
689            "Unable to resolve \"{s}\" as a URL, package name, or file on disk"
690        ))
691    }
692
693    /// Try to resolve the [`PackageSource`] to an executable artifact.
694    ///
695    /// This will try to automatically download and cache any resources from the
696    /// internet.
697    #[tracing::instrument(level = "debug", skip_all)]
698    fn resolve_target(
699        &self,
700        rt: &Arc<dyn Runtime + Send + Sync>,
701        pb: &ProgressBar,
702    ) -> Result<ExecutableTarget, Error> {
703        match self {
704            PackageSource::File(path) => ExecutableTarget::from_file(path, rt, pb),
705            PackageSource::Dir(d) => ExecutableTarget::from_dir(d, rt, pb),
706            PackageSource::Package(pkg) => {
707                pb.set_message("Loading from the registry");
708                let inner_pck = pkg.clone();
709                let inner_rt = rt.clone();
710                let pkg = rt.task_manager().spawn_and_block_on(async move {
711                    BinaryPackage::from_registry(&inner_pck, inner_rt.as_ref()).await
712                })??;
713                Ok(ExecutableTarget::Package(Box::new(pkg)))
714            }
715        }
716    }
717}
718
719impl Display for PackageSource {
720    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721        match self {
722            PackageSource::File(path) | PackageSource::Dir(path) => write!(f, "{}", path.display()),
723            PackageSource::Package(p) => write!(f, "{p}"),
724        }
725    }
726}
727
728/// We've been given the path for a file... What does it contain and how should
729/// that be run?
730#[derive(Debug, Clone)]
731enum TargetOnDisk {
732    WebAssemblyBinary,
733    Wat,
734    LocalWebc,
735    Artifact,
736}
737
738impl TargetOnDisk {
739    fn from_file(path: &Path) -> Result<TargetOnDisk, Error> {
740        // Normally the first couple hundred bytes is enough to figure
741        // out what type of file this is.
742        let mut buffer = [0_u8; 512];
743
744        let mut f = File::open(path)
745            .with_context(|| format!("Unable to open \"{}\" for reading", path.display()))?;
746        let bytes_read = f.read(&mut buffer)?;
747
748        let leading_bytes = &buffer[..bytes_read];
749
750        if wasmer::is_wasm(leading_bytes) {
751            return Ok(TargetOnDisk::WebAssemblyBinary);
752        }
753
754        if webc::detect(leading_bytes).is_ok() {
755            return Ok(TargetOnDisk::LocalWebc);
756        }
757
758        #[cfg(feature = "compiler")]
759        if ArtifactBuild::is_deserializable(leading_bytes) {
760            return Ok(TargetOnDisk::Artifact);
761        }
762
763        // If we can't figure out the file type based on its content, fall back
764        // to checking the extension.
765
766        match path.extension().and_then(|s| s.to_str()) {
767            Some("wat") => Ok(TargetOnDisk::Wat),
768            Some("wasm") => Ok(TargetOnDisk::WebAssemblyBinary),
769            Some("webc") => Ok(TargetOnDisk::LocalWebc),
770            Some("wasmu") => Ok(TargetOnDisk::WebAssemblyBinary),
771            _ => bail!("Unable to determine how to execute \"{}\"", path.display()),
772        }
773    }
774}
775
776#[derive(Debug, Clone)]
777enum ExecutableTarget {
778    WebAssembly {
779        module: Module,
780        module_hash: ModuleHash,
781        path: PathBuf,
782    },
783    Package(Box<BinaryPackage>),
784}
785
786impl ExecutableTarget {
787    /// Try to load a Wasmer package from a directory containing a `wasmer.toml`
788    /// file.
789    #[tracing::instrument(level = "debug", skip_all)]
790    fn from_dir(
791        dir: &Path,
792        runtime: &Arc<dyn Runtime + Send + Sync>,
793        pb: &ProgressBar,
794    ) -> Result<Self, Error> {
795        pb.set_message(format!("Loading \"{}\" into memory", dir.display()));
796        pb.set_message("Resolving dependencies");
797        let inner_runtime = runtime.clone();
798        let pkg = runtime.task_manager().spawn_and_block_on({
799            let path = dir.to_path_buf();
800
801            async move { BinaryPackage::from_dir(&path, inner_runtime.as_ref()).await }
802        })??;
803
804        Ok(ExecutableTarget::Package(Box::new(pkg)))
805    }
806
807    /// Try to load a file into something that can be used to run it.
808    #[tracing::instrument(level = "debug", skip_all)]
809    fn from_file(
810        path: &Path,
811        runtime: &Arc<dyn Runtime + Send + Sync>,
812        pb: &ProgressBar,
813    ) -> Result<Self, Error> {
814        pb.set_message(format!("Loading from \"{}\"", path.display()));
815
816        match TargetOnDisk::from_file(path)? {
817            TargetOnDisk::WebAssemblyBinary | TargetOnDisk::Wat => {
818                let wasm = std::fs::read(path)?;
819                let module_data = HashedModuleData::new(wasm);
820                let module_hash = *module_data.hash();
821
822                pb.set_message("Compiling to WebAssembly");
823                let module = runtime
824                    .load_hashed_module_sync(module_data, None)
825                    .with_context(|| format!("Unable to compile \"{}\"", path.display()))?;
826
827                Ok(ExecutableTarget::WebAssembly {
828                    module,
829                    module_hash,
830                    path: path.to_path_buf(),
831                })
832            }
833            TargetOnDisk::Artifact => {
834                let engine = runtime.engine();
835                pb.set_message("Deserializing pre-compiled WebAssembly module");
836                let module = unsafe { Module::deserialize_from_file(&engine, path)? };
837
838                let module_hash = module.info().hash.ok_or_else(|| {
839                    anyhow::Error::msg("module hash is not present in the artifact")
840                })?;
841
842                Ok(ExecutableTarget::WebAssembly {
843                    module,
844                    module_hash,
845                    path: path.to_path_buf(),
846                })
847            }
848            TargetOnDisk::LocalWebc => {
849                let container = from_disk(path)?;
850                pb.set_message("Resolving dependencies");
851
852                let inner_runtime = runtime.clone();
853                let pkg = runtime.task_manager().spawn_and_block_on(async move {
854                    BinaryPackage::from_webc(&container, inner_runtime.as_ref()).await
855                })??;
856                Ok(ExecutableTarget::Package(Box::new(pkg)))
857            }
858        }
859    }
860}
861
862#[cfg(feature = "coredump")]
863fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
864    let err: &wasmer::RuntimeError = match err.downcast_ref() {
865        Some(e) => e,
866        None => {
867            log::warn!("no runtime error found to generate coredump with");
868            return Ok(());
869        }
870    };
871
872    let mut coredump_builder =
873        wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
874
875    let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
876
877    for frame in err.trace() {
878        let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
879            .codeoffset(frame.func_offset() as u32)
880            .funcidx(frame.func_index())
881            .build();
882        thread_builder.add_frame(coredump_frame);
883    }
884
885    coredump_builder.add_thread(thread_builder.build());
886
887    let coredump = coredump_builder
888        .serialize()
889        .map_err(Error::msg)
890        .context("Coredump serializing failed")?;
891
892    std::fs::write(coredump_path, &coredump).with_context(|| {
893        format!(
894            "Unable to save the coredump to \"{}\"",
895            coredump_path.display()
896        )
897    })?;
898
899    Ok(())
900}
901
902#[derive(Debug, Clone, Parser)]
903pub(crate) struct WcgiOptions {
904    /// The address to serve on.
905    #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
906    pub(crate) addr: SocketAddr,
907}
908
909impl Default for WcgiOptions {
910    fn default() -> Self {
911        Self {
912            addr: ([127, 0, 0, 1], 8000).into(),
913        }
914    }
915}
916
917#[derive(Debug)]
918struct Callbacks {
919    stderr: Mutex<LineWriter<std::io::Stderr>>,
920    addr: SocketAddr,
921}
922
923impl Callbacks {
924    fn new(addr: SocketAddr) -> Self {
925        Callbacks {
926            stderr: Mutex::new(LineWriter::new(std::io::stderr())),
927            addr,
928        }
929    }
930}
931
932impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
933    fn started(&self, _abort: AbortHandle) {
934        println!("WCGI Server running at http://{}/", self.addr);
935    }
936
937    fn on_stderr(&self, raw_message: &[u8]) {
938        if let Ok(mut stderr) = self.stderr.lock() {
939            // If the WCGI runner printed any log messages we want to make sure
940            // they get propagated to the user. Line buffering is important here
941            // because it helps prevent the output from becoming a complete
942            // mess.
943            let _ = stderr.write_all(raw_message);
944        }
945    }
946}
947
948/// Exit the current process, using the WASI exit code if the error contains
949/// one.
950fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
951    let exit_code = match result {
952        Ok(_) => 0,
953        Err(error) => {
954            match error.chain().find_map(get_exit_code) {
955                Some(exit_code) => exit_code.raw(),
956                None => {
957                    eprintln!("{:?}", PrettyError::new(error));
958                    // Something else happened
959                    1
960                }
961            }
962        }
963    };
964
965    std::io::stdout().flush().ok();
966    std::io::stderr().flush().ok();
967
968    std::process::exit(exit_code);
969}
970
971fn get_exit_code(
972    error: &(dyn std::error::Error + 'static),
973) -> Option<wasmer_wasix::types::wasi::ExitCode> {
974    if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
975        return Some(*exit_code);
976    }
977    if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
978        return error.as_exit_code();
979    }
980
981    None
982}
983
984#[derive(Debug)]
985struct MonitoringRuntime<R> {
986    runtime: Arc<R>,
987    progress: ProgressBar,
988}
989
990impl<R> MonitoringRuntime<R> {
991    fn new(runtime: R, progress: ProgressBar) -> Self {
992        MonitoringRuntime {
993            runtime: Arc::new(runtime),
994            progress,
995        }
996    }
997}
998
999impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
1000    fn networking(&self) -> &virtual_net::DynVirtualNetworking {
1001        self.runtime.networking()
1002    }
1003
1004    fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
1005        self.runtime.task_manager()
1006    }
1007
1008    fn package_loader(
1009        &self,
1010    ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
1011        let inner = self.runtime.package_loader();
1012        Arc::new(MonitoringPackageLoader {
1013            inner,
1014            progress: self.progress.clone(),
1015        })
1016    }
1017
1018    fn module_cache(
1019        &self,
1020    ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
1021        self.runtime.module_cache()
1022    }
1023
1024    fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
1025        let inner = self.runtime.source();
1026        Arc::new(MonitoringSource {
1027            inner,
1028            progress: self.progress.clone(),
1029        })
1030    }
1031
1032    fn engine(&self) -> wasmer::Engine {
1033        self.runtime.engine()
1034    }
1035
1036    fn new_store(&self) -> wasmer::Store {
1037        self.runtime.new_store()
1038    }
1039
1040    fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
1041        self.runtime.http_client()
1042    }
1043
1044    fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
1045        self.runtime.tty()
1046    }
1047
1048    #[cfg(feature = "journal")]
1049    fn read_only_journals<'a>(
1050        &'a self,
1051    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
1052        self.runtime.read_only_journals()
1053    }
1054
1055    #[cfg(feature = "journal")]
1056    fn writable_journals<'a>(
1057        &'a self,
1058    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
1059        self.runtime.writable_journals()
1060    }
1061
1062    #[cfg(feature = "journal")]
1063    fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
1064        self.runtime.active_journal()
1065    }
1066
1067    fn load_hashed_module(
1068        &self,
1069        module: HashedModuleData,
1070        engine: Option<&Engine>,
1071    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1072        let hash = *module.hash();
1073        let fut = self.runtime.load_hashed_module(module, engine);
1074        Box::pin(compile_with_progress(fut, hash, None))
1075    }
1076
1077    fn load_hashed_module_sync(
1078        &self,
1079        wasm: HashedModuleData,
1080        engine: Option<&Engine>,
1081    ) -> Result<Module, wasmer_wasix::SpawnError> {
1082        let hash = *wasm.hash();
1083        compile_with_progress_sync(
1084            || self.runtime.load_hashed_module_sync(wasm, engine),
1085            &hash,
1086            None,
1087        )
1088    }
1089
1090    fn load_command_module(
1091        &self,
1092        cmd: &BinaryPackageCommand,
1093    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1094        let fut = self.runtime.load_command_module(cmd);
1095
1096        Box::pin(compile_with_progress(
1097            fut,
1098            *cmd.hash(),
1099            Some(cmd.name().to_owned()),
1100        ))
1101    }
1102
1103    fn load_command_module_sync(
1104        &self,
1105        cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand,
1106    ) -> Result<Module, wasmer_wasix::SpawnError> {
1107        compile_with_progress_sync(
1108            || self.runtime.load_command_module_sync(cmd),
1109            cmd.hash(),
1110            Some(cmd.name()),
1111        )
1112    }
1113}
1114
1115async fn compile_with_progress<'a, F, T>(fut: F, hash: ModuleHash, name: Option<String>) -> T
1116where
1117    F: std::future::Future<Output = T> + Send + 'a,
1118    T: Send + 'static,
1119{
1120    let mut pb = new_progressbar_compile(&hash, name.as_deref());
1121    let res = fut.await;
1122    pb.finish_and_clear();
1123    res
1124}
1125
1126fn compile_with_progress_sync<F, T>(f: F, hash: &ModuleHash, name: Option<&str>) -> T
1127where
1128    F: FnOnce() -> T,
1129{
1130    let mut pb = new_progressbar_compile(hash, name);
1131    let res = f();
1132    pb.finish_and_clear();
1133    res
1134}
1135
1136fn new_progressbar_compile(hash: &ModuleHash, name: Option<&str>) -> ProgressBar {
1137    // Only show a spinner if we're running in a TTY
1138    if std::io::stderr().is_terminal() {
1139        let msg = if let Some(name) = name {
1140            format!("Compiling WebAssembly module for command '{name}' ({hash})...")
1141        } else {
1142            format!("Compiling WebAssembly module {hash}...")
1143        };
1144        let pb = ProgressBar::new_spinner().with_message(msg);
1145        pb.enable_steady_tick(Duration::from_millis(100));
1146        pb
1147    } else {
1148        ProgressBar::hidden()
1149    }
1150}
1151
1152#[derive(Debug)]
1153struct MonitoringSource {
1154    inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
1155    progress: ProgressBar,
1156}
1157
1158#[async_trait::async_trait]
1159impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
1160    async fn query(
1161        &self,
1162        package: &PackageSpecifier,
1163    ) -> Result<Vec<wasmer_wasix::runtime::resolver::PackageSummary>, QueryError> {
1164        self.progress.set_message(format!("Looking up {package}"));
1165        self.inner.query(package).await
1166    }
1167}
1168
1169#[derive(Debug)]
1170struct MonitoringPackageLoader {
1171    inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
1172    progress: ProgressBar,
1173}
1174
1175#[async_trait::async_trait]
1176impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
1177    async fn load(
1178        &self,
1179        summary: &wasmer_wasix::runtime::resolver::PackageSummary,
1180    ) -> Result<Container, Error> {
1181        let pkg_id = summary.package_id();
1182        self.progress.set_message(format!("Downloading {pkg_id}"));
1183
1184        self.inner.load(summary).await
1185    }
1186
1187    async fn load_package_tree(
1188        &self,
1189        root: &Container,
1190        resolution: &wasmer_wasix::runtime::resolver::Resolution,
1191        root_is_local_dir: bool,
1192    ) -> Result<BinaryPackage, Error> {
1193        self.inner
1194            .load_package_tree(root, resolution, root_is_local_dir)
1195            .await
1196    }
1197}