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, is_tmp_mapped, mapped_diretories) =
535 self.wasi.build_mapped_directories()?;
536
537 runner
538 .with_args(&self.args)
539 .with_injected_packages(packages)
540 .with_envs(self.wasi.env_vars.clone())
541 .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
542 .with_mapped_directories(mapped_diretories)
543 .with_home_mapped(is_home_mapped)
544 .with_tmp_mapped(is_tmp_mapped)
545 .with_forward_host_env(self.wasi.forward_host_env)
546 .with_capabilities(self.wasi.capabilities());
547
548 if let Some(ref entry_function) = self.invoke {
549 runner.with_entry_function(entry_function);
550 }
551
552 #[cfg(feature = "journal")]
553 {
554 for trigger in self.wasi.snapshot_on.iter().cloned() {
555 runner.with_snapshot_trigger(trigger);
556 }
557 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
558 runner.with_default_snapshot_triggers();
559 }
560 if let Some(period) = self.wasi.snapshot_interval {
561 if self.wasi.writable_journals.is_empty() {
562 return Err(anyhow::format_err!(
563 "If you specify a snapshot interval then you must also specify a writable journal file"
564 ));
565 }
566 runner.with_snapshot_interval(Duration::from_millis(period));
567 }
568 if self.wasi.stop_after_snapshot {
569 runner.with_stop_running_after_snapshot(true);
570 }
571 let (r, w) = self.wasi.build_journals()?;
572 for journal in r {
573 runner.with_read_only_journal(journal);
574 }
575 for journal in w {
576 runner.with_writable_journal(journal);
577 }
578 runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
579 }
580
581 Ok(runner)
582 }
583
584 #[tracing::instrument(skip_all)]
585 fn execute_wasi_module(
586 &self,
587 wasm_path: &Path,
588 module: Module,
589 module_hash: ModuleHash,
590 runtime: Arc<dyn Runtime + Send + Sync>,
591 ) -> Result<(), Error> {
592 let program_name = wasm_path.display().to_string();
593
594 let runner = self.build_wasi_runner(&runtime)?;
595 runner.run_wasm(
596 RuntimeOrEngine::Runtime(runtime),
597 &program_name,
598 module,
599 module_hash,
600 )
601 }
602
603 #[allow(unused_variables)]
604 fn maybe_save_coredump(&self, e: &Error) {
605 #[cfg(feature = "coredump")]
606 if let Some(coredump) = &self.coredump_on_trap {
607 if let Err(e) = generate_coredump(e, self.input.to_string(), coredump) {
608 tracing::warn!(
609 error = &*e as &dyn std::error::Error,
610 coredump_path=%coredump.display(),
611 "Unable to generate a coredump",
612 );
613 }
614 }
615 }
616
617 pub fn from_binfmt_args() -> Self {
620 Run::from_binfmt_args_fallible().unwrap_or_else(|e| {
621 crate::error::PrettyError::report::<()>(
622 Err(e).context("Failed to set up wasmer binfmt invocation"),
623 )
624 })
625 }
626
627 fn from_binfmt_args_fallible() -> Result<Self, Error> {
628 if cfg!(not(target_os = "linux")) {
629 bail!("binfmt_misc is only available on linux.");
630 }
631
632 let argv = std::env::args().collect::<Vec<_>>();
633 let (_interpreter, executable, original_executable, args) = match &argv[..] {
634 [a, b, c, rest @ ..] => (a, b, c, rest),
635 _ => {
636 bail!(
637 "Wasmer binfmt interpreter needs at least three arguments (including $0) - must be registered as binfmt interpreter with the CFP flags. (Got arguments: {argv:?})"
638 );
639 }
640 };
641 let rt = RuntimeOptions::default();
642 Ok(Run {
643 env: WasmerEnv::default(),
644 rt,
645 wasi: Wasi::for_binfmt_interpreter()?,
646 wcgi: WcgiOptions::default(),
647 stack_size: None,
648 entrypoint: Some(original_executable.to_string()),
649 invoke: None,
650 coredump_on_trap: None,
651 input: PackageSource::infer(executable)?,
652 args: args.to_vec(),
653 hash_algorithm: None,
654 })
655 }
656}
657
658fn invoke_function(
659 instance: &Instance,
660 store: &mut Store,
661 func: &Function,
662 args: &[String],
663) -> Result<Box<[Value]>, Error> {
664 let func_ty = func.ty(store);
665 let required_arguments = func_ty.params().len();
666 let provided_arguments = args.len();
667
668 anyhow::ensure!(
669 required_arguments == provided_arguments,
670 "Function expected {required_arguments} arguments, but received {provided_arguments}"
671 );
672
673 let invoke_args = args
674 .iter()
675 .zip(func_ty.params().iter())
676 .map(|(arg, param_type)| {
677 parse_value(arg, *param_type)
678 .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
679 })
680 .collect::<Result<Vec<_>, _>>()?;
681
682 let return_values = func.call(store, &invoke_args)?;
683
684 Ok(return_values)
685}
686
687fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
688 let value = match ty {
689 Type::I32 => Value::I32(s.parse()?),
690 Type::I64 => Value::I64(s.parse()?),
691 Type::F32 => Value::F32(s.parse()?),
692 Type::F64 => Value::F64(s.parse()?),
693 Type::V128 => Value::V128(s.parse()?),
694 _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
695 };
696 Ok(value)
697}
698
699#[derive(Debug, Clone, PartialEq)]
701enum PackageSource {
702 File(PathBuf),
704 Dir(PathBuf),
706 Package(PackageSpecifier),
708}
709
710impl PackageSource {
711 fn infer(s: &str) -> Result<PackageSource, Error> {
712 let path = Path::new(s);
713 if path.is_file() {
714 return Ok(PackageSource::File(path.to_path_buf()));
715 } else if path.is_dir() {
716 return Ok(PackageSource::Dir(path.to_path_buf()));
717 }
718
719 if let Ok(pkg) = PackageSpecifier::from_str(s) {
720 return Ok(PackageSource::Package(pkg));
721 }
722
723 Err(anyhow::anyhow!(
724 "Unable to resolve \"{s}\" as a URL, package name, or file on disk"
725 ))
726 }
727
728 #[tracing::instrument(level = "debug", skip_all)]
733 fn resolve_target(
734 &self,
735 rt: &Arc<dyn Runtime + Send + Sync>,
736 pb: &ProgressBar,
737 ) -> Result<ExecutableTarget, Error> {
738 match self {
739 PackageSource::File(path) => ExecutableTarget::from_file(path, rt, pb),
740 PackageSource::Dir(d) => ExecutableTarget::from_dir(d, rt, pb),
741 PackageSource::Package(pkg) => {
742 pb.set_message("Loading from the registry");
743 let inner_pck = pkg.clone();
744 let inner_rt = rt.clone();
745 let pkg = rt.task_manager().spawn_and_block_on(async move {
746 BinaryPackage::from_registry(&inner_pck, inner_rt.as_ref()).await
747 })??;
748 Ok(ExecutableTarget::Package(Box::new(pkg)))
749 }
750 }
751 }
752}
753
754impl Display for PackageSource {
755 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
756 match self {
757 PackageSource::File(path) | PackageSource::Dir(path) => write!(f, "{}", path.display()),
758 PackageSource::Package(p) => write!(f, "{p}"),
759 }
760 }
761}
762
763#[derive(Debug, Clone)]
766enum TargetOnDisk {
767 WebAssemblyBinary,
768 Wat,
769 LocalWebc,
770 Artifact,
771}
772
773impl TargetOnDisk {
774 fn from_file(path: &Path) -> Result<TargetOnDisk, Error> {
775 let mut buffer = [0_u8; 512];
778
779 let mut f = File::open(path)
780 .with_context(|| format!("Unable to open \"{}\" for reading", path.display()))?;
781 let bytes_read = f.read(&mut buffer)?;
782
783 let leading_bytes = &buffer[..bytes_read];
784
785 if wasmer::is_wasm(leading_bytes) {
786 return Ok(TargetOnDisk::WebAssemblyBinary);
787 }
788
789 if webc::detect(leading_bytes).is_ok() {
790 return Ok(TargetOnDisk::LocalWebc);
791 }
792
793 #[cfg(feature = "compiler")]
794 if ArtifactBuild::is_deserializable(leading_bytes) {
795 return Ok(TargetOnDisk::Artifact);
796 }
797
798 match path.extension().and_then(|s| s.to_str()) {
802 Some("wat") => Ok(TargetOnDisk::Wat),
803 Some("wasm") => Ok(TargetOnDisk::WebAssemblyBinary),
804 Some("webc") => Ok(TargetOnDisk::LocalWebc),
805 Some("wasmu") => Ok(TargetOnDisk::WebAssemblyBinary),
806 _ => bail!("Unable to determine how to execute \"{}\"", path.display()),
807 }
808 }
809}
810
811#[derive(Debug, Clone)]
812enum ExecutableTarget {
813 WebAssembly {
814 module: Module,
815 module_hash: ModuleHash,
816 path: PathBuf,
817 },
818 Package(Box<BinaryPackage>),
819}
820
821impl ExecutableTarget {
822 #[tracing::instrument(level = "debug", skip_all)]
825 fn from_dir(
826 dir: &Path,
827 runtime: &Arc<dyn Runtime + Send + Sync>,
828 pb: &ProgressBar,
829 ) -> Result<Self, Error> {
830 pb.set_message(format!("Loading \"{}\" into memory", dir.display()));
831 pb.set_message("Resolving dependencies");
832 let inner_runtime = runtime.clone();
833 let pkg = runtime.task_manager().spawn_and_block_on({
834 let path = dir.to_path_buf();
835
836 async move { BinaryPackage::from_dir(&path, inner_runtime.as_ref()).await }
837 })??;
838
839 Ok(ExecutableTarget::Package(Box::new(pkg)))
840 }
841
842 #[tracing::instrument(level = "debug", skip_all)]
844 fn from_file(
845 path: &Path,
846 runtime: &Arc<dyn Runtime + Send + Sync>,
847 pb: &ProgressBar,
848 ) -> Result<Self, Error> {
849 pb.set_message(format!("Loading from \"{}\"", path.display()));
850
851 match TargetOnDisk::from_file(path)? {
852 TargetOnDisk::WebAssemblyBinary | TargetOnDisk::Wat => {
853 let wasm = std::fs::read(path)?;
854 let module_data = HashedModuleData::new(wasm);
855 let module_hash = *module_data.hash();
856
857 pb.set_message("Compiling to WebAssembly");
858 let module = runtime
859 .load_hashed_module_sync(module_data, None)
860 .with_context(|| format!("Unable to compile \"{}\"", path.display()))?;
861
862 Ok(ExecutableTarget::WebAssembly {
863 module,
864 module_hash,
865 path: path.to_path_buf(),
866 })
867 }
868 TargetOnDisk::Artifact => {
869 let engine = runtime.engine();
870 pb.set_message("Deserializing pre-compiled WebAssembly module");
871 let module = unsafe { Module::deserialize_from_file(&engine, path)? };
872
873 let module_hash = module.info().hash.ok_or_else(|| {
874 anyhow::Error::msg("module hash is not present in the artifact")
875 })?;
876
877 Ok(ExecutableTarget::WebAssembly {
878 module,
879 module_hash,
880 path: path.to_path_buf(),
881 })
882 }
883 TargetOnDisk::LocalWebc => {
884 let container = from_disk(path)?;
885 pb.set_message("Resolving dependencies");
886
887 let inner_runtime = runtime.clone();
888 let pkg = runtime.task_manager().spawn_and_block_on(async move {
889 BinaryPackage::from_webc(&container, inner_runtime.as_ref()).await
890 })??;
891 Ok(ExecutableTarget::Package(Box::new(pkg)))
892 }
893 }
894 }
895}
896
897#[cfg(feature = "coredump")]
898fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
899 let err: &wasmer::RuntimeError = match err.downcast_ref() {
900 Some(e) => e,
901 None => {
902 log::warn!("no runtime error found to generate coredump with");
903 return Ok(());
904 }
905 };
906
907 let mut coredump_builder =
908 wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
909
910 let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
911
912 for frame in err.trace() {
913 let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
914 .codeoffset(frame.func_offset() as u32)
915 .funcidx(frame.func_index())
916 .build();
917 thread_builder.add_frame(coredump_frame);
918 }
919
920 coredump_builder.add_thread(thread_builder.build());
921
922 let coredump = coredump_builder
923 .serialize()
924 .map_err(Error::msg)
925 .context("Coredump serializing failed")?;
926
927 std::fs::write(coredump_path, &coredump).with_context(|| {
928 format!(
929 "Unable to save the coredump to \"{}\"",
930 coredump_path.display()
931 )
932 })?;
933
934 Ok(())
935}
936
937#[derive(Debug, Clone, Parser)]
938pub(crate) struct WcgiOptions {
939 #[clap(long, short, env, default_value_t = ([127, 0, 0, 1], 8000).into())]
941 pub(crate) addr: SocketAddr,
942}
943
944impl Default for WcgiOptions {
945 fn default() -> Self {
946 Self {
947 addr: ([127, 0, 0, 1], 8000).into(),
948 }
949 }
950}
951
952#[derive(Debug)]
953struct Callbacks {
954 stderr: Mutex<LineWriter<std::io::Stderr>>,
955 addr: SocketAddr,
956}
957
958impl Callbacks {
959 fn new(addr: SocketAddr) -> Self {
960 Callbacks {
961 stderr: Mutex::new(LineWriter::new(std::io::stderr())),
962 addr,
963 }
964 }
965}
966
967impl wasmer_wasix::runners::wcgi::Callbacks for Callbacks {
968 fn started(&self, _abort: AbortHandle) {
969 println!("WCGI Server running at http://{}/", self.addr);
970 }
971
972 fn on_stderr(&self, raw_message: &[u8]) {
973 if let Ok(mut stderr) = self.stderr.lock() {
974 let _ = stderr.write_all(raw_message);
979 }
980 }
981}
982
983fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
986 let exit_code = match result {
987 Ok(_) => 0,
988 Err(error) => {
989 match error.chain().find_map(get_exit_code) {
990 Some(exit_code) => exit_code.raw(),
991 None => {
992 eprintln!("{:?}", PrettyError::new(error));
993 1
995 }
996 }
997 }
998 };
999
1000 std::io::stdout().flush().ok();
1001 std::io::stderr().flush().ok();
1002
1003 std::process::exit(exit_code);
1004}
1005
1006fn get_exit_code(
1007 error: &(dyn std::error::Error + 'static),
1008) -> Option<wasmer_wasix::types::wasi::ExitCode> {
1009 if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
1010 return Some(*exit_code);
1011 }
1012 if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
1013 return error.as_exit_code();
1014 }
1015
1016 None
1017}
1018
1019#[derive(Debug)]
1020struct MonitoringRuntime<R> {
1021 runtime: Arc<R>,
1022 progress: ProgressBar,
1023}
1024
1025impl<R> MonitoringRuntime<R> {
1026 fn new(runtime: R, progress: ProgressBar) -> Self {
1027 MonitoringRuntime {
1028 runtime: Arc::new(runtime),
1029 progress,
1030 }
1031 }
1032}
1033
1034impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
1035 fn networking(&self) -> &virtual_net::DynVirtualNetworking {
1036 self.runtime.networking()
1037 }
1038
1039 fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
1040 self.runtime.task_manager()
1041 }
1042
1043 fn package_loader(
1044 &self,
1045 ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
1046 let inner = self.runtime.package_loader();
1047 Arc::new(MonitoringPackageLoader {
1048 inner,
1049 progress: self.progress.clone(),
1050 })
1051 }
1052
1053 fn module_cache(
1054 &self,
1055 ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
1056 self.runtime.module_cache()
1057 }
1058
1059 fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
1060 let inner = self.runtime.source();
1061 Arc::new(MonitoringSource {
1062 inner,
1063 progress: self.progress.clone(),
1064 })
1065 }
1066
1067 fn engine(&self) -> wasmer::Engine {
1068 self.runtime.engine()
1069 }
1070
1071 fn new_store(&self) -> wasmer::Store {
1072 self.runtime.new_store()
1073 }
1074
1075 fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
1076 self.runtime.http_client()
1077 }
1078
1079 fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
1080 self.runtime.tty()
1081 }
1082
1083 #[cfg(feature = "journal")]
1084 fn read_only_journals<'a>(
1085 &'a self,
1086 ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
1087 self.runtime.read_only_journals()
1088 }
1089
1090 #[cfg(feature = "journal")]
1091 fn writable_journals<'a>(
1092 &'a self,
1093 ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
1094 self.runtime.writable_journals()
1095 }
1096
1097 #[cfg(feature = "journal")]
1098 fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
1099 self.runtime.active_journal()
1100 }
1101
1102 fn load_hashed_module(
1103 &self,
1104 module: HashedModuleData,
1105 engine: Option<&Engine>,
1106 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1107 let hash = *module.hash();
1108 let fut = self.runtime.load_hashed_module(module, engine);
1109 Box::pin(compile_with_progress(fut, hash, None))
1110 }
1111
1112 fn load_hashed_module_sync(
1113 &self,
1114 wasm: HashedModuleData,
1115 engine: Option<&Engine>,
1116 ) -> Result<Module, wasmer_wasix::SpawnError> {
1117 let hash = *wasm.hash();
1118 compile_with_progress_sync(
1119 || self.runtime.load_hashed_module_sync(wasm, engine),
1120 &hash,
1121 None,
1122 )
1123 }
1124
1125 fn load_command_module(
1126 &self,
1127 cmd: &BinaryPackageCommand,
1128 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
1129 let fut = self.runtime.load_command_module(cmd);
1130
1131 Box::pin(compile_with_progress(
1132 fut,
1133 *cmd.hash(),
1134 Some(cmd.name().to_owned()),
1135 ))
1136 }
1137
1138 fn load_command_module_sync(
1139 &self,
1140 cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand,
1141 ) -> Result<Module, wasmer_wasix::SpawnError> {
1142 compile_with_progress_sync(
1143 || self.runtime.load_command_module_sync(cmd),
1144 cmd.hash(),
1145 Some(cmd.name()),
1146 )
1147 }
1148}
1149
1150async fn compile_with_progress<'a, F, T>(fut: F, hash: ModuleHash, name: Option<String>) -> T
1151where
1152 F: std::future::Future<Output = T> + Send + 'a,
1153 T: Send + 'static,
1154{
1155 let mut pb = new_progressbar_compile(&hash, name.as_deref());
1156 let res = fut.await;
1157 pb.finish_and_clear();
1158 res
1159}
1160
1161fn compile_with_progress_sync<F, T>(f: F, hash: &ModuleHash, name: Option<&str>) -> T
1162where
1163 F: FnOnce() -> T,
1164{
1165 let mut pb = new_progressbar_compile(hash, name);
1166 let res = f();
1167 pb.finish_and_clear();
1168 res
1169}
1170
1171fn new_progressbar_compile(hash: &ModuleHash, name: Option<&str>) -> ProgressBar {
1172 if std::io::stderr().is_terminal() {
1174 let msg = if let Some(name) = name {
1175 format!("Compiling WebAssembly module for command '{name}' ({hash})...")
1176 } else {
1177 format!("Compiling WebAssembly module {hash}...")
1178 };
1179 let pb = ProgressBar::new_spinner().with_message(msg);
1180 pb.enable_steady_tick(Duration::from_millis(100));
1181 pb
1182 } else {
1183 ProgressBar::hidden()
1184 }
1185}
1186
1187#[derive(Debug)]
1188struct MonitoringSource {
1189 inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
1190 progress: ProgressBar,
1191}
1192
1193#[async_trait::async_trait]
1194impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
1195 async fn query(
1196 &self,
1197 package: &PackageSpecifier,
1198 ) -> Result<Vec<wasmer_wasix::runtime::resolver::PackageSummary>, QueryError> {
1199 self.progress.set_message(format!("Looking up {package}"));
1200 self.inner.query(package).await
1201 }
1202}
1203
1204#[derive(Debug)]
1205struct MonitoringPackageLoader {
1206 inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
1207 progress: ProgressBar,
1208}
1209
1210#[async_trait::async_trait]
1211impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
1212 async fn load(
1213 &self,
1214 summary: &wasmer_wasix::runtime::resolver::PackageSummary,
1215 ) -> Result<Container, Error> {
1216 let pkg_id = summary.package_id();
1217 self.progress.set_message(format!("Downloading {pkg_id}"));
1218
1219 self.inner.load(summary).await
1220 }
1221
1222 async fn load_package_tree(
1223 &self,
1224 root: &Container,
1225 resolution: &wasmer_wasix::runtime::resolver::Resolution,
1226 root_is_local_dir: bool,
1227 ) -> Result<BinaryPackage, Error> {
1228 self.inner
1229 .load_package_tree(root, resolution, root_is_local_dir)
1230 .await
1231 }
1232}