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#[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 #[clap(long = "stack-size")]
95 stack_size: Option<usize>,
96 #[clap(short, long, aliases = &["command", "command-name"])]
98 entrypoint: Option<String>,
99 #[clap(short, long)]
101 invoke: Option<String>,
102 #[clap(name = "COREDUMP_PATH", long)]
104 coredump_on_trap: Option<PathBuf>,
105 #[clap(value_parser = CliPackageSource::infer)]
107 input: CliPackageSource,
108 args: Vec<String>,
110 #[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 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 let mut wasm_bytes: Option<Vec<u8>> = None;
149
150 tracing::info!("Input source: {:?}", self.input);
152 if let CliPackageSource::File(path) = &self.input {
153 tracing::info!("Input file path: {}", path.display());
154
155 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 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 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 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 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 if let Some(cmd) = pkg.get_entrypoint_command()
269 && let Some(features) = cmd.wasm_features()
270 {
271 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 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 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 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 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 #[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 let _ = stderr.write_all(raw_message);
773 }
774 }
775}
776
777fn 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 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}