1#![cfg_attr(
4 not(feature = "component-model"),
5 allow(irrefutable_let_patterns, unreachable_patterns)
6)]
7
8use crate::common::{Profile, RunCommon, RunTarget};
9
10use anyhow::{anyhow, bail, Context as _, Error, Result};
11use clap::Parser;
12use std::ffi::OsString;
13use std::path::{Path, PathBuf};
14use std::sync::{Arc, Mutex};
15use std::thread;
16use wasi_common::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
17use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
18use wasmtime_wasi::WasiView;
19
20#[cfg(feature = "wasi-nn")]
21use wasmtime_wasi_nn::wit::WasiNnView;
22
23#[cfg(feature = "wasi-threads")]
24use wasmtime_wasi_threads::WasiThreadsCtx;
25
26#[cfg(feature = "wasi-http")]
27use wasmtime_wasi_http::WasiHttpCtx;
28#[cfg(feature = "wasi-keyvalue")]
29use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder};
30#[cfg(feature = "wasi-runtime-config")]
31use wasmtime_wasi_runtime_config::{WasiRuntimeConfig, WasiRuntimeConfigVariables};
32
33fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
34 let parts: Vec<&str> = s.splitn(2, '=').collect();
35 if parts.len() != 2 {
36 bail!("must contain exactly one equals character ('=')");
37 }
38 Ok((parts[0].into(), parts[1].into()))
39}
40
41#[derive(Parser, PartialEq)]
43pub struct RunCommand {
44 #[command(flatten)]
45 #[allow(missing_docs)]
46 pub run: RunCommon,
47
48 #[arg(long, value_name = "FUNCTION")]
50 pub invoke: Option<String>,
51
52 #[arg(
54 long = "preload",
55 number_of_values = 1,
56 value_name = "NAME=MODULE_PATH",
57 value_parser = parse_preloads,
58 )]
59 pub preloads: Vec<(String, PathBuf)>,
60
61 #[arg(long)]
68 pub argv0: Option<String>,
69
70 #[arg(value_name = "WASM", trailing_var_arg = true, required = true)]
76 pub module_and_args: Vec<OsString>,
77}
78
79enum CliLinker {
80 Core(wasmtime::Linker<Host>),
81 #[cfg(feature = "component-model")]
82 Component(wasmtime::component::Linker<Host>),
83}
84
85impl RunCommand {
86 pub fn execute(mut self) -> Result<()> {
88 self.run.common.init_logging()?;
89
90 let mut config = self.run.common.config(None, None)?;
91
92 if self.run.common.wasm.timeout.is_some() {
93 config.epoch_interruption(true);
94 }
95 match self.run.profile {
96 Some(Profile::Native(s)) => {
97 config.profiler(s);
98 }
99 Some(Profile::Guest { .. }) => {
100 config.epoch_interruption(true);
102 }
103 None => {}
104 }
105
106 let engine = Engine::new(&config)?;
107
108 let main = self
110 .run
111 .load_module(&engine, self.module_and_args[0].as_ref())?;
112
113 if let Some(path) = &self.run.common.debug.coredump {
115 if path.contains("%") {
116 bail!("the coredump-on-trap path does not support patterns yet.")
117 }
118 }
119
120 let mut linker = match &main {
121 RunTarget::Core(_) => CliLinker::Core(wasmtime::Linker::new(&engine)),
122 #[cfg(feature = "component-model")]
123 RunTarget::Component(_) => {
124 CliLinker::Component(wasmtime::component::Linker::new(&engine))
125 }
126 };
127 if let Some(enable) = self.run.common.wasm.unknown_exports_allow {
128 match &mut linker {
129 CliLinker::Core(l) => {
130 l.allow_unknown_exports(enable);
131 }
132 #[cfg(feature = "component-model")]
133 CliLinker::Component(_) => {
134 bail!("--allow-unknown-exports not supported with components");
135 }
136 }
137 }
138
139 let host = Host::default();
140 let mut store = Store::new(&engine, host);
141 self.populate_with_wasi(&mut linker, &mut store, &main)?;
142
143 store.data_mut().limits = self.run.store_limits();
144 store.limiter(|t| &mut t.limits);
145
146 if let Some(fuel) = self.run.common.wasm.fuel {
149 store.set_fuel(fuel)?;
150 }
151
152 let mut modules = Vec::new();
154 if let RunTarget::Core(m) = &main {
155 modules.push((String::new(), m.clone()));
156 }
157 for (name, path) in self.preloads.iter() {
158 let module = match self.run.load_module(&engine, path)? {
160 RunTarget::Core(m) => m,
161 #[cfg(feature = "component-model")]
162 RunTarget::Component(_) => bail!("components cannot be loaded with `--preload`"),
163 };
164 modules.push((name.clone(), module.clone()));
165
166 match &mut linker {
168 #[cfg(feature = "cranelift")]
169 CliLinker::Core(linker) => {
170 linker.module(&mut store, name, &module).context(format!(
171 "failed to process preload `{}` at `{}`",
172 name,
173 path.display()
174 ))?;
175 }
176 #[cfg(not(feature = "cranelift"))]
177 CliLinker::Core(_) => {
178 bail!("support for --preload disabled at compile time");
179 }
180 #[cfg(feature = "component-model")]
181 CliLinker::Component(_) => {
182 bail!("--preload cannot be used with components");
183 }
184 }
185 }
186
187 let result = wasmtime_wasi::runtime::with_ambient_tokio_runtime(|| {
196 self.load_main_module(&mut store, &mut linker, &main, modules)
197 .with_context(|| {
198 format!(
199 "failed to run main module `{}`",
200 self.module_and_args[0].to_string_lossy()
201 )
202 })
203 });
204
205 match result {
207 Ok(()) => (),
208 Err(e) => {
209 if store.data().preview1_ctx.is_some() {
213 return Err(wasi_common::maybe_exit_on_error(e));
214 } else if store.data().preview2_ctx.is_some() {
215 if let Some(exit) = e
216 .downcast_ref::<wasmtime_wasi::I32Exit>()
217 .map(|c| c.process_exit_code())
218 {
219 std::process::exit(exit);
220 }
221 if e.is::<wasmtime::Trap>() {
222 eprintln!("Error: {e:?}");
223 cfg_if::cfg_if! {
224 if #[cfg(unix)] {
225 std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT);
226 } else if #[cfg(windows)] {
227 std::process::exit(3);
229 }
230 }
231 }
232 return Err(e);
233 } else {
234 unreachable!("either preview1_ctx or preview2_ctx present")
235 }
236 }
237 }
238
239 Ok(())
240 }
241
242 fn compute_argv(&self) -> Result<Vec<String>> {
243 let mut result = Vec::new();
244
245 for (i, arg) in self.module_and_args.iter().enumerate() {
246 let arg = if i == 0 {
249 match &self.argv0 {
250 Some(s) => s.as_ref(),
251 None => Path::new(arg).components().next_back().unwrap().as_os_str(),
252 }
253 } else {
254 arg.as_ref()
255 };
256 result.push(
257 arg.to_str()
258 .ok_or_else(|| anyhow!("failed to convert {arg:?} to utf-8"))?
259 .to_string(),
260 );
261 }
262
263 Ok(result)
264 }
265
266 fn setup_epoch_handler(
267 &self,
268 store: &mut Store<Host>,
269 modules: Vec<(String, Module)>,
270 ) -> Result<Box<dyn FnOnce(&mut Store<Host>)>> {
271 if let Some(Profile::Guest { path, interval }) = &self.run.profile {
272 #[cfg(feature = "profiling")]
273 return Ok(self.setup_guest_profiler(store, modules, path, *interval));
274 #[cfg(not(feature = "profiling"))]
275 {
276 let _ = (modules, path, interval);
277 bail!("support for profiling disabled at compile time");
278 }
279 }
280
281 if let Some(timeout) = self.run.common.wasm.timeout {
282 store.set_epoch_deadline(1);
283 let engine = store.engine().clone();
284 thread::spawn(move || {
285 thread::sleep(timeout);
286 engine.increment_epoch();
287 });
288 }
289
290 Ok(Box::new(|_store| {}))
291 }
292
293 #[cfg(feature = "profiling")]
294 fn setup_guest_profiler(
295 &self,
296 store: &mut Store<Host>,
297 modules: Vec<(String, Module)>,
298 path: &str,
299 interval: std::time::Duration,
300 ) -> Box<dyn FnOnce(&mut Store<Host>)> {
301 use wasmtime::{AsContext, GuestProfiler, StoreContext, StoreContextMut, UpdateDeadline};
302
303 let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
304 store.data_mut().guest_profiler =
305 Some(Arc::new(GuestProfiler::new(module_name, interval, modules)));
306
307 fn sample(
308 mut store: StoreContextMut<Host>,
309 f: impl FnOnce(&mut GuestProfiler, StoreContext<Host>),
310 ) {
311 let mut profiler = store.data_mut().guest_profiler.take().unwrap();
312 f(
313 Arc::get_mut(&mut profiler).expect("profiling doesn't support threads yet"),
314 store.as_context(),
315 );
316 store.data_mut().guest_profiler = Some(profiler);
317 }
318
319 store.call_hook(|store, kind| {
320 sample(store, |profiler, store| profiler.call_hook(store, kind));
321 Ok(())
322 });
323
324 if let Some(timeout) = self.run.common.wasm.timeout {
325 let mut timeout = (timeout.as_secs_f64() / interval.as_secs_f64()).ceil() as u64;
326 assert!(timeout > 0);
327 store.epoch_deadline_callback(move |store| {
328 sample(store, |profiler, store| {
329 profiler.sample(store, std::time::Duration::ZERO)
330 });
331 timeout -= 1;
332 if timeout == 0 {
333 bail!("timeout exceeded");
334 }
335 Ok(UpdateDeadline::Continue(1))
336 });
337 } else {
338 store.epoch_deadline_callback(move |store| {
339 sample(store, |profiler, store| {
340 profiler.sample(store, std::time::Duration::ZERO)
341 });
342 Ok(UpdateDeadline::Continue(1))
343 });
344 }
345
346 store.set_epoch_deadline(1);
347 let engine = store.engine().clone();
348 thread::spawn(move || loop {
349 thread::sleep(interval);
350 engine.increment_epoch();
351 });
352
353 let path = path.to_string();
354 return Box::new(move |store| {
355 let profiler = Arc::try_unwrap(store.data_mut().guest_profiler.take().unwrap())
356 .expect("profiling doesn't support threads yet");
357 if let Err(e) = std::fs::File::create(&path)
358 .map_err(anyhow::Error::new)
359 .and_then(|output| profiler.finish(std::io::BufWriter::new(output)))
360 {
361 eprintln!("failed writing profile at {path}: {e:#}");
362 } else {
363 eprintln!();
364 eprintln!("Profile written to: {path}");
365 eprintln!("View this profile at https://profiler.firefox.com/.");
366 }
367 });
368 }
369
370 fn load_main_module(
371 &self,
372 store: &mut Store<Host>,
373 linker: &mut CliLinker,
374 module: &RunTarget,
375 modules: Vec<(String, Module)>,
376 ) -> Result<()> {
377 if self.run.common.wasm.unknown_imports_trap == Some(true) {
380 match linker {
381 CliLinker::Core(linker) => {
382 linker.define_unknown_imports_as_traps(module.unwrap_core())?;
383 }
384 #[cfg(feature = "component-model")]
385 CliLinker::Component(linker) => {
386 linker.define_unknown_imports_as_traps(module.unwrap_component())?;
387 }
388 }
389 }
390
391 if self.run.common.wasm.unknown_imports_default == Some(true) {
393 match linker {
394 CliLinker::Core(linker) => {
395 linker.define_unknown_imports_as_default_values(module.unwrap_core())?;
396 }
397 _ => bail!("cannot use `--default-values-unknown-imports` with components"),
398 }
399 }
400
401 let finish_epoch_handler = self.setup_epoch_handler(store, modules)?;
402
403 let result = match linker {
404 CliLinker::Core(linker) => {
405 let module = module.unwrap_core();
406 let instance = linker.instantiate(&mut *store, &module).context(format!(
407 "failed to instantiate {:?}",
408 self.module_and_args[0]
409 ))?;
410
411 if let Some(func) = instance.get_func(&mut *store, "_initialize") {
414 func.typed::<(), ()>(&store)?.call(&mut *store, ())?;
415 }
416
417 let func = if let Some(name) = &self.invoke {
420 Some(
421 instance
422 .get_func(&mut *store, name)
423 .ok_or_else(|| anyhow!("no func export named `{}` found", name))?,
424 )
425 } else {
426 instance
427 .get_func(&mut *store, "")
428 .or_else(|| instance.get_func(&mut *store, "_start"))
429 };
430
431 match func {
432 Some(func) => self.invoke_func(store, func),
433 None => Ok(()),
434 }
435 }
436 #[cfg(feature = "component-model")]
437 CliLinker::Component(linker) => {
438 if self.invoke.is_some() {
439 bail!("using `--invoke` with components is not supported");
440 }
441
442 let component = module.unwrap_component();
443
444 let command = wasmtime_wasi::bindings::sync::Command::instantiate(
445 &mut *store,
446 component,
447 linker,
448 )?;
449 let result = command
450 .wasi_cli_run()
451 .call_run(&mut *store)
452 .context("failed to invoke `run` function")
453 .map_err(|e| self.handle_core_dump(&mut *store, e));
454
455 result.and_then(|wasm_result| match wasm_result {
458 Ok(()) => Ok(()),
459 Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
460 })
461 }
462 };
463 finish_epoch_handler(store);
464
465 result
466 }
467
468 fn invoke_func(&self, store: &mut Store<Host>, func: Func) -> Result<()> {
469 let ty = func.ty(&store);
470 if ty.params().len() > 0 {
471 eprintln!(
472 "warning: using `--invoke` with a function that takes arguments \
473 is experimental and may break in the future"
474 );
475 }
476 let mut args = self.module_and_args.iter().skip(1);
477 let mut values = Vec::new();
478 for ty in ty.params() {
479 let val = match args.next() {
480 Some(s) => s,
481 None => {
482 if let Some(name) = &self.invoke {
483 bail!("not enough arguments for `{}`", name)
484 } else {
485 bail!("not enough arguments for command default")
486 }
487 }
488 };
489 let val = val
490 .to_str()
491 .ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
492 values.push(match ty {
493 ValType::I32 => Val::I32(val.parse()?),
497 ValType::I64 => Val::I64(val.parse()?),
498 ValType::F32 => Val::F32(val.parse::<f32>()?.to_bits()),
499 ValType::F64 => Val::F64(val.parse::<f64>()?.to_bits()),
500 t => bail!("unsupported argument type {:?}", t),
501 });
502 }
503
504 let mut results = vec![Val::null_func_ref(); ty.results().len()];
507 let invoke_res = func
508 .call(&mut *store, &values, &mut results)
509 .with_context(|| {
510 if let Some(name) = &self.invoke {
511 format!("failed to invoke `{name}`")
512 } else {
513 format!("failed to invoke command default")
514 }
515 });
516
517 if let Err(err) = invoke_res {
518 return Err(self.handle_core_dump(&mut *store, err));
519 }
520
521 if !results.is_empty() {
522 eprintln!(
523 "warning: using `--invoke` with a function that returns values \
524 is experimental and may break in the future"
525 );
526 }
527
528 for result in results {
529 match result {
530 Val::I32(i) => println!("{i}"),
531 Val::I64(i) => println!("{i}"),
532 Val::F32(f) => println!("{}", f32::from_bits(f)),
533 Val::F64(f) => println!("{}", f64::from_bits(f)),
534 Val::V128(i) => println!("{}", i.as_u128()),
535 Val::ExternRef(None) => println!("<null externref>"),
536 Val::ExternRef(Some(_)) => println!("<externref>"),
537 Val::FuncRef(None) => println!("<null funcref>"),
538 Val::FuncRef(Some(_)) => println!("<funcref>"),
539 Val::AnyRef(None) => println!("<null anyref>"),
540 Val::AnyRef(Some(_)) => println!("<anyref>"),
541 }
542 }
543
544 Ok(())
545 }
546
547 #[cfg(feature = "coredump")]
548 fn handle_core_dump(&self, store: &mut Store<Host>, err: Error) -> Error {
549 let coredump_path = match &self.run.common.debug.coredump {
550 Some(path) => path,
551 None => return err,
552 };
553 if !err.is::<wasmtime::Trap>() {
554 return err;
555 }
556 let source_name = self.module_and_args[0]
557 .to_str()
558 .unwrap_or_else(|| "unknown");
559
560 if let Err(coredump_err) = write_core_dump(store, &err, &source_name, coredump_path) {
561 eprintln!("warning: coredump failed to generate: {coredump_err}");
562 err
563 } else {
564 err.context(format!("core dumped at {coredump_path}"))
565 }
566 }
567
568 #[cfg(not(feature = "coredump"))]
569 fn handle_core_dump(&self, _store: &mut Store<Host>, err: Error) -> Error {
570 err
571 }
572
573 fn populate_with_wasi(
575 &self,
576 linker: &mut CliLinker,
577 store: &mut Store<Host>,
578 module: &RunTarget,
579 ) -> Result<()> {
580 let mut cli = self.run.common.wasi.cli;
581
582 if let Some(common) = self.run.common.wasi.common {
584 if cli.is_some() {
585 bail!(
586 "The -Scommon option should not be use with -Scli as it is a deprecated alias"
587 );
588 } else {
589 cli = Some(common);
592 }
593 }
594
595 if cli != Some(false) {
596 match linker {
597 CliLinker::Core(linker) => {
598 match (self.run.common.wasi.preview2, self.run.common.wasi.threads) {
599 (Some(false), _) | (None, Some(true)) => {
603 wasi_common::sync::add_to_linker(linker, |host| {
604 host.preview1_ctx.as_mut().unwrap()
605 })?;
606 self.set_preview1_ctx(store)?;
607 }
608 (Some(true), _) | (None, Some(false) | None) => {
615 if self.run.common.wasi.preview0 != Some(false) {
616 wasmtime_wasi::preview0::add_to_linker_sync(linker, |t| {
617 t.preview2_ctx()
618 })?;
619 }
620 wasmtime_wasi::preview1::add_to_linker_sync(linker, |t| {
621 t.preview2_ctx()
622 })?;
623 self.set_preview2_ctx(store)?;
624 }
625 }
626 }
627 #[cfg(feature = "component-model")]
628 CliLinker::Component(linker) => {
629 wasmtime_wasi::add_to_linker_sync(linker)?;
630 self.set_preview2_ctx(store)?;
631 }
632 }
633 }
634
635 if self.run.common.wasi.nn == Some(true) {
636 #[cfg(not(feature = "wasi-nn"))]
637 {
638 bail!("Cannot enable wasi-nn when the binary is not compiled with this feature.");
639 }
640 #[cfg(all(feature = "wasi-nn", feature = "component-model"))]
641 {
642 let (backends, registry) = self.collect_preloaded_nn_graphs()?;
643 match linker {
644 CliLinker::Core(linker) => {
645 wasmtime_wasi_nn::witx::add_to_linker(linker, |host| {
646 Arc::get_mut(host.wasi_nn_witx.as_mut().unwrap())
647 .expect("wasi-nn is not implemented with multi-threading support")
648 })?;
649 store.data_mut().wasi_nn_witx = Some(Arc::new(
650 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry),
651 ));
652 }
653 #[cfg(feature = "component-model")]
654 CliLinker::Component(linker) => {
655 wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| {
656 let preview2_ctx =
657 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
658 let preview2_ctx = Arc::get_mut(preview2_ctx)
659 .expect("wasmtime_wasi is not compatible with threads")
660 .get_mut()
661 .unwrap();
662 let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap())
663 .expect("wasi-nn is not implemented with multi-threading support");
664 WasiNnView::new(preview2_ctx.table(), nn_ctx)
665 })?;
666 store.data_mut().wasi_nn_wit = Some(Arc::new(
667 wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry),
668 ));
669 }
670 }
671 }
672 }
673
674 if self.run.common.wasi.runtime_config == Some(true) {
675 #[cfg(not(feature = "wasi-runtime-config"))]
676 {
677 bail!("Cannot enable wasi-runtime-config when the binary is not compiled with this feature.");
678 }
679 #[cfg(all(feature = "wasi-runtime-config", feature = "component-model"))]
680 {
681 match linker {
682 CliLinker::Core(_) => {
683 bail!("Cannot enable wasi-runtime-config for core wasm modules");
684 }
685 CliLinker::Component(linker) => {
686 let vars = WasiRuntimeConfigVariables::from_iter(
687 self.run
688 .common
689 .wasi
690 .runtime_config_var
691 .iter()
692 .map(|v| (v.key.clone(), v.value.clone())),
693 );
694
695 wasmtime_wasi_runtime_config::add_to_linker(linker, |h| {
696 WasiRuntimeConfig::new(
697 Arc::get_mut(h.wasi_runtime_config.as_mut().unwrap()).unwrap(),
698 )
699 })?;
700 store.data_mut().wasi_runtime_config = Some(Arc::new(vars));
701 }
702 }
703 }
704 }
705
706 if self.run.common.wasi.keyvalue == Some(true) {
707 #[cfg(not(feature = "wasi-keyvalue"))]
708 {
709 bail!("Cannot enable wasi-keyvalue when the binary is not compiled with this feature.");
710 }
711 #[cfg(all(feature = "wasi-keyvalue", feature = "component-model"))]
712 {
713 match linker {
714 CliLinker::Core(_) => {
715 bail!("Cannot enable wasi-keyvalue for core wasm modules");
716 }
717 CliLinker::Component(linker) => {
718 let ctx = WasiKeyValueCtxBuilder::new()
719 .in_memory_data(
720 self.run
721 .common
722 .wasi
723 .keyvalue_in_memory_data
724 .iter()
725 .map(|v| (v.key.clone(), v.value.clone())),
726 )
727 .build();
728
729 wasmtime_wasi_keyvalue::add_to_linker(linker, |h| {
730 let preview2_ctx =
731 h.preview2_ctx.as_mut().expect("wasip2 is not configured");
732 let preview2_ctx =
733 Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap();
734 WasiKeyValue::new(
735 Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(),
736 preview2_ctx.table(),
737 )
738 })?;
739 store.data_mut().wasi_keyvalue = Some(Arc::new(ctx));
740 }
741 }
742 }
743 }
744
745 if self.run.common.wasi.threads == Some(true) {
746 #[cfg(not(feature = "wasi-threads"))]
747 {
748 let _ = &module;
751
752 bail!(
753 "Cannot enable wasi-threads when the binary is not compiled with this feature."
754 );
755 }
756 #[cfg(feature = "wasi-threads")]
757 {
758 let linker = match linker {
759 CliLinker::Core(linker) => linker,
760 _ => bail!("wasi-threads does not support components yet"),
761 };
762 let module = module.unwrap_core();
763 wasmtime_wasi_threads::add_to_linker(linker, store, &module, |host| {
764 host.wasi_threads.as_ref().unwrap()
765 })?;
766 store.data_mut().wasi_threads = Some(Arc::new(WasiThreadsCtx::new(
767 module.clone(),
768 Arc::new(linker.clone()),
769 )?));
770 }
771 }
772
773 if self.run.common.wasi.http == Some(true) {
774 #[cfg(not(all(feature = "wasi-http", feature = "component-model")))]
775 {
776 bail!("Cannot enable wasi-http when the binary is not compiled with this feature.");
777 }
778 #[cfg(all(feature = "wasi-http", feature = "component-model"))]
779 {
780 match linker {
781 CliLinker::Core(_) => {
782 bail!("Cannot enable wasi-http for core wasm modules");
783 }
784 CliLinker::Component(linker) => {
785 wasmtime_wasi_http::add_only_http_to_linker_sync(linker)?;
786 }
787 }
788
789 store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
790 }
791 }
792
793 Ok(())
794 }
795
796 fn set_preview1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
797 let mut builder = WasiCtxBuilder::new();
798 builder.inherit_stdio().args(&self.compute_argv()?)?;
799
800 if self.run.common.wasi.inherit_env == Some(true) {
801 for (k, v) in std::env::vars() {
802 builder.env(&k, &v)?;
803 }
804 }
805 for (key, value) in self.run.vars.iter() {
806 let value = match value {
807 Some(value) => value.clone(),
808 None => match std::env::var_os(key) {
809 Some(val) => val
810 .into_string()
811 .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?,
812 None => {
813 continue;
815 }
816 },
817 };
818 builder.env(key, &value)?;
819 }
820
821 let mut num_fd: usize = 3;
822
823 if self.run.common.wasi.listenfd == Some(true) {
824 num_fd = ctx_set_listenfd(num_fd, &mut builder)?;
825 }
826
827 for listener in self.run.compute_preopen_sockets()? {
828 let listener = TcpListener::from_std(listener);
829 builder.preopened_socket(num_fd as _, listener)?;
830 num_fd += 1;
831 }
832
833 for (host, guest) in self.run.dirs.iter() {
834 let dir = Dir::open_ambient_dir(host, ambient_authority())
835 .with_context(|| format!("failed to open directory '{host}'"))?;
836 builder.preopened_dir(dir, guest)?;
837 }
838
839 store.data_mut().preview1_ctx = Some(builder.build());
840 Ok(())
841 }
842
843 fn set_preview2_ctx(&self, store: &mut Store<Host>) -> Result<()> {
844 let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
845 builder.inherit_stdio().args(&self.compute_argv()?);
846 self.run.configure_wasip2(&mut builder)?;
847 let ctx = builder.build_p1();
848 store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx)));
849 Ok(())
850 }
851
852 #[cfg(feature = "wasi-nn")]
853 fn collect_preloaded_nn_graphs(
854 &self,
855 ) -> Result<(Vec<wasmtime_wasi_nn::Backend>, wasmtime_wasi_nn::Registry)> {
856 let graphs = self
857 .run
858 .common
859 .wasi
860 .nn_graph
861 .iter()
862 .map(|g| (g.format.clone(), g.dir.clone()))
863 .collect::<Vec<_>>();
864 wasmtime_wasi_nn::preload(&graphs)
865 }
866}
867
868#[derive(Default, Clone)]
869struct Host {
870 preview1_ctx: Option<wasi_common::WasiCtx>,
871
872 preview2_ctx: Option<Arc<Mutex<wasmtime_wasi::preview1::WasiP1Ctx>>>,
876
877 #[cfg(feature = "wasi-nn")]
878 wasi_nn_wit: Option<Arc<wasmtime_wasi_nn::wit::WasiNnCtx>>,
879 #[cfg(feature = "wasi-nn")]
880 wasi_nn_witx: Option<Arc<wasmtime_wasi_nn::witx::WasiNnCtx>>,
881
882 #[cfg(feature = "wasi-threads")]
883 wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
884 #[cfg(feature = "wasi-http")]
885 wasi_http: Option<Arc<WasiHttpCtx>>,
886 limits: StoreLimits,
887 #[cfg(feature = "profiling")]
888 guest_profiler: Option<Arc<wasmtime::GuestProfiler>>,
889
890 #[cfg(feature = "wasi-runtime-config")]
891 wasi_runtime_config: Option<Arc<WasiRuntimeConfigVariables>>,
892 #[cfg(feature = "wasi-keyvalue")]
893 wasi_keyvalue: Option<Arc<WasiKeyValueCtx>>,
894}
895
896impl Host {
897 fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx {
898 let ctx = self
899 .preview2_ctx
900 .as_mut()
901 .expect("wasip2 is not configured");
902 Arc::get_mut(ctx)
903 .expect("wasmtime_wasi is not compatible with threads")
904 .get_mut()
905 .unwrap()
906 }
907}
908
909impl WasiView for Host {
910 fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
911 self.preview2_ctx().table()
912 }
913
914 fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx {
915 self.preview2_ctx().ctx()
916 }
917}
918
919#[cfg(feature = "wasi-http")]
920impl wasmtime_wasi_http::types::WasiHttpView for Host {
921 fn ctx(&mut self) -> &mut WasiHttpCtx {
922 let ctx = self.wasi_http.as_mut().unwrap();
923 Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads")
924 }
925
926 fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
927 self.preview2_ctx().table()
928 }
929}
930
931#[cfg(not(unix))]
932fn ctx_set_listenfd(num_fd: usize, _builder: &mut WasiCtxBuilder) -> Result<usize> {
933 Ok(num_fd)
934}
935
936#[cfg(unix)]
937fn ctx_set_listenfd(mut num_fd: usize, builder: &mut WasiCtxBuilder) -> Result<usize> {
938 use listenfd::ListenFd;
939
940 for env in ["LISTEN_FDS", "LISTEN_FDNAMES"] {
941 if let Ok(val) = std::env::var(env) {
942 builder.env(env, &val)?;
943 }
944 }
945
946 let mut listenfd = ListenFd::from_env();
947
948 for i in 0..listenfd.len() {
949 if let Some(stdlistener) = listenfd.take_tcp_listener(i)? {
950 let _ = stdlistener.set_nonblocking(true)?;
951 let listener = TcpListener::from_std(stdlistener);
952 builder.preopened_socket((3 + i) as _, listener)?;
953 num_fd = 3 + i;
954 }
955 }
956
957 Ok(num_fd)
958}
959
960#[cfg(feature = "coredump")]
961fn write_core_dump(
962 store: &mut Store<Host>,
963 err: &anyhow::Error,
964 name: &str,
965 path: &str,
966) -> Result<()> {
967 use std::fs::File;
968 use std::io::Write;
969
970 let core_dump = err
971 .downcast_ref::<wasmtime::WasmCoreDump>()
972 .expect("should have been configured to capture core dumps");
973
974 let core_dump = core_dump.serialize(store, name);
975
976 let mut core_dump_file =
977 File::create(path).context(format!("failed to create file at `{path}`"))?;
978 core_dump_file
979 .write_all(&core_dump)
980 .with_context(|| format!("failed to write core dump file at `{path}`"))?;
981 Ok(())
982}