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