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, monitoring_runtime)
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, true)?;
428 Runner::run_command(&mut runner, command_name, pkg, runtime)
429 }
430
431 fn run_wcgi(
432 &self,
433 command_name: &str,
434 pkg: &BinaryPackage,
435 uses: Vec<BinaryPackage>,
436 runtime: Arc<dyn Runtime + Send + Sync>,
437 ) -> Result<(), Error> {
438 let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(NoOpWcgiCallbacks);
439 self.config_wcgi(runner.config(), uses)?;
440 runner.run_command(command_name, pkg, runtime)
441 }
442
443 fn config_wcgi(
444 &self,
445 config: &mut wcgi::Config,
446 uses: Vec<BinaryPackage>,
447 ) -> Result<(), Error> {
448 config
449 .args(self.args.clone())
450 .addr(self.wcgi.addr)
451 .envs(self.wasi.env_vars.clone())
452 .map_directories(self.wasi.mapped_dirs.clone())
453 .callbacks(Callbacks::new(self.wcgi.addr))
454 .inject_packages(uses);
455 *config.capabilities() = self.wasi.capabilities();
456 if self.wasi.forward_host_env {
457 config.forward_host_env();
458 }
459
460 #[cfg(feature = "journal")]
461 {
462 for trigger in self.wasi.snapshot_on.iter().cloned() {
463 config.add_snapshot_trigger(trigger);
464 }
465 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
466 config.add_default_snapshot_triggers();
467 }
468 if let Some(period) = self.wasi.snapshot_interval {
469 if self.wasi.writable_journals.is_empty() {
470 return Err(anyhow::format_err!(
471 "If you specify a snapshot interval then you must also specify a writable journal file"
472 ));
473 }
474 config.with_snapshot_interval(Duration::from_millis(period));
475 }
476 if self.wasi.stop_after_snapshot {
477 config.with_stop_running_after_snapshot(true);
478 }
479 let (r, w) = self.wasi.build_journals()?;
480 for journal in r {
481 config.add_read_only_journal(journal);
482 }
483 for journal in w {
484 config.add_writable_journal(journal);
485 }
486 }
487
488 Ok(())
489 }
490
491 fn run_dcgi(
492 &self,
493 command_name: &str,
494 pkg: &BinaryPackage,
495 uses: Vec<BinaryPackage>,
496 runtime: Arc<dyn Runtime + Send + Sync>,
497 ) -> Result<(), Error> {
498 let factory = DcgiInstanceFactory::new();
499 let mut runner = wasmer_wasix::runners::dcgi::DcgiRunner::new(factory);
500 self.config_wcgi(runner.config().inner(), uses);
501 runner.run_command(command_name, pkg, runtime)
502 }
503
504 fn run_dproxy(
505 &self,
506 command_name: &str,
507 pkg: &BinaryPackage,
508 runtime: Arc<dyn Runtime + Send + Sync>,
509 ) -> Result<(), Error> {
510 let mut inner = self.build_wasi_runner(&runtime, true)?;
511 let mut runner = wasmer_wasix::runners::dproxy::DProxyRunner::new(inner, pkg);
512 runner.run_command(command_name, pkg, runtime)
513 }
514
515 #[tracing::instrument(skip_all)]
516 fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
517 let mut store = self.rt.get_store()?;
520 let imports = Imports::default();
521 let instance = Instance::new(&mut store, module, &imports)
522 .context("Unable to instantiate the WebAssembly module")?;
523
524 let entry_function = match &self.invoke {
525 Some(entry) => {
526 instance.exports
527 .get_function(entry)
528 .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
529 },
530 None => {
531 instance.exports.get_function("_start")
532 .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
533 }
534 };
535
536 let result = invoke_function(&instance, &mut store, entry_function, &self.args)?;
537
538 match result {
539 Ok(return_values) => {
540 println!(
541 "{}",
542 return_values
543 .iter()
544 .map(|val| val.to_string())
545 .collect::<Vec<String>>()
546 .join(" ")
547 );
548 Ok(())
549 }
550 Err(err) => {
551 bail!("{}", err.display(&mut store));
552 }
553 }
554 }
555
556 fn build_wasi_runner(
557 &self,
558 runtime: &Arc<dyn Runtime + Send + Sync>,
559 is_wasix: bool,
560 ) -> Result<WasiRunner, anyhow::Error> {
561 let packages = self.load_injected_packages(runtime)?;
562
563 let mut runner = WasiRunner::new();
564
565 let (is_home_mapped, mapped_diretories) = self.wasi.build_mapped_directories(is_wasix)?;
566
567 runner
568 .with_args(&self.args)
569 .with_injected_packages(packages)
570 .with_envs(self.wasi.env_vars.clone())
571 .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
572 .with_mapped_directories(mapped_diretories)
573 .with_home_mapped(is_home_mapped)
574 .with_forward_host_env(self.wasi.forward_host_env)
575 .with_capabilities(self.wasi.capabilities());
576
577 if let Some(cwd) = self.wasi.cwd.as_ref() {
578 if !cwd.starts_with("/") {
579 bail!("The argument to --cwd must be an absolute path");
580 }
581 runner.with_current_dir(cwd.clone());
582 }
583
584 if let Some(ref entry_function) = self.invoke {
585 runner.with_entry_function(entry_function);
586 }
587
588 #[cfg(feature = "journal")]
589 {
590 for trigger in self.wasi.snapshot_on.iter().cloned() {
591 runner.with_snapshot_trigger(trigger);
592 }
593 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
594 runner.with_default_snapshot_triggers();
595 }
596 if let Some(period) = self.wasi.snapshot_interval {
597 if self.wasi.writable_journals.is_empty() {
598 return Err(anyhow::format_err!(
599 "If you specify a snapshot interval then you must also specify a writable journal file"
600 ));
601 }
602 runner.with_snapshot_interval(Duration::from_millis(period));
603 }
604 if self.wasi.stop_after_snapshot {
605 runner.with_stop_running_after_snapshot(true);
606 }
607 let (r, w) = self.wasi.build_journals()?;
608 for journal in r {
609 runner.with_read_only_journal(journal);
610 }
611 for journal in w {
612 runner.with_writable_journal(journal);
613 }
614 runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
615 }
616
617 Ok(runner)
618 }
619
620 #[tracing::instrument(skip_all)]
621 fn execute_wasi_module(
622 &self,
623 wasm_path: &Path,
624 module: Module,
625 module_hash: ModuleHash,
626 runtime: Arc<dyn Runtime + Send + Sync>,
627 ) -> Result<(), Error> {
628 let program_name = wasm_path.display().to_string();
629
630 let runner = self.build_wasi_runner(&runtime, wasmer_wasix::is_wasix_module(&module))?;
631 runner.run_wasm(
632 RuntimeOrEngine::Runtime(runtime),
633 &program_name,
634 module,
635 module_hash,
636 )
637 }
638
639 #[allow(unused_variables)]
640 fn maybe_save_coredump(&self, e: &Error) {
641 #[cfg(feature = "coredump")]
642 if let Some(coredump) = &self.coredump_on_trap
643 && let Err(e) = generate_coredump(e, self.input.to_string(), coredump)
644 {
645 tracing::warn!(
646 error = &*e as &dyn std::error::Error,
647 coredump_path=%coredump.display(),
648 "Unable to generate a coredump",
649 );
650 }
651 }
652}
653
654fn invoke_function(
655 instance: &Instance,
656 store: &mut Store,
657 func: &Function,
658 args: &[String],
659) -> anyhow::Result<Result<Box<[Value]>, RuntimeError>> {
660 let func_ty = func.ty(store);
661 let required_arguments = func_ty.params().len();
662 let provided_arguments = args.len();
663
664 anyhow::ensure!(
665 required_arguments == provided_arguments,
666 "Function expected {required_arguments} arguments, but received {provided_arguments}"
667 );
668
669 let invoke_args = args
670 .iter()
671 .zip(func_ty.params().iter())
672 .map(|(arg, param_type)| {
673 parse_value(arg, *param_type)
674 .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
675 })
676 .collect::<Result<Vec<_>, _>>()?;
677
678 Ok(func.call(store, &invoke_args))
679}
680
681fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
682 let value = match ty {
683 Type::I32 => Value::I32(s.parse()?),
684 Type::I64 => Value::I64(s.parse()?),
685 Type::F32 => Value::F32(s.parse()?),
686 Type::F64 => Value::F64(s.parse()?),
687 Type::V128 => Value::V128(s.parse()?),
688 _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
689 };
690 Ok(value)
691}
692
693#[cfg(feature = "coredump")]
694fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
695 let err: &wasmer::RuntimeError = match err.downcast_ref() {
696 Some(e) => e,
697 None => {
698 log::warn!("no runtime error found to generate coredump with");
699 return Ok(());
700 }
701 };
702
703 let mut coredump_builder =
704 wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
705
706 let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
707
708 for frame in err.trace() {
709 let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
710 .codeoffset(frame.func_offset() as u32)
711 .funcidx(frame.func_index())
712 .build();
713 thread_builder.add_frame(coredump_frame);
714 }
715
716 coredump_builder.add_thread(thread_builder.build());
717
718 let coredump = coredump_builder
719 .serialize()
720 .map_err(Error::msg)
721 .context("Coredump serializing failed")?;
722
723 std::fs::write(coredump_path, &coredump).with_context(|| {
724 format!(
725 "Unable to save the coredump to \"{}\"",
726 coredump_path.display()
727 )
728 })?;
729
730 Ok(())
731}
732
733#[derive(Debug, Clone, Parser)]
734pub(crate) struct WcgiOptions {
735 #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
737 pub(crate) addr: SocketAddr,
738}
739
740impl Default for WcgiOptions {
741 fn default() -> Self {
742 Self {
743 addr: ([127, 0, 0, 1], 8000).into(),
744 }
745 }
746}
747
748#[derive(Debug)]
749struct Callbacks {
750 stderr: Mutex<LineWriter<std::io::Stderr>>,
751 addr: SocketAddr,
752}
753
754impl Callbacks {
755 fn new(addr: SocketAddr) -> Self {
756 Callbacks {
757 stderr: Mutex::new(LineWriter::new(std::io::stderr())),
758 addr,
759 }
760 }
761}
762
763impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
764 fn started(&self, _abort: AbortHandle) {
765 println!("WCGI Server running at http://{}/", self.addr);
766 }
767
768 fn on_stderr(&self, raw_message: &[u8]) {
769 if let Ok(mut stderr) = self.stderr.lock() {
770 let _ = stderr.write_all(raw_message);
775 }
776 }
777}
778
779fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
782 let exit_code = match result {
783 Ok(_) => 0,
784 Err(error) => {
785 match error.chain().find_map(get_exit_code) {
786 Some(exit_code) => exit_code.raw(),
787 None => {
788 eprintln!("{:?}", PrettyError::new(error));
789 1
791 }
792 }
793 }
794 };
795
796 std::io::stdout().flush().ok();
797 std::io::stderr().flush().ok();
798
799 std::process::exit(exit_code);
800}
801
802fn get_exit_code(
803 error: &(dyn std::error::Error + 'static),
804) -> Option<wasmer_wasix::types::wasi::ExitCode> {
805 if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
806 return Some(*exit_code);
807 }
808 if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
809 return error.as_exit_code();
810 }
811
812 None
813}