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#[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 #[clap(long = "stack-size")]
91 stack_size: Option<usize>,
92 #[clap(short, long, aliases = &["command", "command-name"])]
94 entrypoint: Option<String>,
95 #[clap(short, long)]
97 invoke: Option<String>,
98 #[clap(name = "COREDUMP_PATH", long)]
100 coredump_on_trap: Option<PathBuf>,
101 #[clap(value_parser = CliPackageSource::infer)]
103 input: CliPackageSource,
104 args: Vec<String>,
106 #[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 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 let mut wasm_bytes: Option<Vec<u8>> = None;
145
146 tracing::info!("Input source: {:?}", self.input);
148 if let CliPackageSource::File(path) = &self.input {
149 tracing::info!("Input file path: {}", path.display());
150
151 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 let magic = [0x00, 0x61, 0x73, 0x6D]; if bytes.len() >= 4 && bytes[0..4] == magic {
161 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 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 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 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 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 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 if let Some(cmd) = pkg.get_entrypoint_command()
260 && let Some(features) = cmd.wasm_features()
261 {
262 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 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 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 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 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 #[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 let _ = stderr.write_all(raw_message);
764 }
765 }
766}
767
768fn 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 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}