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