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::Rng;
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::thread_rng().r#gen::<[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 if !self.control_plane.config().enable_asynchronous_threading {
336 return false;
337 }
338 self.inner()
339 .static_module_instance_handles()
340 .map(|handles| {
341 handles.asyncify_get_state.is_some()
342 && handles.asyncify_start_rewind.is_some()
343 && handles.asyncify_start_unwind.is_some()
344 })
345 .unwrap_or(false)
346 }
347
348 pub fn layout(&self) -> &WasiMemoryLayout {
350 &self.layout
351 }
352
353 #[allow(clippy::result_large_err)]
354 pub(crate) fn from_init(
355 init: WasiEnvInit,
356 module_hash: ModuleHash,
357 ) -> Result<Self, WasiRuntimeError> {
358 let process = if let Some(p) = init.process {
359 p
360 } else {
361 init.control_plane.new_process(module_hash)?
362 };
363
364 #[cfg(feature = "journal")]
365 {
366 let mut guard = process.inner.0.lock().unwrap();
367 guard.snapshot_on = init.snapshot_on.into_iter().collect();
368 guard.stop_running_after_checkpoint = init.stop_running_after_snapshot;
369 }
370
371 let layout = WasiMemoryLayout::default();
372 let thread = if let Some(t) = init.thread {
373 t
374 } else {
375 process.new_thread(layout.clone(), ThreadStartType::MainThread)?
376 };
377
378 let mut env = Self {
379 control_plane: init.control_plane,
380 process,
381 thread: thread.as_thread(),
382 layout,
383 vfork: None,
384 poll_seed: 0,
385 state: Arc::new(init.state),
386 inner: Default::default(),
387 owned_handles: Vec::new(),
388 #[cfg(feature = "journal")]
389 enable_journal: init.runtime.active_journal().is_some(),
390 #[cfg(not(feature = "journal"))]
391 enable_journal: false,
392 replaying_journal: false,
393 skip_stdio_during_bootstrap: init.skip_stdio_during_bootstrap,
394 enable_deep_sleep: init.capabilities.threading.enable_asynchronous_threading,
395 enable_exponential_cpu_backoff: init
396 .capabilities
397 .threading
398 .enable_exponential_cpu_backoff,
399 runtime: init.runtime,
400 bin_factory: init.bin_factory,
401 capabilities: init.capabilities,
402 disable_fs_cleanup: false,
403 context_switching_environment: None,
404 };
405 env.owned_handles.push(thread);
406
407 for pkg in &init.webc_dependencies {
409 env.use_package(pkg)?;
410 }
411
412 #[cfg(feature = "sys")]
413 env.map_commands(init.mapped_commands.clone())?;
414
415 Ok(env)
416 }
417
418 #[allow(clippy::result_large_err)]
420 pub(crate) fn instantiate(
421 self,
422 module: Module,
423 store: &mut impl AsStoreMut,
424 memory: Option<Memory>,
425 update_layout: bool,
426 call_initialize: bool,
427 parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut<WasiEnv>)>,
428 ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> {
429 let pid = self.process.pid();
430
431 let mut store = store.as_store_mut();
432 let engine = self.runtime().engine();
433 let mut func_env = WasiFunctionEnv::new(&mut store, self);
434
435 let is_dl = super::linker::is_dynamically_linked(&module);
436 if is_dl {
437 let linker = match parent_linker_and_ctx {
438 Some((linker, ctx)) => linker.create_instance_group(ctx, &mut store, &mut func_env),
439 None => {
440 let ld_library_path_owned;
442 let ld_library_path = {
443 let envs = func_env.data(&store).state.envs.lock().unwrap();
444 ld_library_path_owned = match envs
445 .iter()
446 .find_map(|env| env.strip_prefix(b"LD_LIBRARY_PATH="))
447 {
448 Some(path) => path
449 .split(|b| *b == b':')
450 .filter_map(|p| str::from_utf8(p).ok())
451 .map(PathBuf::from)
452 .collect::<Vec<_>>(),
453 None => vec![],
454 };
455 ld_library_path_owned
456 .iter()
457 .map(AsRef::as_ref)
458 .collect::<Vec<_>>()
459 };
460
461 Linker::new(
463 engine,
464 &module,
465 &mut store,
466 memory,
467 &mut func_env,
468 8 * 1024 * 1024,
469 &ld_library_path,
470 )
471 }
472 };
473
474 match linker {
475 Ok((_, linked_module)) => {
476 return Ok((linked_module.instance, func_env));
477 }
478 Err(e) => {
479 tracing::error!(
480 %pid,
481 error = &e as &dyn std::error::Error,
482 "Failed to link DL main module",
483 );
484 func_env
485 .data(&store)
486 .blocking_on_exit(Some(Errno::Noexec.into()));
487 return Err(WasiThreadError::LinkError(Arc::new(e)));
488 }
489 }
490 }
491
492 let mut import_object =
494 import_object_for_all_wasi_versions(&module, &mut store, &func_env.env);
495
496 let imported_memory = if let Some(memory) = memory {
497 import_object.define("env", "memory", memory.clone());
498 Some(memory)
499 } else {
500 None
501 };
502
503 let instance = match Instance::new(&mut store, &module, &import_object) {
505 Ok(a) => a,
506 Err(err) => {
507 tracing::error!(
508 %pid,
509 error = &err as &dyn std::error::Error,
510 "Instantiation failed",
511 );
512 func_env
513 .data(&store)
514 .blocking_on_exit(Some(Errno::Noexec.into()));
515 return Err(WasiThreadError::InstanceCreateFailed(Box::new(err)));
516 }
517 };
518
519 let handles = match imported_memory {
520 Some(memory) => WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
521 memory,
522 &store,
523 instance.clone(),
524 None,
525 )),
526 None => {
527 let exported_memory = instance
528 .exports
529 .iter()
530 .filter_map(|(_, export)| {
531 if let wasmer::Extern::Memory(memory) = export {
532 Some(memory.clone())
533 } else {
534 None
535 }
536 })
537 .next()
538 .ok_or(WasiThreadError::ExportError(ExportError::Missing(
539 "No imported or exported memory found".to_owned(),
540 )))?;
541 WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
542 exported_memory,
543 &store,
544 instance.clone(),
545 None,
546 ))
547 }
548 };
549
550 if let Err(err) = func_env.initialize_handles_and_layout(
552 &mut store,
553 instance.clone(),
554 handles,
555 None,
556 update_layout,
557 ) {
558 tracing::error!(
559 %pid,
560 error = &err as &dyn std::error::Error,
561 "Initialization failed",
562 );
563 func_env
564 .data(&store)
565 .blocking_on_exit(Some(Errno::Noexec.into()));
566 return Err(WasiThreadError::ExportError(err));
567 }
568
569 if call_initialize && let Ok(initialize) = instance.exports.get_function("_initialize") {
571 let initialize_result = initialize.call(&mut store, &[]);
572 if let Err(err) = initialize_result {
573 func_env
574 .data(&store)
575 .blocking_on_exit(Some(Errno::Noexec.into()));
576 return Err(WasiThreadError::InitFailed(Arc::new(anyhow::Error::from(
577 err,
578 ))));
579 }
580 }
581
582 Ok((instance, func_env))
583 }
584
585 pub fn runtime(&self) -> &(dyn Runtime + Send + Sync) {
587 self.runtime.deref()
588 }
589
590 pub fn tasks(&self) -> &Arc<dyn VirtualTaskManager> {
592 self.runtime.task_manager()
593 }
594
595 pub fn fs_root(&self) -> &WasiFsRoot {
596 &self.state.fs.root_fs
597 }
598
599 pub fn set_runtime<R>(&mut self, runtime: R)
601 where
602 R: Runtime + Send + Sync + 'static,
603 {
604 self.runtime = Arc::new(runtime);
605 }
606
607 pub fn active_threads(&self) -> u32 {
609 self.process.active_threads()
610 }
611
612 pub fn do_pending_operations(ctx: &mut FunctionEnvMut<'_, Self>) -> Result<(), WasiError> {
615 Self::do_pending_link_operations(ctx, true)?;
616 _ = Self::process_signals_and_exit(ctx)?;
617 Ok(())
618 }
619
620 pub fn do_pending_link_operations(
621 ctx: &mut FunctionEnvMut<'_, Self>,
622 fast: bool,
623 ) -> Result<(), WasiError> {
624 if let Some(linker) = ctx.data().inner().linker().cloned()
625 && let Err(e) = linker.do_pending_link_operations(ctx, fast)
626 {
627 tracing::warn!(err = ?e, "Failed to process pending link operations");
628 return Err(WasiError::Exit(Errno::Noexec.into()));
629 }
630 Ok(())
631 }
632
633 pub fn process_signals_and_exit(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
635 let env = ctx.data();
638 let env_inner = env
639 .try_inner()
640 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
641 let inner = env_inner.main_module_instance_handles();
642 if !inner.signal_set {
643 let signals = env.thread.pop_signals();
644 if !signals.is_empty() {
645 for sig in signals {
646 if sig == Signal::Sigint
647 || sig == Signal::Sigquit
648 || sig == Signal::Sigkill
649 || sig == Signal::Sigabrt
650 || sig == Signal::Sigpipe
651 {
652 let exit_code = env.thread.set_or_get_exit_code_for_signal(sig);
653 return Err(WasiError::Exit(exit_code));
654 } else {
655 tracing::trace!(pid=%env.pid(), ?sig, "Signal ignored");
656 }
657 }
658 return Ok(Ok(true));
659 }
660 }
661
662 if let Some(forced_exit) = env.should_exit() {
664 return Err(WasiError::Exit(forced_exit));
665 }
666
667 Self::process_signals(ctx)
668 }
669
670 pub(crate) fn process_signals(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
672 let env = ctx.data();
675 let env_inner = env
676 .try_inner()
677 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
678 let inner = env_inner.main_module_instance_handles();
679 if !inner.signal_set {
680 return Ok(Ok(false));
681 }
682
683 let ret = if inner.signal.as_ref().is_some() {
686 let signals = env.thread.pop_signals();
687 Self::process_signals_internal(ctx, signals)?
688 } else {
689 false
690 };
691
692 Ok(Ok(ret))
693 }
694
695 pub(crate) fn process_signals_internal(
696 ctx: &mut FunctionEnvMut<'_, Self>,
697 mut signals: Vec<Signal>,
698 ) -> Result<bool, WasiError> {
699 let env = ctx.data();
700 let env_inner = env
701 .try_inner()
702 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
703 let inner = env_inner.main_module_instance_handles();
704 if let Some(handler) = inner.signal.clone() {
705 let mut now = 0;
707 {
708 let mut has_signal_interval = false;
709 let inner = env.process.inner.0.lock().unwrap();
710 if !inner.signal_intervals.is_empty() {
711 now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap()
712 as u128;
713 for signal in inner.signal_intervals.values() {
714 let elapsed = now - signal.last_signal;
715 if elapsed >= signal.interval.as_nanos() {
716 has_signal_interval = true;
717 break;
718 }
719 }
720 }
721 if has_signal_interval {
722 let mut inner = env.process.inner.0.lock().unwrap();
723 for signal in inner.signal_intervals.values_mut() {
724 let elapsed = now - signal.last_signal;
725 if elapsed >= signal.interval.as_nanos() {
726 signal.last_signal = now;
727 signals.push(signal.signal);
728 }
729 }
730 }
731 }
732
733 for signal in signals {
734 if matches!(signal, Signal::Sigwakeup) {
736 continue;
737 }
738
739 tracing::trace!(
740 pid=%ctx.data().pid(),
741 ?signal,
742 "processing signal via handler",
743 );
744 if let Err(err) = handler.call(ctx, signal as i32) {
745 match err.downcast::<WasiError>() {
746 Ok(wasi_err) => {
747 tracing::warn!(
748 pid=%ctx.data().pid(),
749 wasi_err=&wasi_err as &dyn std::error::Error,
750 "signal handler wasi error",
751 );
752 return Err(wasi_err);
753 }
754 Err(runtime_err) => {
755 if signal != Signal::Sigkill {
758 tracing::warn!(
759 pid=%ctx.data().pid(),
760 runtime_err=&runtime_err as &dyn std::error::Error,
761 "signal handler runtime error",
762 );
763 }
764 return Err(WasiError::Exit(Errno::Intr.into()));
765 }
766 }
767 }
768 tracing::trace!(
769 pid=%ctx.data().pid(),
770 "signal processed",
771 );
772 }
773 Ok(true)
774 } else {
775 tracing::trace!("no signal handler");
776 Ok(false)
777 }
778 }
779
780 pub fn should_exit(&self) -> Option<ExitCode> {
782 if let Some(forced_exit) = self.thread.try_join() {
784 return Some(forced_exit.unwrap_or_else(|err| {
785 tracing::debug!(
786 error = &*err as &dyn std::error::Error,
787 "exit runtime error",
788 );
789 Errno::Child.into()
790 }));
791 }
792 if let Some(forced_exit) = self.process.try_join() {
793 return Some(forced_exit.unwrap_or_else(|err| {
794 tracing::debug!(
795 error = &*err as &dyn std::error::Error,
796 "exit runtime error",
797 );
798 Errno::Child.into()
799 }));
800 }
801 None
802 }
803
804 pub fn net(&self) -> &DynVirtualNetworking {
806 self.runtime.networking()
807 }
808
809 pub(crate) fn inner(&self) -> WasiInstanceGuard<'_> {
812 self.inner.get().expect(
813 "You must initialize the WasiEnv before using it and can not pass it between threads",
814 )
815 }
816
817 pub(crate) fn inner_mut(&mut self) -> WasiInstanceGuardMut<'_> {
820 self.inner.get_mut().expect(
821 "You must initialize the WasiEnv before using it and can not pass it between threads",
822 )
823 }
824
825 pub(crate) fn try_inner(&self) -> Option<WasiInstanceGuard<'_>> {
827 self.inner.get()
828 }
829
830 #[allow(dead_code)]
833 pub(crate) fn try_inner_mut(&mut self) -> Option<WasiInstanceGuardMut<'_>> {
834 self.inner.get_mut()
835 }
836
837 #[doc(hidden)]
841 pub(crate) fn set_inner(&mut self, handles: WasiModuleTreeHandles) {
842 self.inner.set(handles)
843 }
844
845 #[doc(hidden)]
849 pub(crate) fn swap_inner(&mut self, other: &mut Self) {
850 std::mem::swap(&mut self.inner, &mut other.inner);
851 }
852
853 pub(crate) fn ensure_static_module(&self) -> Result<(), ()> {
857 self.inner.get().unwrap().ensure_static_module()
858 }
859
860 pub fn try_clone_instance(&self) -> Option<Instance> {
863 let guard = self.inner.get();
864 match guard {
865 Some(guard) => guard
866 .static_module_instance_handles()
867 .map(|instance| instance.instance.clone()),
868 None => None,
869 }
870 }
871
872 pub fn try_memory(&self) -> Option<WasiInstanceGuardMemory<'_>> {
875 self.try_inner().map(|i| i.memory())
876 }
877
878 pub unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> {
885 self.try_memory().expect(
886 "You must initialize the WasiEnv before using it and can not pass it between threads",
887 )
888 }
889
890 pub fn try_memory_view<'a>(
893 &self,
894 store: &'a (impl AsStoreRef + ?Sized),
895 ) -> Option<MemoryView<'a>> {
896 self.try_memory().map(|m| m.view(store))
897 }
898
899 pub unsafe fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
906 self.try_memory_view(store).expect(
907 "You must initialize the WasiEnv before using it and can not pass it between threads",
908 )
909 }
910
911 #[allow(dead_code)]
914 pub(crate) fn try_memory_clone(&self) -> Option<Memory> {
915 self.try_inner()
916 .map(|i| i.main_module_instance_handles().memory_clone())
917 }
918
919 pub(crate) fn state(&self) -> &WasiState {
921 &self.state
922 }
923
924 pub fn stdout(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
926 self.state.stdout()
927 }
928
929 pub fn stderr(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
931 self.state.stderr()
932 }
933
934 pub fn stdin(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
936 self.state.stdin()
937 }
938
939 pub fn should_journal(&self) -> bool {
941 self.enable_journal && !self.replaying_journal
942 }
943
944 #[cfg(feature = "journal")]
946 pub fn has_active_journal(&self) -> bool {
947 self.runtime().active_journal().is_some()
948 }
949
950 #[cfg(feature = "journal")]
952 pub fn active_journal(&self) -> Result<&DynJournal, Errno> {
953 self.runtime().active_journal().ok_or_else(|| {
954 tracing::debug!("failed to save thread exit as there is not active journal");
955 Errno::Fault
956 })
957 }
958
959 #[cfg(feature = "journal")]
961 pub fn has_snapshot_trigger(&self, trigger: SnapshotTrigger) -> bool {
962 let guard = self.process.inner.0.lock().unwrap();
963 guard.snapshot_on.contains(&trigger)
964 }
965
966 #[cfg(feature = "journal")]
968 pub fn pop_snapshot_trigger(&mut self, trigger: SnapshotTrigger) -> bool {
969 let mut guard = self.process.inner.0.lock().unwrap();
970 if trigger.only_once() {
971 guard.snapshot_on.remove(&trigger)
972 } else {
973 guard.snapshot_on.contains(&trigger)
974 }
975 }
976
977 pub fn std_dev_get(
980 &self,
981 fd: crate::syscalls::WasiFd,
982 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
983 self.state.std_dev_get(fd)
984 }
985
986 pub(crate) unsafe fn get_memory_and_wasi_state<'a>(
992 &'a self,
993 store: &'a impl AsStoreRef,
994 _mem_index: u32,
995 ) -> (MemoryView<'a>, &'a WasiState) {
996 let memory = unsafe { self.memory_view(store) };
997 let state = self.state.deref();
998 (memory, state)
999 }
1000
1001 pub(crate) unsafe fn get_memory_and_wasi_state_and_inodes<'a>(
1007 &'a self,
1008 store: &'a impl AsStoreRef,
1009 _mem_index: u32,
1010 ) -> (MemoryView<'a>, &'a WasiState, &'a WasiInodes) {
1011 let memory = unsafe { self.memory_view(store) };
1012 let state = self.state.deref();
1013 let inodes = &state.inodes;
1014 (memory, state, inodes)
1015 }
1016
1017 pub(crate) fn get_wasi_state_and_inodes(&self) -> (&WasiState, &WasiInodes) {
1018 let state = self.state.deref();
1019 let inodes = &state.inodes;
1020 (state, inodes)
1021 }
1022
1023 pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
1024 block_on(self.use_package_async(pkg))
1025 }
1026
1027 pub async fn use_package_async(
1039 &self,
1040 pkg: &BinaryPackage,
1041 ) -> Result<(), WasiStateCreationError> {
1042 tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment");
1043 let root_fs = &self.state.fs.root_fs;
1044
1045 if let Err(e) = self.state.fs.conditional_union(pkg).await {
1048 tracing::warn!(
1049 error = &e as &dyn std::error::Error,
1050 "Unable to merge the package's filesystem into the main one",
1051 );
1052 }
1053
1054 if !pkg.commands.is_empty() {
1057 let _ = root_fs.create_dir(Path::new("/bin"));
1058 let _ = root_fs.create_dir(Path::new("/usr"));
1059 let _ = root_fs.create_dir(Path::new("/usr/bin"));
1060
1061 for command in &pkg.commands {
1062 let path = format!("/bin/{}", command.name());
1063 let path2 = format!("/usr/bin/{}", command.name());
1064 let path = Path::new(path.as_str());
1065 let path2 = Path::new(path2.as_str());
1066
1067 let atom = command.atom();
1068
1069 match root_fs {
1070 WasiFsRoot::Sandbox(root_fs) => {
1071 if let Err(err) = root_fs
1072 .new_open_options_ext()
1073 .insert_ro_file(path, atom.clone())
1074 {
1075 tracing::debug!(
1076 "failed to add package [{}] command [{}] - {}",
1077 pkg.id,
1078 command.name(),
1079 err
1080 );
1081 continue;
1082 }
1083 if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path2, atom)
1084 {
1085 tracing::debug!(
1086 "failed to add package [{}] command [{}] - {}",
1087 pkg.id,
1088 command.name(),
1089 err
1090 );
1091 continue;
1092 }
1093 }
1094 WasiFsRoot::Overlay(ofs) => {
1095 let root_fs = ofs.primary();
1096
1097 if let Err(err) = root_fs
1098 .new_open_options_ext()
1099 .insert_ro_file(path, atom.clone())
1100 {
1101 tracing::debug!(
1102 "failed to add package [{}] command [{}] - {}",
1103 pkg.id,
1104 command.name(),
1105 err
1106 );
1107 continue;
1108 }
1109 if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path2, atom)
1110 {
1111 tracing::debug!(
1112 "failed to add package [{}] command [{}] - {}",
1113 pkg.id,
1114 command.name(),
1115 err
1116 );
1117 continue;
1118 }
1119 }
1120 WasiFsRoot::Backing(fs) => {
1121 let mut f = fs.new_open_options().create(true).write(true).open(path)?;
1124 if let Err(e) = f.copy_from_owned_buffer(&atom).await {
1125 tracing::warn!(
1126 error = &e as &dyn std::error::Error,
1127 "Unable to copy file reference",
1128 );
1129 }
1130 let mut f = fs.new_open_options().create(true).write(true).open(path2)?;
1131 if let Err(e) = f.copy_from_owned_buffer(&atom).await {
1132 tracing::warn!(
1133 error = &e as &dyn std::error::Error,
1134 "Unable to copy file reference",
1135 );
1136 }
1137 }
1138 }
1139
1140 let mut package = pkg.clone();
1141 package.entrypoint_cmd = Some(command.name().to_string());
1142 let package_arc = Arc::new(package);
1143 self.bin_factory
1144 .set_binary(path.to_string_lossy().as_ref(), &package_arc);
1145 self.bin_factory
1146 .set_binary(path2.to_string_lossy().as_ref(), &package_arc);
1147
1148 tracing::debug!(
1149 package=%pkg.id,
1150 command_name=command.name(),
1151 path=%path.display(),
1152 "Injected a command into the filesystem",
1153 );
1154 }
1155 }
1156
1157 Ok(())
1158 }
1159
1160 pub fn uses<I>(&self, uses: I) -> Result<(), WasiStateCreationError>
1163 where
1164 I: IntoIterator<Item = String>,
1165 {
1166 let rt = self.runtime();
1167
1168 for package_name in uses {
1169 let specifier = package_name.parse::<PackageSource>().map_err(|e| {
1170 WasiStateCreationError::WasiIncludePackageError(format!(
1171 "package_name={package_name}, {e}",
1172 ))
1173 })?;
1174 let pkg = block_on(BinaryPackage::from_registry(&specifier, rt)).map_err(|e| {
1175 WasiStateCreationError::WasiIncludePackageError(format!(
1176 "package_name={package_name}, {e}",
1177 ))
1178 })?;
1179 self.use_package(&pkg)?;
1180 }
1181
1182 Ok(())
1183 }
1184
1185 #[cfg(feature = "sys")]
1186 pub fn map_commands(
1187 &self,
1188 map_commands: std::collections::HashMap<String, std::path::PathBuf>,
1189 ) -> Result<(), WasiStateCreationError> {
1190 #[allow(unused_imports)]
1192 use std::path::Path;
1193
1194 use shared_buffer::OwnedBuffer;
1195 #[allow(unused_imports)]
1196 use virtual_fs::FileSystem;
1197
1198 #[cfg(feature = "sys")]
1199 for (command, target) in map_commands.iter() {
1200 let file = std::fs::read(target).map_err(|err| {
1202 WasiStateCreationError::WasiInheritError(format!(
1203 "failed to read local binary [{}] - {}",
1204 target.as_os_str().to_string_lossy(),
1205 err
1206 ))
1207 })?;
1208 let file = OwnedBuffer::from(file);
1209
1210 if let WasiFsRoot::Sandbox(root_fs) = &self.state.fs.root_fs {
1211 let _ = root_fs.create_dir(Path::new("/bin"));
1212 let _ = root_fs.create_dir(Path::new("/usr"));
1213 let _ = root_fs.create_dir(Path::new("/usr/bin"));
1214
1215 let path = format!("/bin/{command}");
1216 let path = Path::new(path.as_str());
1217 if let Err(err) = root_fs
1218 .new_open_options_ext()
1219 .insert_ro_file(path, file.clone())
1220 {
1221 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1222 continue;
1223 }
1224 let path = format!("/usr/bin/{command}");
1225 let path = Path::new(path.as_str());
1226 if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path, file) {
1227 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1228 continue;
1229 }
1230 } else {
1231 tracing::debug!(
1232 "failed to add atom command [{}] to the root file system as it is not sandboxed",
1233 command
1234 );
1235 continue;
1236 }
1237 }
1238 Ok(())
1239 }
1240
1241 #[allow(clippy::await_holding_lock)]
1243 pub fn blocking_on_exit(&self, process_exit_code: Option<ExitCode>) {
1244 let cleanup = self.on_exit(process_exit_code);
1245 block_on(cleanup);
1246 }
1247
1248 #[allow(clippy::await_holding_lock)]
1250 pub fn on_exit(&self, process_exit_code: Option<ExitCode>) -> BoxFuture<'static, ()> {
1251 const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
1252
1253 #[cfg(feature = "journal")]
1255 if self.should_journal() && self.has_active_journal() {
1256 if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), process_exit_code)
1257 {
1258 tracing::warn!("failed to save snapshot event for thread exit - {}", err);
1259 }
1260
1261 if self.thread.is_main()
1262 && let Err(err) = JournalEffector::save_process_exit(self, process_exit_code)
1263 {
1264 tracing::warn!("failed to save snapshot event for process exit - {}", err);
1265 }
1266 }
1267
1268 if let Some(process_exit_code) = process_exit_code {
1270 let process = self.process.clone();
1271 let disable_fs_cleanup = self.disable_fs_cleanup;
1272 let pid = self.pid();
1273
1274 let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT);
1275 let state = self.state.clone();
1276 Box::pin(async move {
1277 if !disable_fs_cleanup {
1278 tracing::trace!(pid = %pid, "cleaning up open file handles");
1279
1280 tokio::select! {
1282 _ = timeout => {
1283 tracing::debug!(
1284 "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}"
1285 );
1286 },
1287 _ = state.fs.close_all() => { }
1288 }
1289
1290 process.signal_process(Signal::Sigquit);
1292 }
1293
1294 process.terminate(process_exit_code);
1296 })
1297 } else {
1298 Box::pin(async {})
1299 }
1300 }
1301
1302 pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) {
1303 if let Ok(Some(Wasi {
1304 main_args,
1305 env: env_vars,
1306 exec_name,
1307 ..
1308 })) = cmd.metadata().wasi()
1309 {
1310 if let Some(env_vars) = env_vars {
1311 let env_vars = env_vars
1312 .into_iter()
1313 .map(|env_var| {
1314 let (k, v) = env_var.split_once('=').unwrap();
1315
1316 (k.to_string(), v.as_bytes().to_vec())
1317 })
1318 .collect::<Vec<_>>();
1319
1320 let env_vars = conv_env_vars(env_vars);
1321
1322 self.state
1323 .envs
1324 .lock()
1325 .unwrap()
1326 .extend_from_slice(env_vars.as_slice());
1327 }
1328
1329 if let Some(main_args) = main_args {
1330 let mut args: std::sync::MutexGuard<'_, Vec<String>> =
1331 self.state.args.lock().unwrap();
1332 args.splice(1..1, main_args);
1334 }
1335
1336 if let Some(exec_name) = exec_name {
1337 self.state.args.lock().unwrap()[0] = exec_name;
1338 }
1339 }
1340 }
1341}