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