1#[cfg(feature = "journal")]
2use crate::journal::{DynJournal, JournalEffector, SnapshotTrigger};
3use crate::{
4 Runtime, VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, WasiFunctionEnv,
5 WasiResult, WasiRuntimeError, WasiStateCreationError, WasiThreadError, WasiVFork,
6 bin_factory::{BinFactory, BinaryPackage, BinaryPackageCommand},
7 capabilities::Capabilities,
8 fs::{WasiFsRoot, WasiInodes},
9 import_object_for_all_wasi_versions,
10 os::task::{
11 control_plane::ControlPlaneError,
12 process::{WasiProcess, WasiProcessId},
13 thread::{WasiMemoryLayout, WasiThread, WasiThreadHandle, WasiThreadId},
14 },
15 syscalls::platform_clock_time_get,
16};
17use futures::future::BoxFuture;
18use rand::RngExt;
19use std::{
20 collections::HashMap,
21 ops::Deref,
22 path::{Path, PathBuf},
23 str,
24 sync::Arc,
25 time::Duration,
26};
27use virtual_fs::{FileSystem, FsError, VirtualFile};
28use virtual_mio::block_on;
29use virtual_net::DynVirtualNetworking;
30use wasmer::{
31 AsStoreMut, AsStoreRef, ExportError, FunctionEnvMut, Instance, Memory, MemoryType, MemoryView,
32 Module,
33};
34use wasmer_config::package::PackageSource;
35use wasmer_types::ModuleHash;
36use wasmer_wasix_types::{
37 types::Signal,
38 wasi::{Errno, ExitCode, Snapshot0Clockid},
39 wasix::ThreadStartType,
40};
41use webc::metadata::annotations::Wasi;
42
43pub use super::handles::*;
44use super::{Linker, WasiState, context_switching::ContextSwitchingEnvironment, conv_env_vars};
45
46#[derive(Debug)]
48pub struct WasiEnvInit {
49 pub(crate) state: WasiState,
50 pub runtime: Arc<dyn Runtime + Send + Sync>,
51 pub webc_dependencies: Vec<BinaryPackage>,
52 pub mapped_commands: HashMap<String, PathBuf>,
53 pub bin_factory: BinFactory,
54 pub capabilities: Capabilities,
55
56 pub control_plane: WasiControlPlane,
57 pub memory_ty: Option<MemoryType>,
58 pub process: Option<WasiProcess>,
59 pub thread: Option<WasiThreadHandle>,
60
61 pub call_initialize: bool,
64
65 pub can_deep_sleep: bool,
67
68 pub extra_tracing: bool,
70
71 #[cfg(feature = "journal")]
73 pub snapshot_on: Vec<SnapshotTrigger>,
74
75 #[cfg(feature = "journal")]
77 pub stop_running_after_snapshot: bool,
78
79 pub skip_stdio_during_bootstrap: bool,
81}
82
83impl WasiEnvInit {
84 pub fn duplicate(&self) -> Self {
85 let inodes = WasiInodes::new();
86
87 let fs =
89 crate::fs::WasiFs::new_with_preopen(&inodes, &[], &[], self.state.fs.root_fs.clone())
90 .unwrap();
91
92 Self {
93 state: WasiState {
94 secret: rand::rng().random::<[u8; 32]>(),
95 inodes,
96 fs,
97 futexs: Default::default(),
98 clock_offset: std::sync::Mutex::new(
99 self.state.clock_offset.lock().unwrap().clone(),
100 ),
101 args: std::sync::Mutex::new(self.state.args.lock().unwrap().clone()),
102 envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()),
103 signals: std::sync::Mutex::new(self.state.signals.lock().unwrap().deref().clone()),
104 preopen: self.state.preopen.clone(),
105 },
106 runtime: self.runtime.clone(),
107 webc_dependencies: self.webc_dependencies.clone(),
108 mapped_commands: self.mapped_commands.clone(),
109 bin_factory: self.bin_factory.clone(),
110 capabilities: self.capabilities.clone(),
111 control_plane: self.control_plane.clone(),
112 memory_ty: None,
113 process: None,
114 thread: None,
115 call_initialize: self.call_initialize,
116 can_deep_sleep: self.can_deep_sleep,
117 extra_tracing: false,
118 #[cfg(feature = "journal")]
119 snapshot_on: self.snapshot_on.clone(),
120 #[cfg(feature = "journal")]
121 stop_running_after_snapshot: self.stop_running_after_snapshot,
122 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
123 }
124 }
125}
126
127pub struct WasiEnv {
129 pub control_plane: WasiControlPlane,
130 pub process: WasiProcess,
132 pub thread: WasiThread,
134 pub layout: WasiMemoryLayout,
136 pub vfork: Option<WasiVFork>,
138 pub poll_seed: u64,
140 pub(crate) state: Arc<WasiState>,
143 pub bin_factory: BinFactory,
145 pub owned_handles: Vec<WasiThreadHandle>,
148 pub runtime: Arc<dyn Runtime + Send + Sync + 'static>,
150
151 pub capabilities: Capabilities,
152
153 pub enable_deep_sleep: bool,
155
156 pub enable_journal: bool,
158
159 pub enable_exponential_cpu_backoff: Option<Duration>,
163
164 pub replaying_journal: bool,
167
168 pub skip_stdio_during_bootstrap: bool,
170
171 pub(crate) disable_fs_cleanup: bool,
174
175 inner: WasiInstanceHandlesPointer,
180
181 pub(crate) context_switching_environment: Option<ContextSwitchingEnvironment>,
187}
188
189impl std::fmt::Debug for WasiEnv {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "env(pid={}, tid={})", self.pid().raw(), self.tid().raw())
192 }
193}
194
195impl Clone for WasiEnv {
196 fn clone(&self) -> Self {
197 Self {
198 control_plane: self.control_plane.clone(),
199 process: self.process.clone(),
200 poll_seed: self.poll_seed,
201 thread: self.thread.clone(),
202 layout: self.layout.clone(),
203 vfork: self.vfork.clone(),
204 state: self.state.clone(),
205 bin_factory: self.bin_factory.clone(),
206 inner: Default::default(),
207 owned_handles: self.owned_handles.clone(),
208 runtime: self.runtime.clone(),
209 capabilities: self.capabilities.clone(),
210 enable_deep_sleep: self.enable_deep_sleep,
211 enable_journal: self.enable_journal,
212 enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
213 replaying_journal: self.replaying_journal,
214 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
215 disable_fs_cleanup: self.disable_fs_cleanup,
216 context_switching_environment: None,
217 }
218 }
219}
220
221impl WasiEnv {
222 pub fn builder(program_name: impl Into<String>) -> WasiEnvBuilder {
224 WasiEnvBuilder::new(program_name)
225 }
226
227 pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> {
229 let process = self.control_plane.new_process(self.process.module_hash)?;
230 let handle = process.new_thread(self.layout.clone(), ThreadStartType::MainThread)?;
231
232 let thread = handle.as_thread();
233 thread.copy_stack_from(&self.thread);
234
235 let state = Arc::new(self.state.fork());
236
237 let bin_factory = self.bin_factory.clone();
238
239 let new_env = Self {
240 control_plane: self.control_plane.clone(),
241 process,
242 thread,
243 layout: self.layout.clone(),
244 vfork: None,
245 poll_seed: 0,
246 bin_factory,
247 state,
248 inner: Default::default(),
249 owned_handles: Vec::new(),
250 runtime: self.runtime.clone(),
251 capabilities: self.capabilities.clone(),
252 enable_deep_sleep: self.enable_deep_sleep,
253 enable_journal: self.enable_journal,
254 enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
255 replaying_journal: false,
256 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
257 disable_fs_cleanup: self.disable_fs_cleanup,
258 context_switching_environment: None,
259 };
260 Ok((new_env, handle))
261 }
262
263 pub fn pid(&self) -> WasiProcessId {
264 self.process.pid()
265 }
266
267 pub fn tid(&self) -> WasiThreadId {
268 self.thread.tid()
269 }
270
271 pub fn will_use_asyncify(&self) -> bool {
274 self.inner()
275 .static_module_instance_handles()
276 .map(|handles| self.enable_deep_sleep || handles.has_stack_checkpoint)
277 .unwrap_or(false)
278 }
279
280 pub fn reinit(&mut self) -> Result<(), WasiStateCreationError> {
282 if !self.disable_fs_cleanup {
286 if let Ok(mut map) = self.state.fs.fd_map.write() {
289 map.clear();
290 }
291 self.state.fs.preopen_fds.write().unwrap().clear();
292 *self.state.fs.current_dir.lock().unwrap() = "/".to_string();
293
294 self.state.fs.create_stdin(&self.state.inodes);
296 self.state.fs.create_stdout(&self.state.inodes);
297 self.state.fs.create_stderr(&self.state.inodes);
298 self.state
299 .fs
300 .create_rootfd()
301 .map_err(WasiStateCreationError::WasiFsSetupError)?;
302 self.state
303 .fs
304 .create_preopens(&self.state.inodes, true)
305 .map_err(WasiStateCreationError::WasiFsSetupError)?;
306 }
307
308 self.process = WasiProcess::new(
310 self.process.pid,
311 self.process.module_hash,
312 self.process.compute.clone(),
313 );
314 self.thread = WasiThread::new(
315 self.thread.pid(),
316 self.thread.tid(),
317 self.thread.is_main(),
318 self.process.finished.clone(),
319 self.process.compute.must_upgrade().register_task()?,
320 self.thread.memory_layout().clone(),
321 self.thread.thread_start_type(),
322 );
323
324 Ok(())
325 }
326
327 pub unsafe fn capable_of_deep_sleep(&self) -> bool {
335 self.deep_sleep_capability_requested() && self.deep_sleep_supported_by_module()
336 }
337
338 pub(crate) fn refresh_deep_sleep_capability(&mut self) {
339 self.enable_deep_sleep = if cfg!(feature = "js") {
340 false
341 } else {
342 self.deep_sleep_capability_requested() && self.deep_sleep_supported_by_module()
343 };
344 }
345
346 fn deep_sleep_capability_requested(&self) -> bool {
347 self.capabilities.threading.enable_deep_sleep
348 }
349
350 fn deep_sleep_supported_by_module(&self) -> bool {
351 self.try_inner()
352 .map(|handles| {
353 handles
354 .static_module_instance_handles()
355 .map(|handles| {
356 handles.asyncify_get_state.is_some()
357 && handles.asyncify_start_rewind.is_some()
358 && handles.asyncify_start_unwind.is_some()
359 })
360 .unwrap_or(false)
361 })
362 .unwrap_or(false)
363 }
364
365 pub fn layout(&self) -> &WasiMemoryLayout {
367 &self.layout
368 }
369
370 #[allow(clippy::result_large_err)]
371 pub(crate) fn from_init(
372 init: WasiEnvInit,
373 module_hash: ModuleHash,
374 ) -> Result<Self, WasiRuntimeError> {
375 let process = if let Some(p) = init.process {
376 p
377 } else {
378 init.control_plane.new_process(module_hash)?
379 };
380
381 #[cfg(feature = "journal")]
382 {
383 let mut guard = process.inner.0.lock().unwrap();
384 guard.snapshot_on = init.snapshot_on.into_iter().collect();
385 guard.stop_running_after_checkpoint = init.stop_running_after_snapshot;
386 }
387
388 let layout = WasiMemoryLayout::default();
389 let thread = if let Some(t) = init.thread {
390 t
391 } else {
392 process.new_thread(layout.clone(), ThreadStartType::MainThread)?
393 };
394
395 let mut env = Self {
396 control_plane: init.control_plane,
397 process,
398 thread: thread.as_thread(),
399 layout,
400 vfork: None,
401 poll_seed: 0,
402 state: Arc::new(init.state),
403 inner: Default::default(),
404 owned_handles: Vec::new(),
405 #[cfg(feature = "journal")]
406 enable_journal: init.runtime.active_journal().is_some(),
407 #[cfg(not(feature = "journal"))]
408 enable_journal: false,
409 replaying_journal: false,
410 skip_stdio_during_bootstrap: init.skip_stdio_during_bootstrap,
411 enable_deep_sleep: false,
412 enable_exponential_cpu_backoff: init
413 .capabilities
414 .threading
415 .enable_exponential_cpu_backoff,
416 runtime: init.runtime,
417 bin_factory: init.bin_factory,
418 capabilities: init.capabilities,
419 disable_fs_cleanup: false,
420 context_switching_environment: None,
421 };
422 env.owned_handles.push(thread);
423
424 for pkg in &init.webc_dependencies {
426 env.use_package(pkg)?;
427 }
428
429 #[cfg(feature = "sys")]
430 env.map_commands(init.mapped_commands.clone())?;
431
432 Ok(env)
433 }
434
435 #[allow(clippy::result_large_err)]
437 pub(crate) fn instantiate(
438 self,
439 module: Module,
440 store: &mut impl AsStoreMut,
441 memory: Option<Memory>,
442 update_layout: bool,
443 call_initialize: bool,
444 parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut<WasiEnv>)>,
445 ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> {
446 let pid = self.process.pid();
447
448 let mut store = store.as_store_mut();
449 let engine = self.runtime().engine();
450 let mut func_env = WasiFunctionEnv::new(&mut store, self);
451
452 let is_dl = super::linker::is_dynamically_linked(&module);
453 if is_dl {
454 let linker = match parent_linker_and_ctx {
455 Some((linker, ctx)) => linker.create_instance_group(ctx, &mut store, &mut func_env),
456 None => {
457 let ld_library_path_owned;
459 let ld_library_path = {
460 let envs = func_env.data(&store).state.envs.lock().unwrap();
461 ld_library_path_owned = match envs
462 .iter()
463 .find_map(|env| env.strip_prefix(b"LD_LIBRARY_PATH="))
464 {
465 Some(path) => path
466 .split(|b| *b == b':')
467 .filter_map(|p| str::from_utf8(p).ok())
468 .map(PathBuf::from)
469 .collect::<Vec<_>>(),
470 None => vec![],
471 };
472 ld_library_path_owned
473 .iter()
474 .map(AsRef::as_ref)
475 .collect::<Vec<_>>()
476 };
477
478 Linker::new(
480 engine,
481 &module,
482 &mut store,
483 memory,
484 &mut func_env,
485 8 * 1024 * 1024,
486 &ld_library_path,
487 )
488 }
489 };
490
491 match linker {
492 Ok((_, linked_module)) => {
493 return Ok((linked_module.instance, func_env));
494 }
495 Err(e) => {
496 tracing::error!(
497 %pid,
498 error = &e as &dyn std::error::Error,
499 "Failed to link DL main module",
500 );
501 func_env
502 .data(&store)
503 .blocking_on_exit(Some(Errno::Noexec.into()));
504 return Err(WasiThreadError::LinkError(Arc::new(e)));
505 }
506 }
507 }
508
509 let mut import_object =
511 import_object_for_all_wasi_versions(&module, &mut store, &func_env.env);
512 if let Some(memory) = memory.clone() {
513 import_object.define("env", "memory", memory);
514 }
515 let runtime = func_env.data(&store).runtime.clone();
516 let additional_imports = runtime
517 .additional_imports(&module, &mut store)
518 .map_err(|err| WasiThreadError::AdditionalImportCreationFailed(Arc::new(err)))?;
519
520 for ((namespace, name), value) in &additional_imports {
521 if import_object.exists(&namespace, &name) {
523 tracing::warn!(
524 "Skipping duplicate additional import {}.{}",
525 namespace,
526 name
527 );
528 } else {
529 import_object.define(&namespace, &name, value);
530 }
531 }
532
533 let imported_memory = import_object
534 .get_export("env", "memory")
535 .and_then(|ext| match ext {
536 wasmer::Extern::Memory(memory) => Some(memory),
537 _ => None,
538 });
539
540 let instance = match Instance::new(&mut store, &module, &import_object) {
542 Ok(a) => a,
543 Err(err) => {
544 tracing::error!(
545 %pid,
546 error = &err as &dyn std::error::Error,
547 "Instantiation failed",
548 );
549 func_env
550 .data(&store)
551 .blocking_on_exit(Some(Errno::Noexec.into()));
552 return Err(WasiThreadError::InstanceCreateFailed(Box::new(err)));
553 }
554 };
555
556 runtime
557 .configure_new_instance(&module, &mut store, &instance, imported_memory.as_ref())
558 .map_err(|err| WasiThreadError::AdditionalImportCreationFailed(Arc::new(err)))?;
559
560 let handles = match imported_memory {
561 Some(memory) => WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
562 memory,
563 &store,
564 instance.clone(),
565 None,
566 )),
567 None => {
568 let exported_memory = instance
569 .exports
570 .iter()
571 .filter_map(|(_, export)| {
572 if let wasmer::Extern::Memory(memory) = export {
573 Some(memory.clone())
574 } else {
575 None
576 }
577 })
578 .next()
579 .ok_or(WasiThreadError::ExportError(ExportError::Missing(
580 "No imported or exported memory found".to_owned(),
581 )))?;
582 WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
583 exported_memory,
584 &store,
585 instance.clone(),
586 None,
587 ))
588 }
589 };
590
591 if let Err(err) = func_env.initialize_handles_and_layout(
593 &mut store,
594 instance.clone(),
595 handles,
596 None,
597 update_layout,
598 ) {
599 tracing::error!(
600 %pid,
601 error = &err as &dyn std::error::Error,
602 "Initialization failed",
603 );
604 func_env
605 .data(&store)
606 .blocking_on_exit(Some(Errno::Noexec.into()));
607 return Err(WasiThreadError::ExportError(err));
608 }
609
610 if call_initialize && let Ok(initialize) = instance.exports.get_function("_initialize") {
612 let initialize_result = initialize.call(&mut store, &[]);
613 if let Err(err) = initialize_result {
614 func_env
615 .data(&store)
616 .blocking_on_exit(Some(Errno::Noexec.into()));
617 return Err(WasiThreadError::InitFailed(Arc::new(anyhow::Error::from(
618 err,
619 ))));
620 }
621 }
622
623 Ok((instance, func_env))
624 }
625
626 pub fn runtime(&self) -> &(dyn Runtime + Send + Sync) {
628 self.runtime.deref()
629 }
630
631 pub fn tasks(&self) -> &Arc<dyn VirtualTaskManager> {
633 self.runtime.task_manager()
634 }
635
636 pub fn fs_root(&self) -> &WasiFsRoot {
637 &self.state.fs.root_fs
638 }
639
640 pub fn set_runtime<R>(&mut self, runtime: R)
642 where
643 R: Runtime + Send + Sync + 'static,
644 {
645 self.runtime = Arc::new(runtime);
646 }
647
648 pub fn active_threads(&self) -> u32 {
650 self.process.active_threads()
651 }
652
653 pub fn do_pending_operations(ctx: &mut FunctionEnvMut<'_, Self>) -> Result<(), WasiError> {
656 Self::do_pending_link_operations(ctx, true)?;
657 _ = Self::process_signals_and_exit(ctx)?;
658 Ok(())
659 }
660
661 pub fn do_pending_link_operations(
662 ctx: &mut FunctionEnvMut<'_, Self>,
663 fast: bool,
664 ) -> Result<(), WasiError> {
665 if let Some(linker) = ctx.data().inner().linker().cloned()
666 && let Err(e) = linker.do_pending_link_operations(ctx, fast)
667 {
668 tracing::warn!(err = ?e, "Failed to process pending link operations");
669 return Err(WasiError::Exit(Errno::Noexec.into()));
670 }
671 Ok(())
672 }
673
674 pub fn process_signals_and_exit(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
676 let env = ctx.data();
679 let env_inner = env
680 .try_inner()
681 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
682 let inner = env_inner.main_module_instance_handles();
683 if !inner.signal_set {
684 let signals = env.thread.pop_signals();
685 if !signals.is_empty() {
686 for sig in signals {
687 if sig == Signal::Sigint
688 || sig == Signal::Sigquit
689 || sig == Signal::Sigkill
690 || sig == Signal::Sigabrt
691 || sig == Signal::Sigpipe
692 {
693 let exit_code = env.thread.set_or_get_exit_code_for_signal(sig);
694 return Err(WasiError::Exit(exit_code));
695 } else {
696 tracing::trace!(pid=%env.pid(), ?sig, "Signal ignored");
697 }
698 }
699 return Ok(Ok(true));
700 }
701 }
702
703 if let Some(forced_exit) = env.should_exit() {
705 return Err(WasiError::Exit(forced_exit));
706 }
707
708 Self::process_signals(ctx)
709 }
710
711 pub(crate) fn process_signals(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
713 let env = ctx.data();
716 let env_inner = env
717 .try_inner()
718 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
719 let inner = env_inner.main_module_instance_handles();
720 if !inner.signal_set {
721 return Ok(Ok(false));
722 }
723
724 let ret = if inner.signal.as_ref().is_some() {
727 let signals = env.thread.pop_signals();
728 Self::process_signals_internal(ctx, signals)?
729 } else {
730 false
731 };
732
733 Ok(Ok(ret))
734 }
735
736 pub(crate) fn process_signals_internal(
737 ctx: &mut FunctionEnvMut<'_, Self>,
738 mut signals: Vec<Signal>,
739 ) -> Result<bool, WasiError> {
740 let env = ctx.data();
741 let env_inner = env
742 .try_inner()
743 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
744 let inner = env_inner.main_module_instance_handles();
745 if let Some(handler) = inner.signal.clone() {
746 let mut now = 0;
748 {
749 let mut has_signal_interval = false;
750 let mut inner = env.process.inner.0.lock().unwrap();
751 if !inner.signal_intervals.is_empty() {
752 now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap()
753 as u128;
754 for signal in inner.signal_intervals.values() {
755 let elapsed = now - signal.last_signal;
756 if elapsed >= signal.interval.as_nanos() {
757 has_signal_interval = true;
758 break;
759 }
760 }
761 }
762 if has_signal_interval {
763 for signal in inner.signal_intervals.values_mut() {
764 let elapsed = now - signal.last_signal;
765 if elapsed >= signal.interval.as_nanos() {
766 signal.last_signal = now;
767 signals.push(signal.signal);
768 }
769 }
770 }
771 }
772
773 for signal in signals {
774 if matches!(signal, Signal::Sigwakeup) {
776 continue;
777 }
778
779 tracing::trace!(
780 pid=%ctx.data().pid(),
781 ?signal,
782 "processing signal via handler",
783 );
784 if let Err(err) = handler.call(ctx, signal as i32) {
785 match err.downcast::<WasiError>() {
786 Ok(wasi_err) => {
787 tracing::warn!(
788 pid=%ctx.data().pid(),
789 wasi_err=&wasi_err as &dyn std::error::Error,
790 "signal handler wasi error",
791 );
792 return Err(wasi_err);
793 }
794 Err(runtime_err) => {
795 if signal != Signal::Sigkill {
798 tracing::warn!(
799 pid=%ctx.data().pid(),
800 runtime_err=&runtime_err as &dyn std::error::Error,
801 "signal handler runtime error",
802 );
803 }
804 return Err(WasiError::Exit(Errno::Intr.into()));
805 }
806 }
807 }
808 tracing::trace!(
809 pid=%ctx.data().pid(),
810 "signal processed",
811 );
812 }
813 Ok(true)
814 } else {
815 tracing::trace!("no signal handler");
816 Ok(false)
817 }
818 }
819
820 pub fn should_exit(&self) -> Option<ExitCode> {
822 if let Some(forced_exit) = self.thread.try_join() {
824 return Some(forced_exit.unwrap_or_else(|err| {
825 tracing::debug!(
826 error = &*err as &dyn std::error::Error,
827 "exit runtime error",
828 );
829 Errno::Child.into()
830 }));
831 }
832 if let Some(forced_exit) = self.process.try_join() {
833 return Some(forced_exit.unwrap_or_else(|err| {
834 tracing::debug!(
835 error = &*err as &dyn std::error::Error,
836 "exit runtime error",
837 );
838 Errno::Child.into()
839 }));
840 }
841 None
842 }
843
844 pub fn net(&self) -> &DynVirtualNetworking {
846 self.runtime.networking()
847 }
848
849 pub(crate) fn inner(&self) -> WasiInstanceGuard<'_> {
852 self.inner.get().expect(
853 "You must initialize the WasiEnv before using it and can not pass it between threads",
854 )
855 }
856
857 pub(crate) fn inner_mut(&mut self) -> WasiInstanceGuardMut<'_> {
860 self.inner.get_mut().expect(
861 "You must initialize the WasiEnv before using it and can not pass it between threads",
862 )
863 }
864
865 pub(crate) fn try_inner(&self) -> Option<WasiInstanceGuard<'_>> {
867 self.inner.get()
868 }
869
870 #[allow(dead_code)]
873 pub(crate) fn try_inner_mut(&mut self) -> Option<WasiInstanceGuardMut<'_>> {
874 self.inner.get_mut()
875 }
876
877 #[doc(hidden)]
881 pub(crate) fn set_inner(&mut self, handles: WasiModuleTreeHandles) {
882 self.inner.set(handles);
883 self.refresh_deep_sleep_capability();
884 }
885
886 #[doc(hidden)]
890 pub(crate) fn swap_inner(&mut self, other: &mut Self) {
891 std::mem::swap(&mut self.inner, &mut other.inner);
892 }
893
894 pub(crate) fn ensure_static_module(&self) -> Result<(), ()> {
898 self.inner.get().unwrap().ensure_static_module()
899 }
900
901 pub fn try_clone_instance(&self) -> Option<Instance> {
904 let guard = self.inner.get();
905 match guard {
906 Some(guard) => guard
907 .static_module_instance_handles()
908 .map(|instance| instance.instance.clone()),
909 None => None,
910 }
911 }
912
913 pub fn try_memory(&self) -> Option<WasiInstanceGuardMemory<'_>> {
916 self.try_inner().map(|i| i.memory())
917 }
918
919 pub unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> {
926 self.try_memory().expect(
927 "You must initialize the WasiEnv before using it and can not pass it between threads",
928 )
929 }
930
931 pub fn try_memory_view<'a>(
934 &self,
935 store: &'a (impl AsStoreRef + ?Sized),
936 ) -> Option<MemoryView<'a>> {
937 self.try_memory().map(|m| m.view(store))
938 }
939
940 pub unsafe fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
947 self.try_memory_view(store).expect(
948 "You must initialize the WasiEnv before using it and can not pass it between threads",
949 )
950 }
951
952 #[allow(dead_code)]
955 pub(crate) fn try_memory_clone(&self) -> Option<Memory> {
956 self.try_inner()
957 .map(|i| i.main_module_instance_handles().memory_clone())
958 }
959
960 pub(crate) fn state(&self) -> &WasiState {
962 &self.state
963 }
964
965 pub fn stdout(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
967 self.state.stdout()
968 }
969
970 pub fn stderr(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
972 self.state.stderr()
973 }
974
975 pub fn stdin(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
977 self.state.stdin()
978 }
979
980 pub fn should_journal(&self) -> bool {
982 self.enable_journal && !self.replaying_journal
983 }
984
985 #[cfg(feature = "journal")]
987 pub fn has_active_journal(&self) -> bool {
988 self.runtime().active_journal().is_some()
989 }
990
991 #[cfg(feature = "journal")]
993 pub fn active_journal(&self) -> Result<&DynJournal, Errno> {
994 self.runtime().active_journal().ok_or_else(|| {
995 tracing::debug!("failed to save thread exit as there is not active journal");
996 Errno::Fault
997 })
998 }
999
1000 #[cfg(feature = "journal")]
1002 pub fn has_snapshot_trigger(&self, trigger: SnapshotTrigger) -> bool {
1003 let guard = self.process.inner.0.lock().unwrap();
1004 guard.snapshot_on.contains(&trigger)
1005 }
1006
1007 #[cfg(feature = "journal")]
1009 pub fn pop_snapshot_trigger(&mut self, trigger: SnapshotTrigger) -> bool {
1010 let mut guard = self.process.inner.0.lock().unwrap();
1011 if trigger.only_once() {
1012 guard.snapshot_on.remove(&trigger)
1013 } else {
1014 guard.snapshot_on.contains(&trigger)
1015 }
1016 }
1017
1018 pub fn std_dev_get(
1021 &self,
1022 fd: crate::syscalls::WasiFd,
1023 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1024 self.state.std_dev_get(fd)
1025 }
1026
1027 pub(crate) unsafe fn get_memory_and_wasi_state<'a>(
1033 &'a self,
1034 store: &'a impl AsStoreRef,
1035 _mem_index: u32,
1036 ) -> (MemoryView<'a>, &'a WasiState) {
1037 let memory = unsafe { self.memory_view(store) };
1038 let state = self.state.deref();
1039 (memory, state)
1040 }
1041
1042 pub(crate) unsafe fn get_memory_and_wasi_state_and_inodes<'a>(
1048 &'a self,
1049 store: &'a impl AsStoreRef,
1050 _mem_index: u32,
1051 ) -> (MemoryView<'a>, &'a WasiState, &'a WasiInodes) {
1052 let memory = unsafe { self.memory_view(store) };
1053 let state = self.state.deref();
1054 let inodes = &state.inodes;
1055 (memory, state, inodes)
1056 }
1057
1058 pub(crate) fn get_wasi_state_and_inodes(&self) -> (&WasiState, &WasiInodes) {
1059 let state = self.state.deref();
1060 let inodes = &state.inodes;
1061 (state, inodes)
1062 }
1063
1064 pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
1065 block_on(self.use_package_async(pkg))
1066 }
1067
1068 pub async fn use_package_async(
1080 &self,
1081 pkg: &BinaryPackage,
1082 ) -> Result<(), WasiStateCreationError> {
1083 tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment");
1084 let root_fs = &self.state.fs.root_fs;
1085
1086 if let Err(e) = self.state.fs.conditional_union(pkg).await {
1089 tracing::warn!(
1090 error = &e as &dyn std::error::Error,
1091 "Unable to merge the package's filesystem into the main one",
1092 );
1093 }
1094
1095 if !pkg.commands.is_empty() {
1098 let _ = root_fs.create_dir(Path::new("/bin"));
1099 let _ = root_fs.create_dir(Path::new("/usr"));
1100 let _ = root_fs.create_dir(Path::new("/usr/bin"));
1101
1102 for command in &pkg.commands {
1103 let path = format!("/bin/{}", command.name());
1104 let path2 = format!("/usr/bin/{}", command.name());
1105 let path = Path::new(path.as_str());
1106 let path2 = Path::new(path2.as_str());
1107
1108 let atom = command.atom();
1109
1110 match root_fs {
1111 WasiFsRoot::Sandbox(root_fs) => {
1112 if let Err(err) = root_fs
1113 .new_open_options_ext()
1114 .insert_ro_file(path, atom.clone())
1115 {
1116 tracing::debug!(
1117 "failed to add package [{}] command [{}] - {}",
1118 pkg.id,
1119 command.name(),
1120 err
1121 );
1122 continue;
1123 }
1124 if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path2, atom)
1125 {
1126 tracing::debug!(
1127 "failed to add package [{}] command [{}] - {}",
1128 pkg.id,
1129 command.name(),
1130 err
1131 );
1132 continue;
1133 }
1134 }
1135 WasiFsRoot::Overlay(ofs) => {
1136 let root_fs = ofs.primary();
1137
1138 if let Err(err) = root_fs
1139 .new_open_options_ext()
1140 .insert_ro_file(path, atom.clone())
1141 {
1142 tracing::debug!(
1143 "failed to add package [{}] command [{}] - {}",
1144 pkg.id,
1145 command.name(),
1146 err
1147 );
1148 continue;
1149 }
1150 if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path2, atom)
1151 {
1152 tracing::debug!(
1153 "failed to add package [{}] command [{}] - {}",
1154 pkg.id,
1155 command.name(),
1156 err
1157 );
1158 continue;
1159 }
1160 }
1161 WasiFsRoot::Backing(fs) => {
1162 let mut f = fs.new_open_options().create(true).write(true).open(path)?;
1165 if let Err(e) = f.copy_from_owned_buffer(&atom).await {
1166 tracing::warn!(
1167 error = &e as &dyn std::error::Error,
1168 "Unable to copy file reference",
1169 );
1170 }
1171 let mut f = fs.new_open_options().create(true).write(true).open(path2)?;
1172 if let Err(e) = f.copy_from_owned_buffer(&atom).await {
1173 tracing::warn!(
1174 error = &e as &dyn std::error::Error,
1175 "Unable to copy file reference",
1176 );
1177 }
1178 }
1179 }
1180
1181 let mut package = pkg.clone();
1182 package.entrypoint_cmd = Some(command.name().to_string());
1183 let package_arc = Arc::new(package);
1184 self.bin_factory
1185 .set_binary(path.to_string_lossy().as_ref(), &package_arc);
1186 self.bin_factory
1187 .set_binary(path2.to_string_lossy().as_ref(), &package_arc);
1188
1189 tracing::debug!(
1190 package=%pkg.id,
1191 command_name=command.name(),
1192 path=%path.display(),
1193 "Injected a command into the filesystem",
1194 );
1195 }
1196 }
1197
1198 Ok(())
1199 }
1200
1201 pub fn uses<I>(&self, uses: I) -> Result<(), WasiStateCreationError>
1204 where
1205 I: IntoIterator<Item = String>,
1206 {
1207 let rt = self.runtime();
1208
1209 for package_name in uses {
1210 let specifier = package_name.parse::<PackageSource>().map_err(|e| {
1211 WasiStateCreationError::WasiIncludePackageError(format!(
1212 "package_name={package_name}, {e}",
1213 ))
1214 })?;
1215 let pkg = block_on(BinaryPackage::from_registry(&specifier, rt)).map_err(|e| {
1216 WasiStateCreationError::WasiIncludePackageError(format!(
1217 "package_name={package_name}, {e}",
1218 ))
1219 })?;
1220 self.use_package(&pkg)?;
1221 }
1222
1223 Ok(())
1224 }
1225
1226 #[cfg(feature = "sys")]
1227 pub fn map_commands(
1228 &self,
1229 map_commands: std::collections::HashMap<String, std::path::PathBuf>,
1230 ) -> Result<(), WasiStateCreationError> {
1231 #[allow(unused_imports)]
1233 use std::path::Path;
1234
1235 use shared_buffer::OwnedBuffer;
1236 #[allow(unused_imports)]
1237 use virtual_fs::FileSystem;
1238
1239 #[cfg(feature = "sys")]
1240 for (command, target) in map_commands.iter() {
1241 let file = std::fs::read(target).map_err(|err| {
1243 WasiStateCreationError::WasiInheritError(format!(
1244 "failed to read local binary [{}] - {}",
1245 target.as_os_str().to_string_lossy(),
1246 err
1247 ))
1248 })?;
1249 let file = OwnedBuffer::from(file);
1250
1251 if let WasiFsRoot::Sandbox(root_fs) = &self.state.fs.root_fs {
1252 let _ = root_fs.create_dir(Path::new("/bin"));
1253 let _ = root_fs.create_dir(Path::new("/usr"));
1254 let _ = root_fs.create_dir(Path::new("/usr/bin"));
1255
1256 let path = format!("/bin/{command}");
1257 let path = Path::new(path.as_str());
1258 if let Err(err) = root_fs
1259 .new_open_options_ext()
1260 .insert_ro_file(path, file.clone())
1261 {
1262 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1263 continue;
1264 }
1265 let path = format!("/usr/bin/{command}");
1266 let path = Path::new(path.as_str());
1267 if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path, file) {
1268 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1269 continue;
1270 }
1271 } else {
1272 tracing::debug!(
1273 "failed to add atom command [{}] to the root file system as it is not sandboxed",
1274 command
1275 );
1276 continue;
1277 }
1278 }
1279 Ok(())
1280 }
1281
1282 #[allow(clippy::await_holding_lock)]
1284 pub fn blocking_on_exit(&self, process_exit_code: Option<ExitCode>) {
1285 let cleanup = self.on_exit(process_exit_code);
1286 block_on(cleanup);
1287 }
1288
1289 #[allow(clippy::await_holding_lock)]
1291 pub fn on_exit(&self, process_exit_code: Option<ExitCode>) -> BoxFuture<'static, ()> {
1292 const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
1293
1294 #[cfg(feature = "journal")]
1296 if self.should_journal() && self.has_active_journal() {
1297 if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), process_exit_code)
1298 {
1299 tracing::warn!("failed to save snapshot event for thread exit - {}", err);
1300 }
1301
1302 if self.thread.is_main()
1303 && let Err(err) = JournalEffector::save_process_exit(self, process_exit_code)
1304 {
1305 tracing::warn!("failed to save snapshot event for process exit - {}", err);
1306 }
1307 }
1308
1309 if let Some(process_exit_code) = process_exit_code {
1311 let process = self.process.clone();
1312 let disable_fs_cleanup = self.disable_fs_cleanup;
1313 let pid = self.pid();
1314
1315 let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT);
1316 let state = self.state.clone();
1317 Box::pin(async move {
1318 if !disable_fs_cleanup {
1319 tracing::trace!(pid = %pid, "cleaning up open file handles");
1320
1321 tokio::select! {
1323 _ = timeout => {
1324 tracing::debug!(
1325 "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}"
1326 );
1327 },
1328 _ = state.fs.close_all() => { }
1329 }
1330
1331 process.signal_process(Signal::Sigquit);
1333 }
1334
1335 process.terminate(process_exit_code);
1337 })
1338 } else {
1339 Box::pin(async {})
1340 }
1341 }
1342
1343 pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) {
1344 if let Ok(Some(Wasi {
1345 main_args,
1346 env: env_vars,
1347 exec_name,
1348 ..
1349 })) = cmd.metadata().wasi()
1350 {
1351 if let Some(env_vars) = env_vars {
1352 let env_vars = env_vars
1353 .into_iter()
1354 .map(|env_var| {
1355 let (k, v) = env_var.split_once('=').unwrap();
1356
1357 (k.to_string(), v.as_bytes().to_vec())
1358 })
1359 .collect::<Vec<_>>();
1360
1361 let env_vars = conv_env_vars(env_vars);
1362
1363 self.state
1364 .envs
1365 .lock()
1366 .unwrap()
1367 .extend_from_slice(env_vars.as_slice());
1368 }
1369
1370 if let Some(main_args) = main_args {
1371 let mut args: std::sync::MutexGuard<'_, Vec<String>> =
1372 self.state.args.lock().unwrap();
1373 args.splice(1..1, main_args);
1375 }
1376
1377 if let Some(exec_name) = exec_name {
1378 self.state.args.lock().unwrap()[0] = exec_name;
1379 }
1380 }
1381 }
1382}