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 !filtered_backends.is_empty() {
282 let engine_id = filtered_backends[0].to_string();
283
284 if let Ok(new_engine) = filtered_backends[0].get_engine(
286 &Target::default(),
287 &features,
288 &self.rt,
289 ) {
290 tracing::info!(
291 "The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
292 cmd.name(),
293 features,
294 available_engines,
295 engine_id
296 );
297 let capability_cache_path =
299 capabilities::get_capability_cache_path(
300 &self.env,
301 &self.input,
302 )?;
303 let new_runtime = self.wasi.prepare_runtime(
304 new_engine,
305 &self.env,
306 &capability_cache_path,
307 tokio::runtime::Builder::new_multi_thread()
308 .enable_all()
309 .build()?,
310 preferred_webc_version,
311 self.rt.compiler_debug_dir.is_some(),
312 )?;
313
314 let new_runtime = Arc::new(MonitoringRuntime::new(
315 new_runtime,
316 pb.clone(),
317 output.is_quiet_or_no_tty(),
318 ));
319 return self.execute_webc(&pkg, new_runtime);
320 }
321 }
322 }
323 self.execute_webc(&pkg, monitoring_runtime)
324 }
325 }
326 };
327
328 if let Some(state) = tty
330 && let Some(tty) = runtime.tty()
331 {
332 tty.tty_set(state);
333 }
334
335 if let Err(e) = &result {
336 self.maybe_save_coredump(e);
337 }
338
339 result
340 }
341
342 #[tracing::instrument(skip_all)]
343 fn execute_wasm(
344 &self,
345 path: &Path,
346 module: Module,
347 module_hash: ModuleHash,
348 runtime: Arc<dyn Runtime + Send + Sync>,
349 ) -> Result<(), Error> {
350 if wasmer_wasix::is_wasi_module(&module) || wasmer_wasix::is_wasix_module(&module) {
351 self.execute_wasi_module(path, module, module_hash, runtime)
352 } else {
353 self.execute_pure_wasm_module(&module)
354 }
355 }
356
357 #[tracing::instrument(skip_all)]
358 fn execute_webc(
359 &self,
360 pkg: &BinaryPackage,
361 runtime: Arc<dyn Runtime + Send + Sync>,
362 ) -> Result<(), Error> {
363 let id = match self.entrypoint.as_deref() {
364 Some(cmd) => cmd,
365 None => pkg.infer_entrypoint()?,
366 };
367 let cmd = pkg
368 .get_command(id)
369 .with_context(|| format!("Unable to get metadata for the \"{id}\" command"))?;
370
371 let uses = self.load_injected_packages(&runtime)?;
372
373 if DcgiRunner::can_run_command(cmd.metadata())? {
374 self.run_dcgi(id, pkg, uses, runtime)
375 } else if DProxyRunner::can_run_command(cmd.metadata())? {
376 self.run_dproxy(id, pkg, runtime)
377 } else if WcgiRunner::can_run_command(cmd.metadata())? {
378 self.run_wcgi(id, pkg, uses, runtime)
379 } else if WasiRunner::can_run_command(cmd.metadata())? {
380 self.run_wasi(id, pkg, uses, runtime)
381 } else {
382 bail!(
383 "Unable to find a runner that supports \"{}\"",
384 cmd.metadata().runner
385 );
386 }
387 }
388
389 #[tracing::instrument(level = "debug", skip_all)]
390 fn load_injected_packages(
391 &self,
392 runtime: &Arc<dyn Runtime + Send + Sync>,
393 ) -> Result<Vec<BinaryPackage>, Error> {
394 let mut dependencies = Vec::new();
395
396 for name in &self.wasi.uses {
397 let specifier = name
398 .parse::<PackageSource>()
399 .with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
400 let pkg = {
401 let specifier = specifier.clone();
402 let inner_runtime = runtime.clone();
403 runtime
404 .task_manager()
405 .spawn_and_block_on(async move {
406 BinaryPackage::from_registry(&specifier, inner_runtime.as_ref()).await
407 })
408 .with_context(|| format!("Unable to load \"{name}\""))??
409 };
410 dependencies.push(pkg);
411 }
412
413 Ok(dependencies)
414 }
415
416 fn run_wasi(
417 &self,
418 command_name: &str,
419 pkg: &BinaryPackage,
420 uses: Vec<BinaryPackage>,
421 runtime: Arc<dyn Runtime + Send + Sync>,
422 ) -> Result<(), Error> {
423 let mut runner = self.build_wasi_runner(&runtime, true)?;
425 Runner::run_command(&mut runner, command_name, pkg, runtime)
426 }
427
428 fn run_wcgi(
429 &self,
430 command_name: &str,
431 pkg: &BinaryPackage,
432 uses: Vec<BinaryPackage>,
433 runtime: Arc<dyn Runtime + Send + Sync>,
434 ) -> Result<(), Error> {
435 let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(NoOpWcgiCallbacks);
436 self.config_wcgi(runner.config(), uses)?;
437 runner.run_command(command_name, pkg, runtime)
438 }
439
440 fn config_wcgi(
441 &self,
442 config: &mut wcgi::Config,
443 uses: Vec<BinaryPackage>,
444 ) -> Result<(), Error> {
445 config
446 .args(self.args.clone())
447 .addr(self.wcgi.addr)
448 .envs(self.wasi.env_vars.clone())
449 .map_directories(self.wasi.all_volumes())
450 .callbacks(Callbacks::new(self.wcgi.addr))
451 .inject_packages(uses);
452 *config.capabilities() = self.wasi.capabilities();
453 if self.wasi.forward_host_env {
454 config.forward_host_env();
455 }
456
457 #[cfg(feature = "journal")]
458 {
459 for trigger in self.wasi.snapshot_on.iter().cloned() {
460 config.add_snapshot_trigger(trigger);
461 }
462 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
463 config.add_default_snapshot_triggers();
464 }
465 if let Some(period) = self.wasi.snapshot_interval {
466 if self.wasi.writable_journals.is_empty() {
467 return Err(anyhow::format_err!(
468 "If you specify a snapshot interval then you must also specify a writable journal file"
469 ));
470 }
471 config.with_snapshot_interval(Duration::from_millis(period));
472 }
473 if self.wasi.stop_after_snapshot {
474 config.with_stop_running_after_snapshot(true);
475 }
476 let (r, w) = self.wasi.build_journals()?;
477 for journal in r {
478 config.add_read_only_journal(journal);
479 }
480 for journal in w {
481 config.add_writable_journal(journal);
482 }
483 }
484
485 Ok(())
486 }
487
488 fn run_dcgi(
489 &self,
490 command_name: &str,
491 pkg: &BinaryPackage,
492 uses: Vec<BinaryPackage>,
493 runtime: Arc<dyn Runtime + Send + Sync>,
494 ) -> Result<(), Error> {
495 let factory = DcgiInstanceFactory::new();
496 let mut runner = wasmer_wasix::runners::dcgi::DcgiRunner::new(factory);
497 self.config_wcgi(runner.config().inner(), uses);
498 runner.run_command(command_name, pkg, runtime)
499 }
500
501 fn run_dproxy(
502 &self,
503 command_name: &str,
504 pkg: &BinaryPackage,
505 runtime: Arc<dyn Runtime + Send + Sync>,
506 ) -> Result<(), Error> {
507 let mut inner = self.build_wasi_runner(&runtime, true)?;
508 let mut runner = wasmer_wasix::runners::dproxy::DProxyRunner::new(inner, pkg);
509 runner.run_command(command_name, pkg, runtime)
510 }
511
512 #[tracing::instrument(skip_all)]
513 fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
514 let mut store = self.rt.get_store()?;
517 let imports = Imports::default();
518 let instance = Instance::new(&mut store, module, &imports)
519 .context("Unable to instantiate the WebAssembly module")?;
520
521 let entry_function = match &self.invoke {
522 Some(entry) => {
523 instance.exports
524 .get_function(entry)
525 .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
526 },
527 None => {
528 instance.exports.get_function("_start")
529 .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
530 }
531 };
532
533 let result = invoke_function(&instance, &mut store, entry_function, &self.args)?;
534
535 match result {
536 Ok(return_values) => {
537 println!(
538 "{}",
539 return_values
540 .iter()
541 .map(|val| val.to_string())
542 .collect::<Vec<String>>()
543 .join(" ")
544 );
545 Ok(())
546 }
547 Err(err) => {
548 bail!("{}", err.display(&mut store));
549 }
550 }
551 }
552
553 fn build_wasi_runner(
554 &self,
555 runtime: &Arc<dyn Runtime + Send + Sync>,
556 is_wasix: bool,
557 ) -> Result<WasiRunner, anyhow::Error> {
558 let packages = self.load_injected_packages(runtime)?;
559
560 let mut runner = WasiRunner::new();
561
562 let (is_home_mapped, mapped_directories) = self.wasi.build_mapped_directories(is_wasix)?;
563
564 runner
565 .with_args(&self.args)
566 .with_injected_packages(packages)
567 .with_envs(self.wasi.env_vars.clone())
568 .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
569 .with_mapped_directories(mapped_directories)
570 .with_home_mapped(is_home_mapped)
571 .with_forward_host_env(self.wasi.forward_host_env)
572 .with_capabilities(self.wasi.capabilities());
573
574 if let Some(cwd) = self.wasi.cwd.as_ref() {
575 if !cwd.starts_with("/") {
576 bail!("The argument to --cwd must be an absolute path");
577 }
578 runner.with_current_dir(cwd.clone());
579 }
580
581 if let Some(ref entry_function) = self.invoke {
582 runner.with_entry_function(entry_function);
583 }
584
585 #[cfg(feature = "journal")]
586 {
587 for trigger in self.wasi.snapshot_on.iter().cloned() {
588 runner.with_snapshot_trigger(trigger);
589 }
590 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
591 runner.with_default_snapshot_triggers();
592 }
593 if let Some(period) = self.wasi.snapshot_interval {
594 if self.wasi.writable_journals.is_empty() {
595 return Err(anyhow::format_err!(
596 "If you specify a snapshot interval then you must also specify a writable journal file"
597 ));
598 }
599 runner.with_snapshot_interval(Duration::from_millis(period));
600 }
601 if self.wasi.stop_after_snapshot {
602 runner.with_stop_running_after_snapshot(true);
603 }
604 let (r, w) = self.wasi.build_journals()?;
605 for journal in r {
606 runner.with_read_only_journal(journal);
607 }
608 for journal in w {
609 runner.with_writable_journal(journal);
610 }
611 runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
612 }
613
614 Ok(runner)
615 }
616
617 #[tracing::instrument(skip_all)]
618 fn execute_wasi_module(
619 &self,
620 wasm_path: &Path,
621 module: Module,
622 module_hash: ModuleHash,
623 runtime: Arc<dyn Runtime + Send + Sync>,
624 ) -> Result<(), Error> {
625 let program_name = wasm_path.display().to_string();
626
627 let runner = self.build_wasi_runner(&runtime, wasmer_wasix::is_wasix_module(&module))?;
628 runner.run_wasm(
629 RuntimeOrEngine::Runtime(runtime),
630 &program_name,
631 module,
632 module_hash,
633 )
634 }
635
636 #[allow(unused_variables)]
637 fn maybe_save_coredump(&self, e: &Error) {
638 #[cfg(feature = "coredump")]
639 if let Some(coredump) = &self.coredump_on_trap
640 && let Err(e) = generate_coredump(e, self.input.to_string(), coredump)
641 {
642 tracing::warn!(
643 error = &*e as &dyn std::error::Error,
644 coredump_path=%coredump.display(),
645 "Unable to generate a coredump",
646 );
647 }
648 }
649
650 fn print_option_warnings(&self) {
651 if !self.wasi.mapped_dirs.is_empty() {
652 eprintln!(
653 "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
654 "warning: ".yellow(),
655 "--mapdir".yellow(),
656 "--volume".green()
657 );
658 }
659 if !self.wasi.pre_opened_directories.is_empty() {
660 eprintln!(
661 "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
662 "warning: ".yellow(),
663 "--dir".yellow(),
664 "--volume".green()
665 );
666 }
667 }
668}
669
670fn invoke_function(
671 instance: &Instance,
672 store: &mut Store,
673 func: &Function,
674 args: &[String],
675) -> anyhow::Result<Result<Box<[Value]>, RuntimeError>> {
676 let func_ty = func.ty(store);
677 let required_arguments = func_ty.params().len();
678 let provided_arguments = args.len();
679
680 anyhow::ensure!(
681 required_arguments == provided_arguments,
682 "Function expected {required_arguments} arguments, but received {provided_arguments}"
683 );
684
685 let invoke_args = args
686 .iter()
687 .zip(func_ty.params().iter())
688 .map(|(arg, param_type)| {
689 parse_value(arg, *param_type)
690 .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
691 })
692 .collect::<Result<Vec<_>, _>>()?;
693
694 Ok(func.call(store, &invoke_args))
695}
696
697fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
698 let value = match ty {
699 Type::I32 => Value::I32(s.parse()?),
700 Type::I64 => Value::I64(s.parse()?),
701 Type::F32 => Value::F32(s.parse()?),
702 Type::F64 => Value::F64(s.parse()?),
703 Type::V128 => Value::V128(s.parse()?),
704 _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
705 };
706 Ok(value)
707}
708
709#[cfg(feature = "coredump")]
710fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
711 let err: &wasmer::RuntimeError = match err.downcast_ref() {
712 Some(e) => e,
713 None => {
714 log::warn!("no runtime error found to generate coredump with");
715 return Ok(());
716 }
717 };
718
719 let mut coredump_builder =
720 wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
721
722 let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
723
724 for frame in err.trace() {
725 let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
726 .codeoffset(frame.func_offset() as u32)
727 .funcidx(frame.func_index())
728 .build();
729 thread_builder.add_frame(coredump_frame);
730 }
731
732 coredump_builder.add_thread(thread_builder.build());
733
734 let coredump = coredump_builder
735 .serialize()
736 .map_err(Error::msg)
737 .context("Coredump serializing failed")?;
738
739 std::fs::write(coredump_path, &coredump).with_context(|| {
740 format!(
741 "Unable to save the coredump to \"{}\"",
742 coredump_path.display()
743 )
744 })?;
745
746 Ok(())
747}
748
749#[derive(Debug, Clone, Parser)]
750pub(crate) struct WcgiOptions {
751 #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
753 pub(crate) addr: SocketAddr,
754}
755
756impl Default for WcgiOptions {
757 fn default() -> Self {
758 Self {
759 addr: ([127, 0, 0, 1], 8000).into(),
760 }
761 }
762}
763
764#[derive(Debug)]
765struct Callbacks {
766 stderr: Mutex<LineWriter<std::io::Stderr>>,
767 addr: SocketAddr,
768}
769
770impl Callbacks {
771 fn new(addr: SocketAddr) -> Self {
772 Callbacks {
773 stderr: Mutex::new(LineWriter::new(std::io::stderr())),
774 addr,
775 }
776 }
777}
778
779impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
780 fn started(&self, _abort: AbortHandle) {
781 println!("WCGI Server running at http://{}/", self.addr);
782 }
783
784 fn on_stderr(&self, raw_message: &[u8]) {
785 if let Ok(mut stderr) = self.stderr.lock() {
786 let _ = stderr.write_all(raw_message);
791 }
792 }
793}
794
795fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
798 let exit_code = match result {
799 Ok(_) => 0,
800 Err(error) => {
801 match error.chain().find_map(get_exit_code) {
802 Some(exit_code) => exit_code.raw(),
803 None => {
804 eprintln!("{:?}", PrettyError::new(error));
805 1
807 }
808 }
809 }
810 };
811
812 std::io::stdout().flush().ok();
813 std::io::stderr().flush().ok();
814
815 std::process::exit(exit_code);
816}
817
818fn get_exit_code(
819 error: &(dyn std::error::Error + 'static),
820) -> Option<wasmer_wasix::types::wasi::ExitCode> {
821 if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
822 return Some(*exit_code);
823 }
824 if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
825 return error.as_exit_code();
826 }
827
828 None
829}