wasmer_cli/commands/run/
mod.rs

1#![allow(missing_docs, unused)]
2
3mod capabilities;
4mod package_source;
5mod runtime;
6mod target;
7mod wasi;
8
9use std::{
10    collections::{BTreeMap, hash_map::DefaultHasher},
11    fmt::{Binary, Display},
12    fs::File,
13    hash::{BuildHasherDefault, Hash, Hasher},
14    io::{ErrorKind, LineWriter, Read, Write},
15    net::SocketAddr,
16    path::{Path, PathBuf},
17    str::FromStr,
18    sync::{Arc, Mutex},
19    time::{Duration, SystemTime, UNIX_EPOCH},
20};
21
22use anyhow::{Context, Error, anyhow, bail};
23use clap::{Parser, ValueEnum};
24use colored::Colorize;
25use futures::future::BoxFuture;
26use indicatif::{MultiProgress, ProgressBar};
27use once_cell::sync::Lazy;
28use tempfile::NamedTempFile;
29use url::Url;
30#[cfg(feature = "sys")]
31use wasmer::sys::NativeEngineExt;
32use wasmer::{
33    AsStoreMut, DeserializeError, Engine, Function, Imports, Instance, Module, RuntimeError, Store,
34    Type, TypedFunction, Value, wat2wasm,
35};
36
37use wasmer_types::{Features, target::Target};
38
39#[cfg(feature = "compiler")]
40use wasmer_compiler::ArtifactBuild;
41use wasmer_config::package::PackageSource;
42use wasmer_package::utils::from_disk;
43use wasmer_types::ModuleHash;
44
45#[cfg(feature = "journal")]
46use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
47use wasmer_wasix::{
48    Runtime, SpawnError, WasiError,
49    bin_factory::{BinaryPackage, BinaryPackageCommand},
50    journal::CompactingLogFileJournal,
51    runners::{
52        MappedCommand, MappedDirectory, Runner,
53        dcgi::{DcgiInstanceFactory, DcgiRunner},
54        dproxy::DProxyRunner,
55        wasi::{RuntimeOrEngine, WasiRunner},
56        wcgi::{self, AbortHandle, NoOpWcgiCallbacks, WcgiRunner},
57    },
58    runtime::{
59        module_cache::{CacheError, HashedModuleData},
60        package_loader::PackageLoader,
61        resolver::QueryError,
62        task_manager::VirtualTaskManagerExt,
63    },
64};
65use webc::Container;
66use webc::metadata::Manifest;
67
68use crate::{
69    backend::RuntimeOptions,
70    commands::run::{target::TargetOnDisk, wasi::Wasi},
71    config::WasmerEnv,
72    error::PrettyError,
73    logging::Output,
74};
75
76use self::{
77    package_source::CliPackageSource, runtime::MonitoringRuntime, target::ExecutableTarget,
78};
79
80const TICK: Duration = Duration::from_millis(250);
81
82/// The unstable `wasmer run` subcommand.
83#[derive(Debug, Parser)]
84pub struct Run {
85    #[clap(flatten)]
86    env: WasmerEnv,
87    #[clap(flatten)]
88    rt: RuntimeOptions,
89    #[clap(flatten)]
90    wasi: crate::commands::run::Wasi,
91    #[clap(flatten)]
92    wcgi: WcgiOptions,
93    /// Set the default stack size (default is 1048576)
94    #[clap(long = "stack-size")]
95    stack_size: Option<usize>,
96    /// The entrypoint module for webc packages.
97    #[clap(short, long, aliases = &["command", "command-name"])]
98    entrypoint: Option<String>,
99    /// The function to invoke.
100    #[clap(short, long)]
101    invoke: Option<String>,
102    /// Generate a coredump at this path if a WebAssembly trap occurs
103    #[clap(name = "COREDUMP_PATH", long)]
104    coredump_on_trap: Option<PathBuf>,
105    /// The file, URL, or package to run.
106    #[clap(value_parser = CliPackageSource::infer)]
107    input: CliPackageSource,
108    /// Command-line arguments passed to the package
109    args: Vec<String>,
110}
111
112impl Run {
113    pub fn execute(self, output: Output) -> ! {
114        let result = self.execute_inner(output);
115        exit_with_wasi_exit_code(result);
116    }
117
118    #[tracing::instrument(level = "debug", name = "wasmer_run", skip_all)]
119    fn execute_inner(mut self, output: Output) -> Result<(), Error> {
120        self.print_option_warnings();
121
122        let pb = ProgressBar::new_spinner();
123        pb.set_draw_target(output.draw_target());
124        pb.enable_steady_tick(TICK);
125
126        pb.set_message("Initializing the WebAssembly VM");
127
128        let runtime = tokio::runtime::Builder::new_multi_thread()
129            .enable_all()
130            .build()?;
131        let handle = runtime.handle().clone();
132
133        // Check for the preferred webc version.
134        // Default to v3.
135        let webc_version_var = std::env::var("WASMER_WEBC_VERSION");
136        let preferred_webc_version = match webc_version_var.as_deref() {
137            Ok("2") => webc::Version::V2,
138            Ok("3") | Err(_) => webc::Version::V3,
139            Ok(other) => {
140                bail!("unknown webc version: '{other}'");
141            }
142        };
143
144        let _guard = handle.enter();
145
146        // Get the input file path
147        let mut wasm_bytes: Option<Vec<u8>> = None;
148
149        // Try to detect WebAssembly features before selecting a backend
150        tracing::info!("Input source: {:?}", self.input);
151        if let CliPackageSource::File(path) = &self.input {
152            tracing::info!("Input file path: {}", path.display());
153
154            // Try to read and detect any file that exists, regardless of extension
155            let target = TargetOnDisk::from_file(path);
156            if let Ok(target) = target {
157                match target {
158                    TargetOnDisk::WebAssemblyBinary => {
159                        if let Ok(data) = std::fs::read(path) {
160                            wasm_bytes = Some(data);
161                        } else {
162                            tracing::info!("Failed to read file: {}", path.display());
163                        }
164                    }
165                    TargetOnDisk::Wat => match std::fs::read(path) {
166                        Ok(data) => match wat2wasm(&data) {
167                            Ok(wasm) => {
168                                wasm_bytes = Some(wasm.to_vec());
169                            }
170                            Err(e) => {
171                                tracing::info!(
172                                    "Failed to convert WAT to Wasm for {}: {e}",
173                                    path.display()
174                                );
175                            }
176                        },
177                        Err(e) => {
178                            tracing::info!("Failed to read WAT file {}: {e}", path.display());
179                        }
180                    },
181                    _ => {}
182                }
183            } else {
184                tracing::info!(
185                    "Failed to read file for feature detection: {}",
186                    path.display()
187                );
188            }
189        } else {
190            tracing::info!("Input is not a file, skipping WebAssembly feature detection");
191        }
192
193        // Get engine with feature-based backend selection if possible
194        let mut engine = match &wasm_bytes {
195            Some(wasm_bytes) => {
196                tracing::info!("Attempting to detect WebAssembly features from binary");
197
198                self.rt
199                    .get_engine_for_module(wasm_bytes, &Target::default())?
200            }
201            None => {
202                // No WebAssembly file available for analysis, check if we have a webc package
203                if let CliPackageSource::Package(pkg_source) = &self.input {
204                    tracing::info!("Checking package for WebAssembly features: {}", pkg_source);
205                    self.rt.get_engine(&Target::default())?
206                } else {
207                    tracing::info!("No feature detection possible, using default engine");
208                    self.rt.get_engine(&Target::default())?
209                }
210            }
211        };
212
213        let engine_kind = engine.deterministic_id();
214        tracing::info!("Executing on backend {engine_kind:?}");
215
216        #[cfg(feature = "sys")]
217        if engine.is_sys() && self.stack_size.is_some() {
218            wasmer_vm::set_stack_size(self.stack_size.unwrap());
219        }
220
221        let engine = engine.clone();
222
223        let runtime = self.wasi.prepare_runtime(
224            engine,
225            &self.env,
226            &capabilities::get_capability_cache_path(&self.env, &self.input)?,
227            runtime,
228            preferred_webc_version,
229            self.rt.compiler_debug_dir.is_some(),
230        )?;
231
232        // This is a slow operation, so let's temporarily wrap the runtime with
233        // something that displays progress
234        let monitoring_runtime = Arc::new(MonitoringRuntime::new(
235            runtime,
236            pb.clone(),
237            output.is_quiet_or_no_tty(),
238        ));
239        let runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime.runtime.clone();
240        let monitoring_runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime;
241
242        let target = self.input.resolve_target(&monitoring_runtime, &pb)?;
243
244        if let ExecutableTarget::Package(ref pkg) = target {
245            self.wasi
246                .volumes
247                .extend(pkg.additional_host_mapped_directories.clone());
248        }
249
250        pb.finish_and_clear();
251
252        // push the TTY state so we can restore it after the program finishes
253        let tty = runtime.tty().map(|tty| tty.tty_get());
254
255        let result = {
256            match target {
257                ExecutableTarget::WebAssembly {
258                    module,
259                    module_hash,
260                    path,
261                } => self.execute_wasm(&path, module, module_hash, runtime.clone()),
262                ExecutableTarget::Package(pkg) => {
263                    // Check if we should update the engine based on the WebC package features
264                    if let Some(cmd) = pkg.get_entrypoint_command()
265                        && let Some(features) = cmd.wasm_features()
266                    {
267                        // Get the right engine for these features
268                        let backends = self.rt.get_available_backends()?;
269                        let available_engines = backends
270                            .iter()
271                            .map(|b| b.to_string())
272                            .collect::<Vec<_>>()
273                            .join(", ");
274
275                        let filtered_backends = RuntimeOptions::filter_backends_by_features(
276                            backends.clone(),
277                            &features,
278                            &Target::default(),
279                        );
280
281                        if !filtered_backends.is_empty() {
282                            let engine_id = filtered_backends[0].to_string();
283
284                            // Get a new engine that's compatible with the required features
285                            if let Ok(new_engine) = filtered_backends[0].get_engine(
286                                &Target::default(),
287                                &features,
288                                &self.rt,
289                            ) {
290                                tracing::info!(
291                                    "The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
292                                    cmd.name(),
293                                    features,
294                                    available_engines,
295                                    engine_id
296                                );
297                                // Create a new runtime with the updated engine
298                                let capability_cache_path =
299                                    capabilities::get_capability_cache_path(
300                                        &self.env,
301                                        &self.input,
302                                    )?;
303                                let new_runtime = self.wasi.prepare_runtime(
304                                    new_engine,
305                                    &self.env,
306                                    &capability_cache_path,
307                                    tokio::runtime::Builder::new_multi_thread()
308                                        .enable_all()
309                                        .build()?,
310                                    preferred_webc_version,
311                                    self.rt.compiler_debug_dir.is_some(),
312                                )?;
313
314                                let new_runtime = Arc::new(MonitoringRuntime::new(
315                                    new_runtime,
316                                    pb.clone(),
317                                    output.is_quiet_or_no_tty(),
318                                ));
319                                return self.execute_webc(&pkg, new_runtime);
320                            }
321                        }
322                    }
323                    self.execute_webc(&pkg, monitoring_runtime)
324                }
325            }
326        };
327
328        // restore the TTY state as the execution may have changed it
329        if let Some(state) = tty
330            && let Some(tty) = runtime.tty()
331        {
332            tty.tty_set(state);
333        }
334
335        if let Err(e) = &result {
336            self.maybe_save_coredump(e);
337        }
338
339        result
340    }
341
342    #[tracing::instrument(skip_all)]
343    fn execute_wasm(
344        &self,
345        path: &Path,
346        module: Module,
347        module_hash: ModuleHash,
348        runtime: Arc<dyn Runtime + Send + Sync>,
349    ) -> Result<(), Error> {
350        if wasmer_wasix::is_wasi_module(&module) || wasmer_wasix::is_wasix_module(&module) {
351            self.execute_wasi_module(path, module, module_hash, runtime)
352        } else {
353            self.execute_pure_wasm_module(&module)
354        }
355    }
356
357    #[tracing::instrument(skip_all)]
358    fn execute_webc(
359        &self,
360        pkg: &BinaryPackage,
361        runtime: Arc<dyn Runtime + Send + Sync>,
362    ) -> Result<(), Error> {
363        let id = match self.entrypoint.as_deref() {
364            Some(cmd) => cmd,
365            None => pkg.infer_entrypoint()?,
366        };
367        let cmd = pkg
368            .get_command(id)
369            .with_context(|| format!("Unable to get metadata for the \"{id}\" command"))?;
370
371        let uses = self.load_injected_packages(&runtime)?;
372
373        if DcgiRunner::can_run_command(cmd.metadata())? {
374            self.run_dcgi(id, pkg, uses, runtime)
375        } else if DProxyRunner::can_run_command(cmd.metadata())? {
376            self.run_dproxy(id, pkg, runtime)
377        } else if WcgiRunner::can_run_command(cmd.metadata())? {
378            self.run_wcgi(id, pkg, uses, runtime)
379        } else if WasiRunner::can_run_command(cmd.metadata())? {
380            self.run_wasi(id, pkg, uses, runtime)
381        } else {
382            bail!(
383                "Unable to find a runner that supports \"{}\"",
384                cmd.metadata().runner
385            );
386        }
387    }
388
389    #[tracing::instrument(level = "debug", skip_all)]
390    fn load_injected_packages(
391        &self,
392        runtime: &Arc<dyn Runtime + Send + Sync>,
393    ) -> Result<Vec<BinaryPackage>, Error> {
394        let mut dependencies = Vec::new();
395
396        for name in &self.wasi.uses {
397            let specifier = name
398                .parse::<PackageSource>()
399                .with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
400            let pkg = {
401                let specifier = specifier.clone();
402                let inner_runtime = runtime.clone();
403                runtime
404                    .task_manager()
405                    .spawn_and_block_on(async move {
406                        BinaryPackage::from_registry(&specifier, inner_runtime.as_ref()).await
407                    })
408                    .with_context(|| format!("Unable to load \"{name}\""))??
409            };
410            dependencies.push(pkg);
411        }
412
413        Ok(dependencies)
414    }
415
416    fn run_wasi(
417        &self,
418        command_name: &str,
419        pkg: &BinaryPackage,
420        uses: Vec<BinaryPackage>,
421        runtime: Arc<dyn Runtime + Send + Sync>,
422    ) -> Result<(), Error> {
423        // Assume webcs are always WASIX
424        let mut runner = self.build_wasi_runner(&runtime, true)?;
425        Runner::run_command(&mut runner, command_name, pkg, runtime)
426    }
427
428    fn run_wcgi(
429        &self,
430        command_name: &str,
431        pkg: &BinaryPackage,
432        uses: Vec<BinaryPackage>,
433        runtime: Arc<dyn Runtime + Send + Sync>,
434    ) -> Result<(), Error> {
435        let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(NoOpWcgiCallbacks);
436        self.config_wcgi(runner.config(), uses)?;
437        runner.run_command(command_name, pkg, runtime)
438    }
439
440    fn config_wcgi(
441        &self,
442        config: &mut wcgi::Config,
443        uses: Vec<BinaryPackage>,
444    ) -> Result<(), Error> {
445        config
446            .args(self.args.clone())
447            .addr(self.wcgi.addr)
448            .envs(self.wasi.env_vars.clone())
449            .map_directories(self.wasi.all_volumes())
450            .callbacks(Callbacks::new(self.wcgi.addr))
451            .inject_packages(uses);
452        *config.capabilities() = self.wasi.capabilities();
453        if self.wasi.forward_host_env {
454            config.forward_host_env();
455        }
456
457        #[cfg(feature = "journal")]
458        {
459            for trigger in self.wasi.snapshot_on.iter().cloned() {
460                config.add_snapshot_trigger(trigger);
461            }
462            if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
463                config.add_default_snapshot_triggers();
464            }
465            if let Some(period) = self.wasi.snapshot_interval {
466                if self.wasi.writable_journals.is_empty() {
467                    return Err(anyhow::format_err!(
468                        "If you specify a snapshot interval then you must also specify a writable journal file"
469                    ));
470                }
471                config.with_snapshot_interval(Duration::from_millis(period));
472            }
473            if self.wasi.stop_after_snapshot {
474                config.with_stop_running_after_snapshot(true);
475            }
476            let (r, w) = self.wasi.build_journals()?;
477            for journal in r {
478                config.add_read_only_journal(journal);
479            }
480            for journal in w {
481                config.add_writable_journal(journal);
482            }
483        }
484
485        Ok(())
486    }
487
488    fn run_dcgi(
489        &self,
490        command_name: &str,
491        pkg: &BinaryPackage,
492        uses: Vec<BinaryPackage>,
493        runtime: Arc<dyn Runtime + Send + Sync>,
494    ) -> Result<(), Error> {
495        let factory = DcgiInstanceFactory::new();
496        let mut runner = wasmer_wasix::runners::dcgi::DcgiRunner::new(factory);
497        self.config_wcgi(runner.config().inner(), uses);
498        runner.run_command(command_name, pkg, runtime)
499    }
500
501    fn run_dproxy(
502        &self,
503        command_name: &str,
504        pkg: &BinaryPackage,
505        runtime: Arc<dyn Runtime + Send + Sync>,
506    ) -> Result<(), Error> {
507        let mut inner = self.build_wasi_runner(&runtime, true)?;
508        let mut runner = wasmer_wasix::runners::dproxy::DProxyRunner::new(inner, pkg);
509        runner.run_command(command_name, pkg, runtime)
510    }
511
512    #[tracing::instrument(skip_all)]
513    fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
514        /// The rest of the execution happens in the main thread, so we can create the
515        /// store here.
516        let mut store = self.rt.get_store()?;
517        let imports = Imports::default();
518        let instance = Instance::new(&mut store, module, &imports)
519            .context("Unable to instantiate the WebAssembly module")?;
520
521        let entry_function  = match &self.invoke {
522            Some(entry) => {
523                instance.exports
524                    .get_function(entry)
525                    .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
526            },
527            None => {
528                instance.exports.get_function("_start")
529                    .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
530            }
531        };
532
533        let result = invoke_function(&instance, &mut store, entry_function, &self.args)?;
534
535        match result {
536            Ok(return_values) => {
537                println!(
538                    "{}",
539                    return_values
540                        .iter()
541                        .map(|val| val.to_string())
542                        .collect::<Vec<String>>()
543                        .join(" ")
544                );
545                Ok(())
546            }
547            Err(err) => {
548                bail!("{}", err.display(&mut store));
549            }
550        }
551    }
552
553    fn build_wasi_runner(
554        &self,
555        runtime: &Arc<dyn Runtime + Send + Sync>,
556        is_wasix: bool,
557    ) -> Result<WasiRunner, anyhow::Error> {
558        let packages = self.load_injected_packages(runtime)?;
559
560        let mut runner = WasiRunner::new();
561
562        let (is_home_mapped, mapped_directories) = self.wasi.build_mapped_directories(is_wasix)?;
563
564        runner
565            .with_args(&self.args)
566            .with_injected_packages(packages)
567            .with_envs(self.wasi.env_vars.clone())
568            .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
569            .with_mapped_directories(mapped_directories)
570            .with_home_mapped(is_home_mapped)
571            .with_forward_host_env(self.wasi.forward_host_env)
572            .with_capabilities(self.wasi.capabilities());
573
574        if let Some(cwd) = self.wasi.cwd.as_ref() {
575            if !cwd.starts_with("/") {
576                bail!("The argument to --cwd must be an absolute path");
577            }
578            runner.with_current_dir(cwd.clone());
579        }
580
581        if let Some(ref entry_function) = self.invoke {
582            runner.with_entry_function(entry_function);
583        }
584
585        #[cfg(feature = "journal")]
586        {
587            for trigger in self.wasi.snapshot_on.iter().cloned() {
588                runner.with_snapshot_trigger(trigger);
589            }
590            if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
591                runner.with_default_snapshot_triggers();
592            }
593            if let Some(period) = self.wasi.snapshot_interval {
594                if self.wasi.writable_journals.is_empty() {
595                    return Err(anyhow::format_err!(
596                        "If you specify a snapshot interval then you must also specify a writable journal file"
597                    ));
598                }
599                runner.with_snapshot_interval(Duration::from_millis(period));
600            }
601            if self.wasi.stop_after_snapshot {
602                runner.with_stop_running_after_snapshot(true);
603            }
604            let (r, w) = self.wasi.build_journals()?;
605            for journal in r {
606                runner.with_read_only_journal(journal);
607            }
608            for journal in w {
609                runner.with_writable_journal(journal);
610            }
611            runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
612        }
613
614        Ok(runner)
615    }
616
617    #[tracing::instrument(skip_all)]
618    fn execute_wasi_module(
619        &self,
620        wasm_path: &Path,
621        module: Module,
622        module_hash: ModuleHash,
623        runtime: Arc<dyn Runtime + Send + Sync>,
624    ) -> Result<(), Error> {
625        let program_name = wasm_path.display().to_string();
626
627        let runner = self.build_wasi_runner(&runtime, wasmer_wasix::is_wasix_module(&module))?;
628        runner.run_wasm(
629            RuntimeOrEngine::Runtime(runtime),
630            &program_name,
631            module,
632            module_hash,
633        )
634    }
635
636    #[allow(unused_variables)]
637    fn maybe_save_coredump(&self, e: &Error) {
638        #[cfg(feature = "coredump")]
639        if let Some(coredump) = &self.coredump_on_trap
640            && let Err(e) = generate_coredump(e, self.input.to_string(), coredump)
641        {
642            tracing::warn!(
643                error = &*e as &dyn std::error::Error,
644                coredump_path=%coredump.display(),
645                "Unable to generate a coredump",
646            );
647        }
648    }
649
650    fn print_option_warnings(&self) {
651        if !self.wasi.mapped_dirs.is_empty() {
652            eprintln!(
653                "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
654                "warning: ".yellow(),
655                "--mapdir".yellow(),
656                "--volume".green()
657            );
658        }
659        if !self.wasi.pre_opened_directories.is_empty() {
660            eprintln!(
661                "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
662                "warning: ".yellow(),
663                "--dir".yellow(),
664                "--volume".green()
665            );
666        }
667    }
668}
669
670fn invoke_function(
671    instance: &Instance,
672    store: &mut Store,
673    func: &Function,
674    args: &[String],
675) -> anyhow::Result<Result<Box<[Value]>, RuntimeError>> {
676    let func_ty = func.ty(store);
677    let required_arguments = func_ty.params().len();
678    let provided_arguments = args.len();
679
680    anyhow::ensure!(
681        required_arguments == provided_arguments,
682        "Function expected {required_arguments} arguments, but received {provided_arguments}"
683    );
684
685    let invoke_args = args
686        .iter()
687        .zip(func_ty.params().iter())
688        .map(|(arg, param_type)| {
689            parse_value(arg, *param_type)
690                .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
691        })
692        .collect::<Result<Vec<_>, _>>()?;
693
694    Ok(func.call(store, &invoke_args))
695}
696
697fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
698    let value = match ty {
699        Type::I32 => Value::I32(s.parse()?),
700        Type::I64 => Value::I64(s.parse()?),
701        Type::F32 => Value::F32(s.parse()?),
702        Type::F64 => Value::F64(s.parse()?),
703        Type::V128 => Value::V128(s.parse()?),
704        _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
705    };
706    Ok(value)
707}
708
709#[cfg(feature = "coredump")]
710fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
711    let err: &wasmer::RuntimeError = match err.downcast_ref() {
712        Some(e) => e,
713        None => {
714            log::warn!("no runtime error found to generate coredump with");
715            return Ok(());
716        }
717    };
718
719    let mut coredump_builder =
720        wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
721
722    let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
723
724    for frame in err.trace() {
725        let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
726            .codeoffset(frame.func_offset() as u32)
727            .funcidx(frame.func_index())
728            .build();
729        thread_builder.add_frame(coredump_frame);
730    }
731
732    coredump_builder.add_thread(thread_builder.build());
733
734    let coredump = coredump_builder
735        .serialize()
736        .map_err(Error::msg)
737        .context("Coredump serializing failed")?;
738
739    std::fs::write(coredump_path, &coredump).with_context(|| {
740        format!(
741            "Unable to save the coredump to \"{}\"",
742            coredump_path.display()
743        )
744    })?;
745
746    Ok(())
747}
748
749#[derive(Debug, Clone, Parser)]
750pub(crate) struct WcgiOptions {
751    /// The address to serve on.
752    #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
753    pub(crate) addr: SocketAddr,
754}
755
756impl Default for WcgiOptions {
757    fn default() -> Self {
758        Self {
759            addr: ([127, 0, 0, 1], 8000).into(),
760        }
761    }
762}
763
764#[derive(Debug)]
765struct Callbacks {
766    stderr: Mutex<LineWriter<std::io::Stderr>>,
767    addr: SocketAddr,
768}
769
770impl Callbacks {
771    fn new(addr: SocketAddr) -> Self {
772        Callbacks {
773            stderr: Mutex::new(LineWriter::new(std::io::stderr())),
774            addr,
775        }
776    }
777}
778
779impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
780    fn started(&self, _abort: AbortHandle) {
781        println!("WCGI Server running at http://{}/", self.addr);
782    }
783
784    fn on_stderr(&self, raw_message: &[u8]) {
785        if let Ok(mut stderr) = self.stderr.lock() {
786            // If the WCGI runner printed any log messages we want to make sure
787            // they get propagated to the user. Line buffering is important here
788            // because it helps prevent the output from becoming a complete
789            // mess.
790            let _ = stderr.write_all(raw_message);
791        }
792    }
793}
794
795/// Exit the current process, using the WASI exit code if the error contains
796/// one.
797fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
798    let exit_code = match result {
799        Ok(_) => 0,
800        Err(error) => {
801            match error.chain().find_map(get_exit_code) {
802                Some(exit_code) => exit_code.raw(),
803                None => {
804                    eprintln!("{:?}", PrettyError::new(error));
805                    // Something else happened
806                    1
807                }
808            }
809        }
810    };
811
812    std::io::stdout().flush().ok();
813    std::io::stderr().flush().ok();
814
815    std::process::exit(exit_code);
816}
817
818fn get_exit_code(
819    error: &(dyn std::error::Error + 'static),
820) -> Option<wasmer_wasix::types::wasi::ExitCode> {
821    if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
822        return Some(*exit_code);
823    }
824    if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
825        return error.as_exit_code();
826    }
827
828    None
829}