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