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