1#![allow(missing_docs, unused)]
2
3mod capabilities;
4mod wasi;
5
6use std::{
7 collections::{BTreeMap, hash_map::DefaultHasher},
8 fmt::{Binary, Display},
9 fs::File,
10 hash::{BuildHasherDefault, Hash, Hasher},
11 io::{ErrorKind, LineWriter, Read, Write},
12 net::SocketAddr,
13 path::{Path, PathBuf},
14 str::FromStr,
15 sync::{Arc, Mutex},
16 time::{Duration, SystemTime, UNIX_EPOCH},
17};
18
19use anyhow::{Context, Error, anyhow, bail};
20use clap::{Parser, ValueEnum};
21use futures::future::BoxFuture;
22use indicatif::{MultiProgress, ProgressBar};
23use is_terminal::IsTerminal as _;
24use once_cell::sync::Lazy;
25use tempfile::NamedTempFile;
26use url::Url;
27#[cfg(feature = "sys")]
28use wasmer::sys::NativeEngineExt;
29use wasmer::{
30 AsStoreMut, DeserializeError, Engine, Function, Imports, Instance, Module, Store, Type,
31 TypedFunction, Value,
32};
33
34use wasmer_types::{Features, target::Target};
35
36#[cfg(feature = "compiler")]
37use wasmer_compiler::ArtifactBuild;
38use wasmer_config::package::PackageSource as PackageSpecifier;
39use wasmer_package::utils::from_disk;
40use wasmer_types::ModuleHash;
41
42#[cfg(feature = "journal")]
43use wasmer_wasix::journal::{LogFileJournal, SnapshotTrigger};
44use wasmer_wasix::{
45 Runtime, SpawnError, WasiError,
46 bin_factory::{BinaryPackage, BinaryPackageCommand},
47 journal::CompactingLogFileJournal,
48 runners::{
49 MappedCommand, MappedDirectory, Runner,
50 dcgi::{DcgiInstanceFactory, DcgiRunner},
51 dproxy::DProxyRunner,
52 wasi::{RuntimeOrEngine, WasiRunner},
53 wcgi::{self, AbortHandle, NoOpWcgiCallbacks, WcgiRunner},
54 },
55 runtime::{
56 module_cache::{CacheError, HashedModuleData},
57 package_loader::PackageLoader,
58 resolver::QueryError,
59 task_manager::VirtualTaskManagerExt,
60 },
61};
62use webc::Container;
63use webc::metadata::Manifest;
64
65use crate::{
66 backend::RuntimeOptions, commands::run::wasi::Wasi, common::HashAlgorithm, config::WasmerEnv,
67 error::PrettyError, logging::Output,
68};
69
70const TICK: Duration = Duration::from_millis(250);
71
72#[derive(Debug, Parser)]
74pub struct Run {
75 #[clap(flatten)]
76 env: WasmerEnv,
77 #[clap(flatten)]
78 rt: RuntimeOptions,
79 #[clap(flatten)]
80 wasi: crate::commands::run::Wasi,
81 #[clap(flatten)]
82 wcgi: WcgiOptions,
83 #[clap(long = "stack-size")]
85 stack_size: Option<usize>,
86 #[clap(short, long, aliases = &["command", "command-name"])]
88 entrypoint: Option<String>,
89 #[clap(short, long)]
91 invoke: Option<String>,
92 #[clap(name = "COREDUMP_PATH", long)]
94 coredump_on_trap: Option<PathBuf>,
95 #[clap(value_parser = PackageSource::infer)]
97 input: PackageSource,
98 args: Vec<String>,
100 #[clap(long, value_enum)]
102 hash_algorithm: Option<HashAlgorithm>,
103}
104
105impl Run {
106 pub fn execute(self, output: Output) -> ! {
107 let result = self.execute_inner(output);
108 exit_with_wasi_exit_code(result);
109 }
110
111 #[tracing::instrument(level = "debug", name = "wasmer_run", skip_all)]
112 fn execute_inner(mut self, output: Output) -> Result<(), Error> {
113 let pb = ProgressBar::new_spinner();
114 pb.set_draw_target(output.draw_target());
115 pb.enable_steady_tick(TICK);
116
117 pb.set_message("Initializing the WebAssembly VM");
118
119 let runtime = tokio::runtime::Builder::new_multi_thread()
120 .enable_all()
121 .build()?;
122 let handle = runtime.handle().clone();
123
124 let webc_version_var = std::env::var("WASMER_WEBC_VERSION");
127 let preferred_webc_version = match webc_version_var.as_deref() {
128 Ok("2") => webc::Version::V2,
129 Ok("3") | Err(_) => webc::Version::V3,
130 Ok(other) => {
131 bail!("unknown webc version: '{other}'");
132 }
133 };
134
135 let _guard = handle.enter();
136
137 let mut wasm_bytes: Option<Vec<u8>> = None;
139
140 tracing::info!("Input source: {:?}", self.input);
142 if let PackageSource::File(path) = &self.input {
143 tracing::info!("Input file path: {}", path.display());
144
145 if path.exists() {
147 tracing::info!("Found file: {}", path.display());
148 match std::fs::read(path) {
149 Ok(bytes) => {
150 tracing::info!("Read {} bytes from file", bytes.len());
151
152 let magic = [0x00, 0x61, 0x73, 0x6D]; if bytes.len() >= 4 && bytes[0..4] == magic {
155 tracing::info!(
157 "Valid WebAssembly module detected, magic header verified"
158 );
159 wasm_bytes = Some(bytes);
160 } else {
161 tracing::info!(
162 "File does not have valid WebAssembly magic number, will try to run it anyway"
163 );
164 wasm_bytes = Some(bytes);
166 }
167 }
168 Err(e) => {
169 tracing::info!("Failed to read file for feature detection: {}", e);
170 }
171 }
172 } else {
173 tracing::info!("File does not exist: {}", path.display());
174 }
175 } else {
176 tracing::info!("Input is not a file, skipping WebAssembly feature detection");
177 }
178
179 let mut engine = match &wasm_bytes {
181 Some(wasm_bytes) => {
182 tracing::info!("Attempting to detect WebAssembly features from binary");
183
184 self.rt
185 .get_engine_for_module(wasm_bytes, &Target::default())?
186 }
187 None => {
188 if let PackageSource::Package(pkg_source) = &self.input {
190 tracing::info!("Checking package for WebAssembly features: {}", pkg_source);
191 self.rt.get_engine(&Target::default())?
192 } else {
193 tracing::info!("No feature detection possible, using default engine");
194 self.rt.get_engine(&Target::default())?
195 }
196 }
197 };
198
199 let engine_kind = engine.deterministic_id();
200 tracing::info!("Executing on backend {engine_kind:?}");
201
202 #[cfg(feature = "sys")]
203 if engine.is_sys() {
204 if self.stack_size.is_some() {
205 wasmer_vm::set_stack_size(self.stack_size.unwrap());
206 }
207 let hash_algorithm = self.hash_algorithm.unwrap_or_default().into();
208 engine.set_hash_algorithm(Some(hash_algorithm));
209 }
210
211 let engine = engine.clone();
212
213 let runtime = self.wasi.prepare_runtime(
214 engine,
215 &self.env,
216 &capabilities::get_capability_cache_path(&self.env, &self.input)?,
217 runtime,
218 preferred_webc_version,
219 )?;
220
221 let monitoring_runtime = Arc::new(MonitoringRuntime::new(runtime, pb.clone()));
224 let runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime.runtime.clone();
225 let monitoring_runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime;
226
227 let target = self.input.resolve_target(&monitoring_runtime, &pb)?;
228
229 if let ExecutableTarget::Package(ref pkg) = target {
230 self.wasi
231 .mapped_dirs
232 .extend(pkg.additional_host_mapped_directories.clone());
233 }
234
235 pb.finish_and_clear();
236
237 let tty = runtime.tty().map(|tty| tty.tty_get());
239
240 let result = {
241 match target {
242 ExecutableTarget::WebAssembly {
243 module,
244 module_hash,
245 path,
246 } => self.execute_wasm(&path, module, module_hash, runtime.clone()),
247 ExecutableTarget::Package(pkg) => {
248 if let Some(cmd) = pkg.get_entrypoint_command() {
250 if let Some(features) = cmd.wasm_features() {
251 let backends = self.rt.get_available_backends()?;
253 let available_engines = backends
254 .iter()
255 .map(|b| b.to_string())
256 .collect::<Vec<_>>()
257 .join(", ");
258
259 let filtered_backends = RuntimeOptions::filter_backends_by_features(
260 backends.clone(),
261 &features,
262 &Target::default(),
263 );
264
265 if !filtered_backends.is_empty() {
266 let engine_id = filtered_backends[0].to_string();
267
268 if let Ok(new_engine) = filtered_backends[0].get_engine(
270 &Target::default(),
271 &features,
272 &self.rt,
273 ) {
274 tracing::info!(
275 "The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
276 cmd.name(),
277 features,
278 available_engines,
279 engine_id
280 );
281 let capability_cache_path =
283 capabilities::get_capability_cache_path(
284 &self.env,
285 &self.input,
286 )?;
287 let new_runtime = self.wasi.prepare_runtime(
288 new_engine,
289 &self.env,
290 &capability_cache_path,
291 tokio::runtime::Builder::new_multi_thread()
292 .enable_all()
293 .build()?,
294 preferred_webc_version,
295 )?;
296
297 let new_runtime =
298 Arc::new(MonitoringRuntime::new(new_runtime, pb.clone()));
299 return self.execute_webc(&pkg, new_runtime);
300 }
301 }
302 }
303 }
304 self.execute_webc(&pkg, runtime.clone())
305 }
306 }
307 };
308
309 if let Some(state) = tty {
311 if let Some(tty) = runtime.tty() {
312 tty.tty_set(state);
313 }
314 }
315
316 if let Err(e) = &result {
317 self.maybe_save_coredump(e);
318 }
319
320 result
321 }
322
323 #[tracing::instrument(skip_all)]
324 fn execute_wasm(
325 &self,
326 path: &Path,
327 module: Module,
328 module_hash: ModuleHash,
329 runtime: Arc<dyn Runtime + Send + Sync>,
330 ) -> Result<(), Error> {
331 if wasmer_wasix::is_wasi_module(&module) || wasmer_wasix::is_wasix_module(&module) {
332 self.execute_wasi_module(path, module, module_hash, runtime)
333 } else {
334 self.execute_pure_wasm_module(&module)
335 }
336 }
337
338 #[tracing::instrument(skip_all)]
339 fn execute_webc(
340 &self,
341 pkg: &BinaryPackage,
342 runtime: Arc<dyn Runtime + Send + Sync>,
343 ) -> Result<(), Error> {
344 let id = match self.entrypoint.as_deref() {
345 Some(cmd) => cmd,
346 None => pkg.infer_entrypoint()?,
347 };
348 let cmd = pkg
349 .get_command(id)
350 .with_context(|| format!("Unable to get metadata for the \"{id}\" command"))?;
351
352 let uses = self.load_injected_packages(&runtime)?;
353
354 if DcgiRunner::can_run_command(cmd.metadata())? {
355 self.run_dcgi(id, pkg, uses, runtime)
356 } else if DProxyRunner::can_run_command(cmd.metadata())? {
357 self.run_dproxy(id, pkg, runtime)
358 } else if WcgiRunner::can_run_command(cmd.metadata())? {
359 self.run_wcgi(id, pkg, uses, runtime)
360 } else if WasiRunner::can_run_command(cmd.metadata())? {
361 self.run_wasi(id, pkg, uses, runtime)
362 } else {
363 bail!(
364 "Unable to find a runner that supports \"{}\"",
365 cmd.metadata().runner
366 );
367 }
368 }
369
370 #[tracing::instrument(level = "debug", skip_all)]
371 fn load_injected_packages(
372 &self,
373 runtime: &Arc<dyn Runtime + Send + Sync>,
374 ) -> Result<Vec<BinaryPackage>, Error> {
375 let mut dependencies = Vec::new();
376
377 for name in &self.wasi.uses {
378 let specifier = PackageSpecifier::from_str(name)
379 .with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
380 let pkg = {
381 let specifier = specifier.clone();
382 let inner_runtime = runtime.clone();
383 runtime
384 .task_manager()
385 .spawn_and_block_on(async move {
386 BinaryPackage::from_registry(&specifier, inner_runtime.as_ref()).await
387 })
388 .with_context(|| format!("Unable to load \"{name}\""))??
389 };
390 dependencies.push(pkg);
391 }
392
393 Ok(dependencies)
394 }
395
396 fn run_wasi(
397 &self,
398 command_name: &str,
399 pkg: &BinaryPackage,
400 uses: Vec<BinaryPackage>,
401 runtime: Arc<dyn Runtime + Send + Sync>,
402 ) -> Result<(), Error> {
403 let mut runner = self.build_wasi_runner(&runtime)?;
404 Runner::run_command(&mut runner, command_name, pkg, runtime)
405 }
406
407 fn run_wcgi(
408 &self,
409 command_name: &str,
410 pkg: &BinaryPackage,
411 uses: Vec<BinaryPackage>,
412 runtime: Arc<dyn Runtime + Send + Sync>,
413 ) -> Result<(), Error> {
414 let mut runner = wasmer_wasix::runners::wcgi::WcgiRunner::new(NoOpWcgiCallbacks);
415 self.config_wcgi(runner.config(), uses)?;
416 runner.run_command(command_name, pkg, runtime)
417 }
418
419 fn config_wcgi(
420 &self,
421 config: &mut wcgi::Config,
422 uses: Vec<BinaryPackage>,
423 ) -> Result<(), Error> {
424 config
425 .args(self.args.clone())
426 .addr(self.wcgi.addr)
427 .envs(self.wasi.env_vars.clone())
428 .map_directories(self.wasi.mapped_dirs.clone())
429 .callbacks(Callbacks::new(self.wcgi.addr))
430 .inject_packages(uses);
431 *config.capabilities() = self.wasi.capabilities();
432 if self.wasi.forward_host_env {
433 config.forward_host_env();
434 }
435
436 #[cfg(feature = "journal")]
437 {
438 for trigger in self.wasi.snapshot_on.iter().cloned() {
439 config.add_snapshot_trigger(trigger);
440 }
441 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
442 config.add_default_snapshot_triggers();
443 }
444 if let Some(period) = self.wasi.snapshot_interval {
445 if self.wasi.writable_journals.is_empty() {
446 return Err(anyhow::format_err!(
447 "If you specify a snapshot interval then you must also specify a writable journal file"
448 ));
449 }
450 config.with_snapshot_interval(Duration::from_millis(period));
451 }
452 if self.wasi.stop_after_snapshot {
453 config.with_stop_running_after_snapshot(true);
454 }
455 let (r, w) = self.wasi.build_journals()?;
456 for journal in r {
457 config.add_read_only_journal(journal);
458 }
459 for journal in w {
460 config.add_writable_journal(journal);
461 }
462 }
463
464 Ok(())
465 }
466
467 fn run_dcgi(
468 &self,
469 command_name: &str,
470 pkg: &BinaryPackage,
471 uses: Vec<BinaryPackage>,
472 runtime: Arc<dyn Runtime + Send + Sync>,
473 ) -> Result<(), Error> {
474 let factory = DcgiInstanceFactory::new();
475 let mut runner = wasmer_wasix::runners::dcgi::DcgiRunner::new(factory);
476 self.config_wcgi(runner.config().inner(), uses);
477 runner.run_command(command_name, pkg, runtime)
478 }
479
480 fn run_dproxy(
481 &self,
482 command_name: &str,
483 pkg: &BinaryPackage,
484 runtime: Arc<dyn Runtime + Send + Sync>,
485 ) -> Result<(), Error> {
486 let mut inner = self.build_wasi_runner(&runtime)?;
487 let mut runner = wasmer_wasix::runners::dproxy::DProxyRunner::new(inner, pkg);
488 runner.run_command(command_name, pkg, runtime)
489 }
490
491 #[tracing::instrument(skip_all)]
492 fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
493 let mut store = self.rt.get_store()?;
496 let imports = Imports::default();
497 let instance = Instance::new(&mut store, module, &imports)
498 .context("Unable to instantiate the WebAssembly module")?;
499
500 let entry_function = match &self.invoke {
501 Some(entry) => {
502 instance.exports
503 .get_function(entry)
504 .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
505 },
506 None => {
507 instance.exports.get_function("_start")
508 .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
509 }
510 };
511
512 let return_values = invoke_function(&instance, &mut store, entry_function, &self.args)?;
513
514 println!(
515 "{}",
516 return_values
517 .iter()
518 .map(|val| val.to_string())
519 .collect::<Vec<String>>()
520 .join(" ")
521 );
522
523 Ok(())
524 }
525
526 fn build_wasi_runner(
527 &self,
528 runtime: &Arc<dyn Runtime + Send + Sync>,
529 ) -> Result<WasiRunner, anyhow::Error> {
530 let packages = self.load_injected_packages(runtime)?;
531
532 let mut runner = WasiRunner::new();
533
534 let (is_home_mapped, mapped_diretories) = self.wasi.build_mapped_directories()?;
535
536 runner
537 .with_args(&self.args)
538 .with_injected_packages(packages)
539 .with_envs(self.wasi.env_vars.clone())
540 .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
541 .with_mapped_directories(mapped_diretories)
542 .with_home_mapped(is_home_mapped)
543 .with_forward_host_env(self.wasi.forward_host_env)
544 .with_capabilities(self.wasi.capabilities());
545
546 if let Some(cwd) = self.wasi.cwd.as_ref() {
547 if !cwd.starts_with("/") {
548 bail!("The argument to --cwd must be an absolute path");
549 }
550 runner.with_current_dir(cwd.clone());
551 }
552
553 if let Some(ref entry_function) = self.invoke {
554 runner.with_entry_function(entry_function);
555 }
556
557 #[cfg(feature = "journal")]
558 {
559 for trigger in self.wasi.snapshot_on.iter().cloned() {
560 runner.with_snapshot_trigger(trigger);
561 }
562 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
563 runner.with_default_snapshot_triggers();
564 }
565 if let Some(period) = self.wasi.snapshot_interval {
566 if self.wasi.writable_journals.is_empty() {
567 return Err(anyhow::format_err!(
568 "If you specify a snapshot interval then you must also specify a writable journal file"
569 ));
570 }
571 runner.with_snapshot_interval(Duration::from_millis(period));
572 }
573 if self.wasi.stop_after_snapshot {
574 runner.with_stop_running_after_snapshot(true);
575 }
576 let (r, w) = self.wasi.build_journals()?;
577 for journal in r {
578 runner.with_read_only_journal(journal);
579 }
580 for journal in w {
581 runner.with_writable_journal(journal);
582 }
583 runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
584 }
585
586 Ok(runner)
587 }
588
589 #[tracing::instrument(skip_all)]
590 fn execute_wasi_module(
591 &self,
592 wasm_path: &Path,
593 module: Module,
594 module_hash: ModuleHash,
595 runtime: Arc<dyn Runtime + Send + Sync>,
596 ) -> Result<(), Error> {
597 let program_name = wasm_path.display().to_string();
598
599 let runner = self.build_wasi_runner(&runtime)?;
600 runner.run_wasm(
601 RuntimeOrEngine::Runtime(runtime),
602 &program_name,
603 module,
604 module_hash,
605 )
606 }
607
608 #[allow(unused_variables)]
609 fn maybe_save_coredump(&self, e: &Error) {
610 #[cfg(feature = "coredump")]
611 if let Some(coredump) = &self.coredump_on_trap {
612 if let Err(e) = generate_coredump(e, self.input.to_string(), coredump) {
613 tracing::warn!(
614 error = &*e as &dyn std::error::Error,
615 coredump_path=%coredump.display(),
616 "Unable to generate a coredump",
617 );
618 }
619 }
620 }
621}
622
623fn invoke_function(
624 instance: &Instance,
625 store: &mut Store,
626 func: &Function,
627 args: &[String],
628) -> Result<Box<[Value]>, Error> {
629 let func_ty = func.ty(store);
630 let required_arguments = func_ty.params().len();
631 let provided_arguments = args.len();
632
633 anyhow::ensure!(
634 required_arguments == provided_arguments,
635 "Function expected {required_arguments} arguments, but received {provided_arguments}"
636 );
637
638 let invoke_args = args
639 .iter()
640 .zip(func_ty.params().iter())
641 .map(|(arg, param_type)| {
642 parse_value(arg, *param_type)
643 .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
644 })
645 .collect::<Result<Vec<_>, _>>()?;
646
647 let return_values = func.call(store, &invoke_args)?;
648
649 Ok(return_values)
650}
651
652fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
653 let value = match ty {
654 Type::I32 => Value::I32(s.parse()?),
655 Type::I64 => Value::I64(s.parse()?),
656 Type::F32 => Value::F32(s.parse()?),
657 Type::F64 => Value::F64(s.parse()?),
658 Type::V128 => Value::V128(s.parse()?),
659 _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
660 };
661 Ok(value)
662}
663
664#[derive(Debug, Clone, PartialEq)]
666enum PackageSource {
667 File(PathBuf),
669 Dir(PathBuf),
671 Package(PackageSpecifier),
673}
674
675impl PackageSource {
676 fn infer(s: &str) -> Result<PackageSource, Error> {
677 let path = Path::new(s);
678 if path.is_file() {
679 return Ok(PackageSource::File(path.to_path_buf()));
680 } else if path.is_dir() {
681 return Ok(PackageSource::Dir(path.to_path_buf()));
682 }
683
684 if let Ok(pkg) = PackageSpecifier::from_str(s) {
685 return Ok(PackageSource::Package(pkg));
686 }
687
688 Err(anyhow::anyhow!(
689 "Unable to resolve \"{s}\" as a URL, package name, or file on disk"
690 ))
691 }
692
693 #[tracing::instrument(level = "debug", skip_all)]
698 fn resolve_target(
699 &self,
700 rt: &Arc<dyn Runtime + Send + Sync>,
701 pb: &ProgressBar,
702 ) -> Result<ExecutableTarget, Error> {
703 match self {
704 PackageSource::File(path) => ExecutableTarget::from_file(path, rt, pb),
705 PackageSource::Dir(d) => ExecutableTarget::from_dir(d, rt, pb),
706 PackageSource::Package(pkg) => {
707 pb.set_message("Loading from the registry");
708 let inner_pck = pkg.clone();
709 let inner_rt = rt.clone();
710 let pkg = rt.task_manager().spawn_and_block_on(async move {
711 BinaryPackage::from_registry(&inner_pck, inner_rt.as_ref()).await
712 })??;
713 Ok(ExecutableTarget::Package(Box::new(pkg)))
714 }
715 }
716 }
717}
718
719impl Display for PackageSource {
720 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721 match self {
722 PackageSource::File(path) | PackageSource::Dir(path) => write!(f, "{}", path.display()),
723 PackageSource::Package(p) => write!(f, "{p}"),
724 }
725 }
726}
727
728#[derive(Debug, Clone)]
731enum TargetOnDisk {
732 WebAssemblyBinary,
733 Wat,
734 LocalWebc,
735 Artifact,
736}
737
738impl TargetOnDisk {
739 fn from_file(path: &Path) -> Result<TargetOnDisk, Error> {
740 let mut buffer = [0_u8; 512];
743
744 let mut f = File::open(path)
745 .with_context(|| format!("Unable to open \"{}\" for reading", path.display()))?;
746 let bytes_read = f.read(&mut buffer)?;
747
748 let leading_bytes = &buffer[..bytes_read];
749
750 if wasmer::is_wasm(leading_bytes) {
751 return Ok(TargetOnDisk::WebAssemblyBinary);
752 }
753
754 if webc::detect(leading_bytes).is_ok() {
755 return Ok(TargetOnDisk::LocalWebc);
756 }
757
758 #[cfg(feature = "compiler")]
759 if ArtifactBuild::is_deserializable(leading_bytes) {
760 return Ok(TargetOnDisk::Artifact);
761 }
762
763 match path.extension().and_then(|s| s.to_str()) {
767 Some("wat") => Ok(TargetOnDisk::Wat),
768 Some("wasm") => Ok(TargetOnDisk::WebAssemblyBinary),
769 Some("webc") => Ok(TargetOnDisk::LocalWebc),
770 Some("wasmu") => Ok(TargetOnDisk::WebAssemblyBinary),
771 _ => bail!("Unable to determine how to execute \"{}\"", path.display()),
772 }
773 }
774}
775
776#[derive(Debug, Clone)]
777enum ExecutableTarget {
778 WebAssembly {
779 module: Module,
780 module_hash: ModuleHash,
781 path: PathBuf,
782 },
783 Package(Box<BinaryPackage>),
784}
785
786impl ExecutableTarget {
787 #[tracing::instrument(level = "debug", skip_all)]
790 fn from_dir(
791 dir: &Path,
792 runtime: &Arc<dyn Runtime + Send + Sync>,
793 pb: &ProgressBar,
794 ) -> Result<Self, Error> {
795 pb.set_message(format!("Loading \"{}\" into memory", dir.display()));
796 pb.set_message("Resolving dependencies");
797 let inner_runtime = runtime.clone();
798 let pkg = runtime.task_manager().spawn_and_block_on({
799 let path = dir.to_path_buf();
800
801 async move { BinaryPackage::from_dir(&path, inner_runtime.as_ref()).await }
802 })??;
803
804 Ok(ExecutableTarget::Package(Box::new(pkg)))
805 }
806
807 #[tracing::instrument(level = "debug", skip_all)]
809 fn from_file(
810 path: &Path,
811 runtime: &Arc<dyn Runtime + Send + Sync>,
812 pb: &ProgressBar,
813 ) -> Result<Self, Error> {
814 pb.set_message(format!("Loading from \"{}\"", path.display()));
815
816 match TargetOnDisk::from_file(path)? {
817 TargetOnDisk::WebAssemblyBinary | TargetOnDisk::Wat => {
818 let wasm = std::fs::read(path)?;
819 let module_data = HashedModuleData::new(wasm);
820 let module_hash = *module_data.hash();
821
822 pb.set_message("Compiling to WebAssembly");
823 let module = runtime
824 .load_hashed_module_sync(module_data, None)
825 .with_context(|| format!("Unable to compile \"{}\"", path.display()))?;
826
827 Ok(ExecutableTarget::WebAssembly {
828 module,
829 module_hash,
830 path: path.to_path_buf(),
831 })
832 }
833 TargetOnDisk::Artifact => {
834 let engine = runtime.engine();
835 pb.set_message("Deserializing pre-compiled WebAssembly module");
836 let module = unsafe { Module::deserialize_from_file(&engine, path)? };
837
838 let module_hash = module.info().hash.ok_or_else(|| {
839 anyhow::Error::msg("module hash is not present in the artifact")
840 })?;
841
842 Ok(ExecutableTarget::WebAssembly {
843 module,
844 module_hash,
845 path: path.to_path_buf(),
846 })
847 }
848 TargetOnDisk::LocalWebc => {
849 let container = from_disk(path)?;
850 pb.set_message("Resolving dependencies");
851
852 let inner_runtime = runtime.clone();
853 let pkg = runtime.task_manager().spawn_and_block_on(async move {
854 BinaryPackage::from_webc(&container, inner_runtime.as_ref()).await
855 })??;
856 Ok(ExecutableTarget::Package(Box::new(pkg)))
857 }
858 }
859 }
860}
861
862#[cfg(feature = "coredump")]
863fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
864 let err: &wasmer::RuntimeError = match err.downcast_ref() {
865 Some(e) => e,
866 None => {
867 log::warn!("no runtime error found to generate coredump with");
868 return Ok(());
869 }
870 };
871
872 let mut coredump_builder =
873 wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
874
875 let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
876
877 for frame in err.trace() {
878 let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
879 .codeoffset(frame.func_offset() as u32)
880 .funcidx(frame.func_index())
881 .build();
882 thread_builder.add_frame(coredump_frame);
883 }
884
885 coredump_builder.add_thread(thread_builder.build());
886
887 let coredump = coredump_builder
888 .serialize()
889 .map_err(Error::msg)
890 .context("Coredump serializing failed")?;
891
892 std::fs::write(coredump_path, &coredump).with_context(|| {
893 format!(
894 "Unable to save the coredump to \"{}\"",
895 coredump_path.display()
896 )
897 })?;
898
899 Ok(())
900}
901
902#[derive(Debug, Clone, Parser)]
903pub(crate) struct WcgiOptions {
904 #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
906 pub(crate) addr: SocketAddr,
907}
908
909impl Default for WcgiOptions {
910 fn default() -> Self {
911 Self {
912 addr: ([127, 0, 0, 1], 8000).into(),
913 }
914 }
915}
916
917#[derive(Debug)]
918struct Callbacks {
919 stderr: Mutex<LineWriter<std::io::Stderr>>,
920 addr: SocketAddr,
921}
922
923impl Callbacks {
924 fn new(addr: SocketAddr) -> Self {
925 Callbacks {
926 stderr: Mutex::new(LineWriter::new(std::io::stderr())),
927 addr,
928 }
929 }
930}
931
932impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
933 fn started(&self, _abort: AbortHandle) {
934 println!("WCGI Server running at http://{}/", self.addr);
935 }
936
937 fn on_stderr(&self, raw_message: &[u8]) {
938 if let Ok(mut stderr) = self.stderr.lock() {
939 let _ = stderr.write_all(raw_message);
944 }
945 }
946}
947
948fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
951 let exit_code = match result {
952 Ok(_) => 0,
953 Err(error) => {
954 match error.chain().find_map(get_exit_code) {
955 Some(exit_code) => exit_code.raw(),
956 None => {
957 eprintln!("{:?}", PrettyError::new(error));
958 1
960 }
961 }
962 }
963 };
964
965 std::io::stdout().flush().ok();
966 std::io::stderr().flush().ok();
967
968 std::process::exit(exit_code);
969}
970
971fn get_exit_code(
972 error: &(dyn std::error::Error + 'static),
973) -> Option<wasmer_wasix::types::wasi::ExitCode> {
974 if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
975 return Some(*exit_code);
976 }
977 if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
978 return error.as_exit_code();
979 }
980
981 None
982}
983
984#[derive(Debug)]
985struct MonitoringRuntime<R> {
986 runtime: Arc<R>,
987 progress: ProgressBar,
988}
989
990impl<R> MonitoringRuntime<R> {
991 fn new(runtime: R, progress: ProgressBar) -> Self {
992 MonitoringRuntime {
993 runtime: Arc::new(runtime),
994 progress,
995 }
996 }
997}
998
999impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
1000 fn networking(&self) -> &virtual_net::DynVirtualNetworking {
1001 self.runtime.networking()
1002 }
1003
1004 fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
1005 self.runtime.task_manager()
1006 }
1007
1008 fn package_loader(
1009 &self,
1010 ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
1011 let inner = self.runtime.package_loader();
1012 Arc::new(MonitoringPackageLoader {
1013 inner,
1014 progress: self.progress.clone(),
1015 })
1016 }
1017
1018 fn module_cache(
1019 &self,
1020 ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
1021 self.runtime.module_cache()
1022 }
1023
1024 fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
1025 let inner = self.runtime.source();
1026 Arc::new(MonitoringSource {
1027 inner,
1028 progress: self.progress.clone(),
1029 })
1030 }
1031
1032 fn engine(&self) -> wasmer::Engine {
1033 self.runtime.engine()
1034 }
1035
1036 fn new_store(&self) -> wasmer::Store {
1037 self.runtime.new_store()
1038 }
1039
1040 fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
1041 self.runtime.http_client()
1042 }
1043
1044 fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
1045 self.runtime.tty()
1046 }
1047
1048 #[cfg(feature = "journal")]
1049 fn read_only_journals<'a>(
1050 &'a self,
1051 ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
1052 self.runtime.read_only_journals()
1053 }
1054
1055 #[cfg(feature = "journal")]
1056 fn writable_journals<'a>(
1057 &'a self,
1058 ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
1059 self.runtime.writable_journals()
1060 }
1061
1062 #[cfg(feature = "journal")]
1063 fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
1064 self.runtime.active_journal()
1065 }
1066
1067 fn load_hashed_module(
1068 &self,
1069 module: HashedModuleData,
1070 engine: Option<&Engine>,
1071 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1072 let hash = *module.hash();
1073 let fut = self.runtime.load_hashed_module(module, engine);
1074 Box::pin(compile_with_progress(fut, hash, None))
1075 }
1076
1077 fn load_hashed_module_sync(
1078 &self,
1079 wasm: HashedModuleData,
1080 engine: Option<&Engine>,
1081 ) -> Result<Module, wasmer_wasix::SpawnError> {
1082 let hash = *wasm.hash();
1083 compile_with_progress_sync(
1084 || self.runtime.load_hashed_module_sync(wasm, engine),
1085 &hash,
1086 None,
1087 )
1088 }
1089
1090 fn load_command_module(
1091 &self,
1092 cmd: &BinaryPackageCommand,
1093 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1094 let fut = self.runtime.load_command_module(cmd);
1095
1096 Box::pin(compile_with_progress(
1097 fut,
1098 *cmd.hash(),
1099 Some(cmd.name().to_owned()),
1100 ))
1101 }
1102
1103 fn load_command_module_sync(
1104 &self,
1105 cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand,
1106 ) -> Result<Module, wasmer_wasix::SpawnError> {
1107 compile_with_progress_sync(
1108 || self.runtime.load_command_module_sync(cmd),
1109 cmd.hash(),
1110 Some(cmd.name()),
1111 )
1112 }
1113}
1114
1115async fn compile_with_progress<'a, F, T>(fut: F, hash: ModuleHash, name: Option<String>) -> T
1116where
1117 F: std::future::Future<Output = T> + Send + 'a,
1118 T: Send + 'static,
1119{
1120 let mut pb = new_progressbar_compile(&hash, name.as_deref());
1121 let res = fut.await;
1122 pb.finish_and_clear();
1123 res
1124}
1125
1126fn compile_with_progress_sync<F, T>(f: F, hash: &ModuleHash, name: Option<&str>) -> T
1127where
1128 F: FnOnce() -> T,
1129{
1130 let mut pb = new_progressbar_compile(hash, name);
1131 let res = f();
1132 pb.finish_and_clear();
1133 res
1134}
1135
1136fn new_progressbar_compile(hash: &ModuleHash, name: Option<&str>) -> ProgressBar {
1137 if std::io::stderr().is_terminal() {
1139 let msg = if let Some(name) = name {
1140 format!("Compiling WebAssembly module for command '{name}' ({hash})...")
1141 } else {
1142 format!("Compiling WebAssembly module {hash}...")
1143 };
1144 let pb = ProgressBar::new_spinner().with_message(msg);
1145 pb.enable_steady_tick(Duration::from_millis(100));
1146 pb
1147 } else {
1148 ProgressBar::hidden()
1149 }
1150}
1151
1152#[derive(Debug)]
1153struct MonitoringSource {
1154 inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
1155 progress: ProgressBar,
1156}
1157
1158#[async_trait::async_trait]
1159impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
1160 async fn query(
1161 &self,
1162 package: &PackageSpecifier,
1163 ) -> Result<Vec<wasmer_wasix::runtime::resolver::PackageSummary>, QueryError> {
1164 self.progress.set_message(format!("Looking up {package}"));
1165 self.inner.query(package).await
1166 }
1167}
1168
1169#[derive(Debug)]
1170struct MonitoringPackageLoader {
1171 inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
1172 progress: ProgressBar,
1173}
1174
1175#[async_trait::async_trait]
1176impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
1177 async fn load(
1178 &self,
1179 summary: &wasmer_wasix::runtime::resolver::PackageSummary,
1180 ) -> Result<Container, Error> {
1181 let pkg_id = summary.package_id();
1182 self.progress.set_message(format!("Downloading {pkg_id}"));
1183
1184 self.inner.load(summary).await
1185 }
1186
1187 async fn load_package_tree(
1188 &self,
1189 root: &Container,
1190 resolution: &wasmer_wasix::runtime::resolver::Resolution,
1191 root_is_local_dir: bool,
1192 ) -> Result<BinaryPackage, Error> {
1193 self.inner
1194 .load_package_tree(root, resolution, root_is_local_dir)
1195 .await
1196 }
1197}