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