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