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