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