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 wasi::{RuntimeOrEngine, WasiRunner},
55 },
56 runtime::{
57 OverriddenRuntime,
58 module_cache::{CacheError, HashedModuleData},
59 package_loader::PackageLoader,
60 resolver::QueryError,
61 task_manager::VirtualTaskManagerExt,
62 },
63};
64use webc::Container;
65use webc::metadata::Manifest;
66
67use crate::{
68 backend::RuntimeOptions,
69 commands::run::{target::TargetOnDisk, wasi::Wasi},
70 config::WasmerEnv,
71 error::PrettyError,
72 logging::Output,
73};
74
75use self::{
76 package_source::CliPackageSource, runtime::MonitoringRuntime, target::ExecutableTarget,
77};
78
79const TICK: Duration = Duration::from_millis(250);
80
81#[derive(Debug, Parser)]
83pub struct Run {
84 #[clap(flatten)]
85 env: WasmerEnv,
86 #[clap(flatten)]
87 rt: RuntimeOptions,
88 #[clap(flatten)]
89 wasi: crate::commands::run::Wasi,
90 #[clap(long = "stack-size")]
92 stack_size: Option<usize>,
93 #[clap(short, long, aliases = &["command", "command-name"])]
95 entrypoint: Option<String>,
96 #[clap(short, long)]
98 invoke: Option<String>,
99 #[clap(name = "COREDUMP_PATH", long)]
101 coredump_on_trap: Option<PathBuf>,
102 #[clap(long = "experimental-napi")]
104 experimental_napi: bool,
105 #[clap(value_parser = CliPackageSource::infer)]
107 input: CliPackageSource,
108 args: Vec<String>,
110}
111
112impl Run {
113 #[cfg(feature = "napi-v8")]
114 fn module_needs_napi(module: &Module) -> bool {
115 let (napi_version, napi_extension_version) = wasmer_napi::module_needs_napi(module);
116 napi_version.is_some() || napi_extension_version.is_some()
117 }
118
119 #[cfg(feature = "napi-v8")]
120 fn maybe_wrap_runtime_with_napi(
121 &self,
122 module: &Module,
123 runtime: Arc<dyn Runtime + Send + Sync>,
124 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
125 use anyhow::ensure;
126
127 if !Self::module_needs_napi(module) {
128 return Ok(runtime);
129 }
130 ensure!(
131 self.experimental_napi,
132 "This module imports N-API. Re-run with '--experimental-napi' to enable the experimental N-API runtime."
133 );
134
135 let hooks = wasmer_napi::NapiCtx::default().runtime_hooks();
136 Ok(Arc::new(
137 OverriddenRuntime::new(runtime)
138 .with_additional_imports({
139 let hooks = hooks.clone();
140 move |module, store| hooks.additional_imports(module, store)
141 })
142 .with_instance_setup(move |module, store, instance, imported_memory| {
143 hooks.configure_instance(module, store, instance, imported_memory)
144 }),
145 ))
146 }
147
148 #[cfg(feature = "napi-v8")]
149 fn configure_wasi_runner_for_napi(&self, module: &Module, runner: &mut WasiRunner) {
150 if Self::module_needs_napi(module) {
151 runner
152 .capabilities_mut()
153 .threading
154 .enable_asynchronous_threading = false;
155 }
156 }
157
158 #[cfg(not(feature = "napi-v8"))]
159 fn maybe_wrap_runtime_with_napi(
160 &self,
161 _module: &Module,
162 runtime: Arc<dyn Runtime + Send + Sync>,
163 ) -> Result<Arc<dyn Runtime + Send + Sync>, Error> {
164 Ok(runtime)
165 }
166
167 #[cfg(not(feature = "napi-v8"))]
168 fn configure_wasi_runner_for_napi(&self, _module: &Module, _runner: &mut WasiRunner) {}
169
170 pub fn execute(self, output: Output) -> ! {
171 let result = self.execute_inner(output);
172 exit_with_wasi_exit_code(result);
173 }
174
175 #[tracing::instrument(level = "debug", name = "wasmer_run", skip_all)]
176 fn execute_inner(mut self, output: Output) -> Result<(), Error> {
177 self.print_option_warnings();
178
179 let pb = ProgressBar::new_spinner();
180 pb.set_draw_target(output.draw_target());
181 pb.enable_steady_tick(TICK);
182
183 pb.set_message("Initializing the WebAssembly VM");
184
185 let runtime = tokio::runtime::Builder::new_multi_thread()
186 .enable_all()
187 .build()?;
188 let handle = runtime.handle().clone();
189
190 let webc_version_var = std::env::var("WASMER_WEBC_VERSION");
193 let preferred_webc_version = match webc_version_var.as_deref() {
194 Ok("2") => webc::Version::V2,
195 Ok("3") | Err(_) => webc::Version::V3,
196 Ok(other) => {
197 bail!("unknown webc version: '{other}'");
198 }
199 };
200
201 let _guard = handle.enter();
202
203 let mut wasm_bytes: Option<Vec<u8>> = None;
205
206 tracing::info!("Input source: {:?}", self.input);
208 if let CliPackageSource::File(path) = &self.input {
209 tracing::info!("Input file path: {}", path.display());
210
211 let target = TargetOnDisk::from_file(path);
213 if let Ok(target) = target {
214 match target {
215 TargetOnDisk::WebAssemblyBinary => {
216 if let Ok(data) = std::fs::read(path) {
217 wasm_bytes = Some(data);
218 } else {
219 tracing::info!("Failed to read file: {}", path.display());
220 }
221 }
222 TargetOnDisk::Wat => match std::fs::read(path) {
223 Ok(data) => match wat2wasm(&data) {
224 Ok(wasm) => {
225 wasm_bytes = Some(wasm.to_vec());
226 }
227 Err(e) => {
228 tracing::info!(
229 "Failed to convert WAT to Wasm for {}: {e}",
230 path.display()
231 );
232 }
233 },
234 Err(e) => {
235 tracing::info!("Failed to read WAT file {}: {e}", path.display());
236 }
237 },
238 _ => {}
239 }
240 } else {
241 tracing::info!(
242 "Failed to read file for feature detection: {}",
243 path.display()
244 );
245 }
246 } else {
247 tracing::info!("Input is not a file, skipping WebAssembly feature detection");
248 }
249
250 let mut engine = match &wasm_bytes {
252 Some(wasm_bytes) => {
253 tracing::info!("Attempting to detect WebAssembly features from binary");
254
255 self.rt
256 .get_engine_for_module(wasm_bytes, &Target::default())?
257 }
258 None => {
259 if let CliPackageSource::Package(pkg_source) = &self.input {
261 tracing::info!("Checking package for WebAssembly features: {}", pkg_source);
262 self.rt.get_engine(&Target::default())?
263 } else {
264 tracing::info!("No feature detection possible, using default engine");
265 self.rt.get_engine(&Target::default())?
266 }
267 }
268 };
269
270 let engine_kind = engine.deterministic_id();
271 tracing::info!("Executing on backend {engine_kind:?}");
272
273 #[cfg(feature = "sys")]
274 if engine.is_sys()
275 && let Some(stack_size) = self.stack_size
276 {
277 wasmer_vm::set_stack_size(stack_size);
278 }
279
280 let engine = engine.clone();
281
282 let runtime = self.wasi.prepare_runtime(
283 engine,
284 &self.env,
285 &capabilities::get_capability_cache_path(&self.env, &self.input)?,
286 runtime,
287 preferred_webc_version,
288 self.rt.compiler_debug_dir.is_some(),
289 )?;
290
291 let monitoring_runtime = Arc::new(MonitoringRuntime::new(
294 runtime,
295 pb.clone(),
296 output.is_quiet_or_no_tty(),
297 ));
298 let runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime.runtime.clone();
299 let monitoring_runtime: Arc<dyn Runtime + Send + Sync> = monitoring_runtime;
300
301 let target = self.input.resolve_target(&monitoring_runtime, &pb)?;
302
303 if let ExecutableTarget::Package(ref pkg) = target {
304 self.wasi
305 .volumes
306 .extend(pkg.additional_host_mapped_directories.clone());
307 }
308
309 pb.finish_and_clear();
310
311 let tty = runtime.tty().map(|tty| tty.tty_get());
313
314 let result = {
315 match target {
316 ExecutableTarget::WebAssembly {
317 module,
318 module_hash,
319 path,
320 } => self.execute_wasm(&path, module, module_hash, runtime.clone()),
321 ExecutableTarget::Package(pkg) => {
322 if let Some(cmd) = pkg.get_entrypoint_command()
324 && let Some(features) = cmd.wasm_features()
325 {
326 let backends = self.rt.get_available_backends()?;
328 let available_engines = backends
329 .iter()
330 .map(|b| b.to_string())
331 .collect::<Vec<_>>()
332 .join(", ");
333
334 let filtered_backends = RuntimeOptions::filter_backends_by_features(
335 backends.clone(),
336 &features,
337 &Target::default(),
338 );
339
340 if let Some(backend) = filtered_backends.first() {
341 let engine_id = backend.to_string();
342
343 if let Ok(new_engine) = backend.get_engine(&Target::default(), &self.rt)
345 {
346 tracing::info!(
347 "The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
348 cmd.name(),
349 features,
350 available_engines,
351 engine_id
352 );
353 let capability_cache_path =
355 capabilities::get_capability_cache_path(
356 &self.env,
357 &self.input,
358 )?;
359 let new_runtime = self.wasi.prepare_runtime(
360 new_engine,
361 &self.env,
362 &capability_cache_path,
363 tokio::runtime::Builder::new_multi_thread()
364 .enable_all()
365 .build()?,
366 preferred_webc_version,
367 self.rt.compiler_debug_dir.is_some(),
368 )?;
369
370 let new_runtime = Arc::new(MonitoringRuntime::new(
371 new_runtime,
372 pb.clone(),
373 output.is_quiet_or_no_tty(),
374 ));
375 return self.execute_webc(&pkg, new_runtime);
376 }
377 }
378 }
379 self.execute_webc(&pkg, monitoring_runtime)
380 }
381 }
382 };
383
384 if let Some(state) = tty
386 && let Some(tty) = runtime.tty()
387 {
388 tty.tty_set(state);
389 }
390
391 if let Err(e) = &result {
392 self.maybe_save_coredump(e);
393 }
394
395 result
396 }
397
398 #[tracing::instrument(skip_all)]
399 fn execute_wasm(
400 &self,
401 path: &Path,
402 module: Module,
403 module_hash: ModuleHash,
404 runtime: Arc<dyn Runtime + Send + Sync>,
405 ) -> Result<(), Error> {
406 if wasmer_wasix::is_wasi_module(&module) || wasmer_wasix::is_wasix_module(&module) {
407 self.execute_wasi_module(path, module, module_hash, runtime)
408 } else {
409 self.execute_pure_wasm_module(&module)
410 }
411 }
412
413 #[tracing::instrument(skip_all)]
414 fn execute_webc(
415 &self,
416 pkg: &BinaryPackage,
417 runtime: Arc<dyn Runtime + Send + Sync>,
418 ) -> Result<(), Error> {
419 let id = match self.entrypoint.as_deref() {
420 Some(cmd) => cmd,
421 None => pkg.infer_entrypoint()?,
422 };
423 let cmd = pkg
424 .get_command(id)
425 .with_context(|| format!("Unable to get metadata for the \"{id}\" command"))?;
426
427 let uses = self.load_injected_packages(&runtime)?;
428
429 if WasiRunner::can_run_command(cmd.metadata())? {
430 self.run_wasi(id, pkg, uses, runtime)
431 } else {
432 bail!(
433 "Unable to find a runner that supports \"{}\"",
434 cmd.metadata().runner
435 );
436 }
437 }
438
439 #[tracing::instrument(level = "debug", skip_all)]
440 fn load_injected_packages(
441 &self,
442 runtime: &Arc<dyn Runtime + Send + Sync>,
443 ) -> Result<Vec<BinaryPackage>, Error> {
444 let mut dependencies = Vec::new();
445
446 for name in &self.wasi.uses {
447 let specifier = name
448 .parse::<PackageSource>()
449 .with_context(|| format!("Unable to parse \"{name}\" as a package specifier"))?;
450 let pkg = {
451 let specifier = specifier.clone();
452 let inner_runtime = runtime.clone();
453 runtime
454 .task_manager()
455 .spawn_and_block_on(async move {
456 BinaryPackage::from_registry(&specifier, inner_runtime.as_ref()).await
457 })
458 .with_context(|| format!("Unable to load \"{name}\""))??
459 };
460 dependencies.push(pkg);
461 }
462
463 Ok(dependencies)
464 }
465
466 fn run_wasi(
467 &self,
468 command_name: &str,
469 pkg: &BinaryPackage,
470 uses: Vec<BinaryPackage>,
471 runtime: Arc<dyn Runtime + Send + Sync>,
472 ) -> Result<(), Error> {
473 #[cfg(feature = "napi-v8")]
474 let (module, runtime) = {
475 let cmd = pkg.get_command(command_name).with_context(|| {
476 format!("Unable to get metadata for the \"{command_name}\" command")
477 })?;
478 let module = runtime.resolve_module_sync(
479 wasmer_wasix::runtime::ModuleInput::Command(Cow::Borrowed(cmd)),
480 None,
481 None,
482 )?;
483 let runtime = self.maybe_wrap_runtime_with_napi(&module, runtime)?;
484 (module, runtime)
485 };
486
487 let mut runner = self.build_wasi_runner(&runtime, true)?;
489 #[cfg(feature = "napi-v8")]
490 self.configure_wasi_runner_for_napi(&module, &mut runner);
491 Runner::run_command(&mut runner, command_name, pkg, runtime)
492 }
493
494 #[tracing::instrument(skip_all)]
495 fn execute_pure_wasm_module(&self, module: &Module) -> Result<(), Error> {
496 let mut store = self.rt.get_store()?;
499 let imports = Imports::default();
500 let instance = Instance::new(&mut store, module, &imports)
501 .context("Unable to instantiate the WebAssembly module")?;
502
503 let entry_function = match &self.invoke {
504 Some(entry) => {
505 instance.exports
506 .get_function(entry)
507 .with_context(|| format!("The module doesn't export a function named \"{entry}\""))?
508 },
509 None => {
510 instance.exports.get_function("_start")
511 .context("The module doesn't export a \"_start\" function. Either implement it or specify an entry function with --invoke")?
512 }
513 };
514
515 let result = invoke_function(&instance, &mut store, entry_function, &self.args)?;
516
517 match result {
518 Ok(return_values) => {
519 println!(
520 "{}",
521 return_values
522 .iter()
523 .map(|val| val.to_string())
524 .collect::<Vec<String>>()
525 .join(" ")
526 );
527 Ok(())
528 }
529 Err(err) => {
530 bail!("{}", err.display(&mut store));
531 }
532 }
533 }
534
535 fn build_wasi_runner(
536 &self,
537 runtime: &Arc<dyn Runtime + Send + Sync>,
538 is_wasix: bool,
539 ) -> Result<WasiRunner, anyhow::Error> {
540 let packages = self.load_injected_packages(runtime)?;
541
542 let mut runner = WasiRunner::new();
543
544 let (is_home_mapped, mapped_directories) = self.wasi.build_mapped_directories(is_wasix)?;
545
546 runner
547 .with_args(&self.args)
548 .with_injected_packages(packages)
549 .with_envs(self.wasi.env_vars.clone())
550 .with_mapped_host_commands(self.wasi.build_mapped_commands()?)
551 .with_mapped_directories(mapped_directories)
552 .with_home_mapped(is_home_mapped)
553 .with_forward_host_env(self.wasi.forward_host_env)
554 .with_capabilities(self.wasi.capabilities());
555
556 if let Some(cwd) = self.wasi.cwd.as_ref() {
557 if !cwd.starts_with("/") {
558 bail!("The argument to --cwd must be an absolute path");
559 }
560 runner.with_current_dir(cwd.clone());
561 }
562
563 if let Some(ref entry_function) = self.invoke {
564 runner.with_entry_function(entry_function);
565 }
566
567 #[cfg(feature = "journal")]
568 {
569 for trigger in self.wasi.snapshot_on.iter().cloned() {
570 runner.with_snapshot_trigger(trigger);
571 }
572 if self.wasi.snapshot_on.is_empty() && !self.wasi.writable_journals.is_empty() {
573 runner.with_default_snapshot_triggers();
574 }
575 if let Some(period) = self.wasi.snapshot_interval {
576 if self.wasi.writable_journals.is_empty() {
577 return Err(anyhow::format_err!(
578 "If you specify a snapshot interval then you must also specify a writable journal file"
579 ));
580 }
581 runner.with_snapshot_interval(Duration::from_millis(period));
582 }
583 if self.wasi.stop_after_snapshot {
584 runner.with_stop_running_after_snapshot(true);
585 }
586 let (r, w) = self.wasi.build_journals()?;
587 for journal in r {
588 runner.with_read_only_journal(journal);
589 }
590 for journal in w {
591 runner.with_writable_journal(journal);
592 }
593 runner.with_skip_stdio_during_bootstrap(self.wasi.skip_stdio_during_bootstrap);
594 }
595
596 Ok(runner)
597 }
598
599 #[tracing::instrument(skip_all)]
600 fn execute_wasi_module(
601 &self,
602 wasm_path: &Path,
603 module: Module,
604 module_hash: ModuleHash,
605 runtime: Arc<dyn Runtime + Send + Sync>,
606 ) -> Result<(), Error> {
607 let program_name = wasm_path.display().to_string();
608 let runtime = self.maybe_wrap_runtime_with_napi(&module, runtime)?;
609
610 let mut runner =
611 self.build_wasi_runner(&runtime, wasmer_wasix::is_wasix_module(&module))?;
612 self.configure_wasi_runner_for_napi(&module, &mut runner);
613 runner.run_wasm(
614 RuntimeOrEngine::Runtime(runtime),
615 &program_name,
616 module,
617 module_hash,
618 )
619 }
620
621 #[allow(unused_variables)]
622 fn maybe_save_coredump(&self, e: &Error) {
623 #[cfg(feature = "coredump")]
624 if let Some(coredump) = &self.coredump_on_trap
625 && let Err(e) = generate_coredump(e, self.input.to_string(), coredump)
626 {
627 tracing::warn!(
628 error = &*e as &dyn std::error::Error,
629 coredump_path=%coredump.display(),
630 "Unable to generate a coredump",
631 );
632 }
633 }
634
635 fn print_option_warnings(&self) {
636 if !self.wasi.mapped_dirs.is_empty() {
637 eprintln!(
638 "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
639 "warning: ".yellow(),
640 "--mapdir".yellow(),
641 "--volume".green()
642 );
643 }
644 if !self.wasi.pre_opened_directories.is_empty() {
645 eprintln!(
646 "{}The `{}` option is deprecated and will be removed in the next major release. Please use `{}` instead.",
647 "warning: ".yellow(),
648 "--dir".yellow(),
649 "--volume".green()
650 );
651 }
652 }
653}
654
655fn invoke_function(
656 instance: &Instance,
657 store: &mut Store,
658 func: &Function,
659 args: &[String],
660) -> anyhow::Result<Result<Box<[Value]>, RuntimeError>> {
661 let func_ty = func.ty(store);
662 let required_arguments = func_ty.params().len();
663 let provided_arguments = args.len();
664
665 anyhow::ensure!(
666 required_arguments == provided_arguments,
667 "Function expected {required_arguments} arguments, but received {provided_arguments}"
668 );
669
670 let invoke_args = args
671 .iter()
672 .zip(func_ty.params().iter())
673 .map(|(arg, param_type)| {
674 parse_value(arg, *param_type)
675 .with_context(|| format!("Unable to convert {arg:?} to {param_type:?}"))
676 })
677 .collect::<Result<Vec<_>, _>>()?;
678
679 Ok(func.call(store, &invoke_args))
680}
681
682fn parse_value(s: &str, ty: wasmer_types::Type) -> Result<Value, Error> {
683 let value = match ty {
684 Type::I32 => Value::I32(s.parse()?),
685 Type::I64 => Value::I64(s.parse()?),
686 Type::F32 => Value::F32(s.parse()?),
687 Type::F64 => Value::F64(s.parse()?),
688 Type::V128 => Value::V128(s.parse()?),
689 _ => bail!("There is no known conversion from {s:?} to {ty:?}"),
690 };
691 Ok(value)
692}
693
694#[cfg(feature = "coredump")]
695fn generate_coredump(err: &Error, source_name: String, coredump_path: &Path) -> Result<(), Error> {
696 let err: &wasmer::RuntimeError = match err.downcast_ref() {
697 Some(e) => e,
698 None => {
699 log::warn!("no runtime error found to generate coredump with");
700 return Ok(());
701 }
702 };
703
704 let mut coredump_builder =
705 wasm_coredump_builder::CoredumpBuilder::new().executable_name(&source_name);
706
707 let mut thread_builder = wasm_coredump_builder::ThreadBuilder::new().thread_name("main");
708
709 for frame in err.trace() {
710 let coredump_frame = wasm_coredump_builder::FrameBuilder::new()
711 .codeoffset(frame.func_offset() as u32)
712 .funcidx(frame.func_index())
713 .build();
714 thread_builder.add_frame(coredump_frame);
715 }
716
717 coredump_builder.add_thread(thread_builder.build());
718
719 let coredump = coredump_builder
720 .serialize()
721 .map_err(Error::msg)
722 .context("Coredump serializing failed")?;
723
724 std::fs::write(coredump_path, &coredump).with_context(|| {
725 format!(
726 "Unable to save the coredump to \"{}\"",
727 coredump_path.display()
728 )
729 })?;
730
731 Ok(())
732}
733
734fn exit_with_wasi_exit_code(result: Result<(), Error>) -> ! {
737 let exit_code = match result {
738 Ok(_) => 0,
739 Err(error) => {
740 match error.chain().find_map(get_exit_code) {
741 Some(exit_code) => exit_code.raw(),
742 None => {
743 eprintln!("{:?}", PrettyError::new(error));
744 1
746 }
747 }
748 }
749 };
750
751 std::io::stdout().flush().ok();
752 std::io::stderr().flush().ok();
753
754 std::process::exit(exit_code);
755}
756
757fn get_exit_code(
758 error: &(dyn std::error::Error + 'static),
759) -> Option<wasmer_wasix::types::wasi::ExitCode> {
760 if let Some(WasiError::Exit(exit_code)) = error.downcast_ref() {
761 return Some(*exit_code);
762 }
763 if let Some(error) = error.downcast_ref::<wasmer_wasix::WasiRuntimeError>() {
764 return error.as_exit_code();
765 }
766
767 None
768}