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 signal_handler_registered: std::sync::atomic::AtomicBool::new(false),
132 preopen: self.state.preopen.clone(),
133 },
134 runtime: self.runtime.clone(),
135 webc_dependencies: self.webc_dependencies.clone(),
136 mapped_commands: self.mapped_commands.clone(),
137 bin_factory: self.bin_factory.clone(),
138 capabilities: self.capabilities.clone(),
139 control_plane: self.control_plane.clone(),
140 memory_ty: None,
141 process: None,
142 thread: None,
143 call_initialize: self.call_initialize,
144 can_deep_sleep: self.can_deep_sleep,
145 extra_tracing: false,
146 #[cfg(feature = "journal")]
147 snapshot_on: self.snapshot_on.clone(),
148 #[cfg(feature = "journal")]
149 stop_running_after_snapshot: self.stop_running_after_snapshot,
150 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
151 }
152 }
153}
154
155pub struct WasiEnv {
157 pub control_plane: WasiControlPlane,
158 pub process: WasiProcess,
160 pub thread: WasiThread,
162 pub layout: WasiMemoryLayout,
164 pub vfork: Option<WasiVFork>,
166 pub poll_seed: u64,
168 pub(crate) state: Arc<WasiState>,
171 pub bin_factory: BinFactory,
173 pub owned_handles: Vec<WasiThreadHandle>,
176 pub runtime: Arc<dyn Runtime + Send + Sync + 'static>,
178
179 pub capabilities: Capabilities,
180
181 pub enable_deep_sleep: bool,
183
184 pub enable_journal: bool,
186
187 pub enable_exponential_cpu_backoff: Option<Duration>,
191
192 pub replaying_journal: bool,
195
196 pub skip_stdio_during_bootstrap: bool,
198
199 pub(crate) disable_fs_cleanup: bool,
202
203 inner: WasiInstanceHandlesPointer,
208
209 pub(crate) context_switching_environment: Option<ContextSwitchingEnvironment>,
215}
216
217impl std::fmt::Debug for WasiEnv {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 write!(f, "env(pid={}, tid={})", self.pid().raw(), self.tid().raw())
220 }
221}
222
223impl Clone for WasiEnv {
224 fn clone(&self) -> Self {
225 Self {
226 control_plane: self.control_plane.clone(),
227 process: self.process.clone(),
228 poll_seed: self.poll_seed,
229 thread: self.thread.clone(),
230 layout: self.layout.clone(),
231 vfork: self.vfork.clone(),
232 state: self.state.clone(),
233 bin_factory: self.bin_factory.clone(),
234 inner: Default::default(),
235 owned_handles: self.owned_handles.clone(),
236 runtime: self.runtime.clone(),
237 capabilities: self.capabilities.clone(),
238 enable_deep_sleep: self.enable_deep_sleep,
239 enable_journal: self.enable_journal,
240 enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
241 replaying_journal: self.replaying_journal,
242 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
243 disable_fs_cleanup: self.disable_fs_cleanup,
244 context_switching_environment: None,
245 }
246 }
247}
248
249impl WasiEnv {
250 pub fn builder(program_name: impl Into<String>) -> WasiEnvBuilder {
252 WasiEnvBuilder::new(program_name)
253 }
254
255 pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> {
257 let process = self.control_plane.new_process(self.process.module_hash)?;
258 let handle = process.new_thread(self.layout.clone(), ThreadStartType::MainThread)?;
259
260 let thread = handle.as_thread();
261 thread.copy_stack_from(&self.thread);
262
263 let state = Arc::new(self.state.fork());
264
265 let bin_factory = self.bin_factory.clone();
266
267 let new_env = Self {
268 control_plane: self.control_plane.clone(),
269 process,
270 thread,
271 layout: self.layout.clone(),
272 vfork: None,
273 poll_seed: 0,
274 bin_factory,
275 state,
276 inner: Default::default(),
277 owned_handles: Vec::new(),
278 runtime: self.runtime.clone(),
279 capabilities: self.capabilities.clone(),
280 enable_deep_sleep: self.enable_deep_sleep,
281 enable_journal: self.enable_journal,
282 enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
283 replaying_journal: false,
284 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
285 disable_fs_cleanup: self.disable_fs_cleanup,
286 context_switching_environment: None,
287 };
288 Ok((new_env, handle))
289 }
290
291 pub fn pid(&self) -> WasiProcessId {
292 self.process.pid()
293 }
294
295 pub fn tid(&self) -> WasiThreadId {
296 self.thread.tid()
297 }
298
299 pub fn will_use_asyncify(&self) -> bool {
302 self.inner()
303 .static_module_instance_handles()
304 .map(|handles| self.enable_deep_sleep || handles.has_stack_checkpoint)
305 .unwrap_or(false)
306 }
307
308 pub fn reinit(&mut self) -> Result<(), WasiStateCreationError> {
310 if !self.disable_fs_cleanup {
314 if let Ok(mut map) = self.state.fs.fd_map.write() {
317 map.clear();
318 }
319 self.state.fs.preopen_fds.write().unwrap().clear();
320 *self.state.fs.current_dir.lock().unwrap() = "/".to_string();
321
322 self.state.fs.create_stdin(&self.state.inodes);
324 self.state.fs.create_stdout(&self.state.inodes);
325 self.state.fs.create_stderr(&self.state.inodes);
326 self.state
327 .fs
328 .create_rootfd()
329 .map_err(WasiStateCreationError::WasiFsSetupError)?;
330 self.state
331 .fs
332 .create_preopens(&self.state.inodes, true)
333 .map_err(WasiStateCreationError::WasiFsSetupError)?;
334 }
335
336 self.process = WasiProcess::new(
338 self.process.pid,
339 self.process.module_hash,
340 self.process.compute.clone(),
341 );
342 self.thread = WasiThread::new(
343 self.thread.pid(),
344 self.thread.tid(),
345 self.thread.is_main(),
346 self.process.finished.clone(),
347 self.process.compute.must_upgrade().register_task()?,
348 self.thread.memory_layout().clone(),
349 self.thread.thread_start_type(),
350 );
351
352 Ok(())
353 }
354
355 pub unsafe fn capable_of_deep_sleep(&self) -> bool {
363 self.deep_sleep_capability_requested() && self.deep_sleep_supported_by_module()
364 }
365
366 pub(crate) fn refresh_deep_sleep_capability(&mut self) {
367 self.enable_deep_sleep = if cfg!(feature = "js") {
368 false
369 } else {
370 self.deep_sleep_capability_requested() && self.deep_sleep_supported_by_module()
371 };
372 }
373
374 fn deep_sleep_capability_requested(&self) -> bool {
375 self.capabilities.threading.enable_deep_sleep
376 }
377
378 fn deep_sleep_supported_by_module(&self) -> bool {
379 self.try_inner()
380 .map(|handles| {
381 handles
382 .static_module_instance_handles()
383 .map(|handles| {
384 handles.asyncify_get_state.is_some()
385 && handles.asyncify_start_rewind.is_some()
386 && handles.asyncify_start_unwind.is_some()
387 })
388 .unwrap_or(false)
389 })
390 .unwrap_or(false)
391 }
392
393 pub fn layout(&self) -> &WasiMemoryLayout {
395 &self.layout
396 }
397
398 #[allow(clippy::result_large_err)]
399 pub(crate) fn from_init(
400 init: WasiEnvInit,
401 module_hash: ModuleHash,
402 ) -> Result<Self, WasiRuntimeError> {
403 let process = if let Some(p) = init.process {
404 p
405 } else {
406 init.control_plane.new_process(module_hash)?
407 };
408
409 #[cfg(feature = "journal")]
410 {
411 let mut guard = process.inner.0.lock().unwrap();
412 guard.snapshot_on = init.snapshot_on.into_iter().collect();
413 guard.stop_running_after_checkpoint = init.stop_running_after_snapshot;
414 }
415
416 let layout = WasiMemoryLayout::default();
417 let thread = if let Some(t) = init.thread {
418 t
419 } else {
420 process.new_thread(layout.clone(), ThreadStartType::MainThread)?
421 };
422
423 let mut env = Self {
424 control_plane: init.control_plane,
425 process,
426 thread: thread.as_thread(),
427 layout,
428 vfork: None,
429 poll_seed: 0,
430 state: Arc::new(init.state),
431 inner: Default::default(),
432 owned_handles: Vec::new(),
433 #[cfg(feature = "journal")]
434 enable_journal: init.runtime.active_journal().is_some(),
435 #[cfg(not(feature = "journal"))]
436 enable_journal: false,
437 replaying_journal: false,
438 skip_stdio_during_bootstrap: init.skip_stdio_during_bootstrap,
439 enable_deep_sleep: false,
440 enable_exponential_cpu_backoff: init
441 .capabilities
442 .threading
443 .enable_exponential_cpu_backoff,
444 runtime: init.runtime,
445 bin_factory: init.bin_factory,
446 capabilities: init.capabilities,
447 disable_fs_cleanup: false,
448 context_switching_environment: None,
449 };
450 env.owned_handles.push(thread);
451
452 for pkg in &init.webc_dependencies {
454 env.use_package(pkg)?;
455 }
456
457 #[cfg(feature = "sys")]
458 env.map_commands(init.mapped_commands.clone())?;
459
460 Ok(env)
461 }
462
463 #[allow(clippy::result_large_err)]
465 pub(crate) fn instantiate(
466 self,
467 module: Module,
468 store: &mut impl AsStoreMut,
469 memory: Option<Memory>,
470 update_layout: bool,
471 call_initialize: bool,
472 linker_instance_group_data: Option<PreparedInstanceGroupData>,
473 ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> {
474 let pid = self.process.pid();
475
476 let mut store = store.as_store_mut();
477 let engine = self.runtime().engine();
478 let mut func_env = WasiFunctionEnv::new(&mut store, self);
479
480 let is_dl = super::linker::is_dynamically_linked(&module);
481 if is_dl {
482 let linker = match linker_instance_group_data {
483 Some(instance_group_data) => {
484 Linker::create_instance_group(instance_group_data, &mut store, &mut func_env)
485 }
486 None => {
487 let ld_library_path_owned;
489 let ld_library_path = {
490 let envs = func_env.data(&store).state.envs.lock().unwrap();
491 ld_library_path_owned = match envs
492 .iter()
493 .find_map(|env| env.strip_prefix(b"LD_LIBRARY_PATH="))
494 {
495 Some(path) => path
496 .split(|b| *b == b':')
497 .filter_map(|p| str::from_utf8(p).ok())
498 .map(PathBuf::from)
499 .collect::<Vec<_>>(),
500 None => vec![],
501 };
502 ld_library_path_owned
503 .iter()
504 .map(AsRef::as_ref)
505 .collect::<Vec<_>>()
506 };
507
508 Linker::new(
510 engine,
511 &module,
512 &mut store,
513 memory,
514 &mut func_env,
515 8 * 1024 * 1024,
516 &ld_library_path,
517 )
518 }
519 };
520
521 match linker {
522 Ok((_, linked_module)) => {
523 return Ok((linked_module.instance, func_env));
524 }
525 Err(e) => {
526 tracing::error!(
527 %pid,
528 error = &e as &dyn std::error::Error,
529 "Failed to link DL main module",
530 );
531 func_env
532 .data(&store)
533 .blocking_on_exit(Some(Errno::Noexec.into()));
534 return Err(WasiThreadError::LinkError(Arc::new(e)));
535 }
536 }
537 }
538
539 let mut import_object =
541 import_object_for_all_wasi_versions(&module, &mut store, &func_env.env);
542 if let Some(memory) = memory.clone() {
543 import_object.define("env", "memory", memory);
544 }
545 let runtime = func_env.data(&store).runtime.clone();
546 let instantiation_state = runtime
547 .prepare_imports(&module, &mut store, &mut import_object)
548 .map_err(|err| WasiThreadError::ImportPreparationFailed(Arc::new(err)))?;
549
550 let imported_memory = import_object
551 .get_export("env", "memory")
552 .and_then(|ext| match ext {
553 wasmer::Extern::Memory(memory) => Some(memory),
554 _ => None,
555 });
556
557 let instance = match Instance::new(&mut store, &module, &import_object) {
559 Ok(a) => a,
560 Err(err) => {
561 tracing::error!(
562 %pid,
563 error = &err as &dyn std::error::Error,
564 "Instantiation failed",
565 );
566 func_env
567 .data(&store)
568 .blocking_on_exit(Some(Errno::Noexec.into()));
569 return Err(WasiThreadError::InstanceCreateFailed(Box::new(err)));
570 }
571 };
572
573 runtime
574 .configure_new_instance(
575 &module,
576 &mut store,
577 &instance,
578 imported_memory.as_ref(),
579 instantiation_state,
580 )
581 .map_err(|err| WasiThreadError::InstanceConfigurationFailed(Arc::new(err)))?;
582
583 let handles = match imported_memory {
584 Some(memory) => WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
585 memory,
586 &store,
587 instance.clone(),
588 None,
589 )),
590 None => {
591 let exported_memory = instance
592 .exports
593 .iter()
594 .filter_map(|(_, export)| {
595 if let wasmer::Extern::Memory(memory) = export {
596 Some(memory.clone())
597 } else {
598 None
599 }
600 })
601 .next()
602 .ok_or(WasiThreadError::ExportError(ExportError::Missing(
603 "No imported or exported memory found".to_owned(),
604 )))?;
605 WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
606 exported_memory,
607 &store,
608 instance.clone(),
609 None,
610 ))
611 }
612 };
613
614 if let Err(err) = func_env.initialize_handles_and_layout(
616 &mut store,
617 instance.clone(),
618 handles,
619 None,
620 update_layout,
621 ) {
622 tracing::error!(
623 %pid,
624 error = &err as &dyn std::error::Error,
625 "Initialization failed",
626 );
627 func_env
628 .data(&store)
629 .blocking_on_exit(Some(Errno::Noexec.into()));
630 return Err(WasiThreadError::ExportError(err));
631 }
632
633 if call_initialize && let Ok(initialize) = instance.exports.get_function("_initialize") {
635 let initialize_result = initialize.call(&mut store, &[]);
636 if let Err(err) = initialize_result {
637 func_env
638 .data(&store)
639 .blocking_on_exit(Some(Errno::Noexec.into()));
640 return Err(WasiThreadError::InitFailed(Arc::new(anyhow::Error::from(
641 err,
642 ))));
643 }
644 }
645
646 Ok((instance, func_env))
647 }
648
649 pub fn runtime(&self) -> &(dyn Runtime + Send + Sync) {
651 self.runtime.deref()
652 }
653
654 pub fn tasks(&self) -> &Arc<dyn VirtualTaskManager> {
656 self.runtime.task_manager()
657 }
658
659 pub fn fs_root(&self) -> &WasiFsRoot {
660 &self.state.fs.root_fs
661 }
662
663 pub fn set_runtime<R>(&mut self, runtime: R)
665 where
666 R: Runtime + Send + Sync + 'static,
667 {
668 self.runtime = Arc::new(runtime);
669 }
670
671 pub fn active_threads(&self) -> u32 {
673 self.process.active_threads()
674 }
675
676 pub fn do_pending_operations(ctx: &mut FunctionEnvMut<'_, Self>) -> Result<(), WasiError> {
679 Self::do_pending_link_operations(ctx, true)?;
680 _ = Self::process_signals_and_exit(ctx)?;
681 Ok(())
682 }
683
684 pub fn do_pending_link_operations(
685 ctx: &mut FunctionEnvMut<'_, Self>,
686 fast: bool,
687 ) -> Result<(), WasiError> {
688 if let Some(linker) = ctx.data().inner().linker().cloned()
689 && let Err(e) = linker.do_pending_link_operations(ctx, fast)
690 {
691 tracing::warn!(err = ?e, "Failed to process pending link operations");
692 return Err(WasiError::Exit(Errno::Noexec.into()));
693 }
694 Ok(())
695 }
696
697 pub fn process_signals_and_exit(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
699 let env = ctx.data();
702 let env_inner = env
703 .try_inner()
704 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
705 let inner = env_inner.main_module_instance_handles();
706 let handler_registered = inner.signal_set
712 || env
713 .state
714 .signal_handler_registered
715 .load(std::sync::atomic::Ordering::SeqCst);
716 if !handler_registered {
717 let signals = env.thread.pop_signals();
718 if !signals.is_empty() {
719 for sig in signals {
720 if sig == Signal::Sigint
721 || sig == Signal::Sigquit
722 || sig == Signal::Sigkill
723 || sig == Signal::Sigabrt
724 || sig == Signal::Sigpipe
725 {
726 let exit_code = env.thread.set_or_get_exit_code_for_signal(sig);
727 return Err(WasiError::Exit(exit_code));
728 } else {
729 tracing::trace!(pid=%env.pid(), ?sig, "Signal ignored");
730 }
731 }
732 return Ok(Ok(true));
733 }
734 }
735
736 if let Some(forced_exit) = env.should_exit() {
738 return Err(WasiError::Exit(forced_exit));
739 }
740
741 Self::process_signals(ctx)
742 }
743
744 pub(crate) fn process_signals(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
746 let env = ctx.data();
749 let env_inner = env
750 .try_inner()
751 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
752 let inner = env_inner.main_module_instance_handles();
753 if !inner.signal_set {
754 let drained = !env.thread.pop_signals().is_empty();
760 return Ok(Ok(drained));
761 }
762
763 let ret = if inner.signal.as_ref().is_some() {
766 let signals = env.thread.pop_signals();
767 Self::process_signals_internal(ctx, signals)?
768 } else {
769 false
770 };
771
772 Ok(Ok(ret))
773 }
774
775 pub(crate) fn process_signals_internal(
776 ctx: &mut FunctionEnvMut<'_, Self>,
777 mut signals: Vec<Signal>,
778 ) -> Result<bool, WasiError> {
779 let env = ctx.data();
780 let env_inner = env
781 .try_inner()
782 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
783 let inner = env_inner.main_module_instance_handles();
784 if let Some(handler) = inner.signal.clone() {
785 let mut now = 0;
787 {
788 let mut has_signal_interval = false;
789 let mut inner = env.process.inner.0.lock().unwrap();
790 if !inner.signal_intervals.is_empty() {
791 now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap()
792 as u128;
793 for signal in inner.signal_intervals.values() {
794 let elapsed = now - signal.last_signal;
795 if elapsed >= signal.interval.as_nanos() {
796 has_signal_interval = true;
797 break;
798 }
799 }
800 }
801 if has_signal_interval {
802 for signal in inner.signal_intervals.values_mut() {
803 let elapsed = now - signal.last_signal;
804 if elapsed >= signal.interval.as_nanos() {
805 signal.last_signal = now;
806 signals.push(signal.signal);
807 }
808 }
809 }
810 }
811
812 for signal in signals {
813 if matches!(signal, Signal::Sigwakeup) {
815 continue;
816 }
817
818 tracing::trace!(
819 pid=%ctx.data().pid(),
820 ?signal,
821 "processing signal via handler",
822 );
823 if let Err(err) = handler.call(ctx, signal as i32) {
824 match err.downcast::<WasiError>() {
825 Ok(wasi_err) => {
826 tracing::warn!(
827 pid=%ctx.data().pid(),
828 wasi_err=&wasi_err as &dyn std::error::Error,
829 "signal handler wasi error",
830 );
831 return Err(wasi_err);
832 }
833 Err(runtime_err) => {
834 if signal != Signal::Sigkill {
837 tracing::warn!(
838 pid=%ctx.data().pid(),
839 runtime_err=&runtime_err as &dyn std::error::Error,
840 "signal handler runtime error",
841 );
842 }
843 return Err(WasiError::Exit(Errno::Intr.into()));
844 }
845 }
846 }
847 tracing::trace!(
848 pid=%ctx.data().pid(),
849 "signal processed",
850 );
851 }
852 Ok(true)
853 } else {
854 tracing::trace!("no signal handler");
855 Ok(false)
856 }
857 }
858
859 pub fn should_exit(&self) -> Option<ExitCode> {
861 if let Some(forced_exit) = self.thread.try_join() {
863 return Some(forced_exit.unwrap_or_else(|err| {
864 tracing::debug!(
865 error = &*err as &dyn std::error::Error,
866 "exit runtime error",
867 );
868 Errno::Child.into()
869 }));
870 }
871 if let Some(forced_exit) = self.process.try_join() {
872 return Some(forced_exit.unwrap_or_else(|err| {
873 tracing::debug!(
874 error = &*err as &dyn std::error::Error,
875 "exit runtime error",
876 );
877 Errno::Child.into()
878 }));
879 }
880 None
881 }
882
883 pub fn net(&self) -> &DynVirtualNetworking {
885 self.runtime.networking()
886 }
887
888 pub(crate) fn inner(&self) -> WasiInstanceGuard<'_> {
891 self.inner.get().expect(
892 "You must initialize the WasiEnv before using it and can not pass it between threads",
893 )
894 }
895
896 pub(crate) fn inner_mut(&mut self) -> WasiInstanceGuardMut<'_> {
899 self.inner.get_mut().expect(
900 "You must initialize the WasiEnv before using it and can not pass it between threads",
901 )
902 }
903
904 pub(crate) fn try_inner(&self) -> Option<WasiInstanceGuard<'_>> {
906 self.inner.get()
907 }
908
909 #[allow(dead_code)]
912 pub(crate) fn try_inner_mut(&mut self) -> Option<WasiInstanceGuardMut<'_>> {
913 self.inner.get_mut()
914 }
915
916 #[doc(hidden)]
920 pub(crate) fn set_inner(&mut self, handles: WasiModuleTreeHandles) {
921 self.inner.set(handles);
922 self.refresh_deep_sleep_capability();
923 }
924
925 #[doc(hidden)]
929 pub(crate) fn swap_inner(&mut self, other: &mut Self) {
930 std::mem::swap(&mut self.inner, &mut other.inner);
931 }
932
933 pub(crate) fn ensure_static_module(&self) -> Result<(), ()> {
937 self.inner.get().unwrap().ensure_static_module()
938 }
939
940 pub fn try_clone_instance(&self) -> Option<Instance> {
943 let guard = self.inner.get();
944 match guard {
945 Some(guard) => guard
946 .static_module_instance_handles()
947 .map(|instance| instance.instance.clone()),
948 None => None,
949 }
950 }
951
952 pub fn try_memory(&self) -> Option<WasiInstanceGuardMemory<'_>> {
955 self.try_inner().map(|i| i.memory())
956 }
957
958 pub unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> {
965 self.try_memory().expect(
966 "You must initialize the WasiEnv before using it and can not pass it between threads",
967 )
968 }
969
970 pub fn try_memory_view<'a>(
973 &self,
974 store: &'a (impl AsStoreRef + ?Sized),
975 ) -> Option<MemoryView<'a>> {
976 self.try_memory().map(|m| m.view(store))
977 }
978
979 pub unsafe fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
986 self.try_memory_view(store).expect(
987 "You must initialize the WasiEnv before using it and can not pass it between threads",
988 )
989 }
990
991 #[allow(dead_code)]
994 pub(crate) fn try_memory_clone(&self) -> Option<Memory> {
995 self.try_inner()
996 .map(|i| i.main_module_instance_handles().memory_clone())
997 }
998
999 pub(crate) fn state(&self) -> &WasiState {
1001 &self.state
1002 }
1003
1004 pub fn stdout(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1006 self.state.stdout()
1007 }
1008
1009 pub fn stderr(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1011 self.state.stderr()
1012 }
1013
1014 pub fn stdin(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1016 self.state.stdin()
1017 }
1018
1019 pub fn should_journal(&self) -> bool {
1021 self.enable_journal && !self.replaying_journal
1022 }
1023
1024 #[cfg(feature = "journal")]
1026 pub fn has_active_journal(&self) -> bool {
1027 self.runtime().active_journal().is_some()
1028 }
1029
1030 #[cfg(feature = "journal")]
1032 pub fn active_journal(&self) -> Result<&DynJournal, Errno> {
1033 self.runtime().active_journal().ok_or_else(|| {
1034 tracing::debug!("failed to save thread exit as there is not active journal");
1035 Errno::Fault
1036 })
1037 }
1038
1039 #[cfg(feature = "journal")]
1041 pub fn has_snapshot_trigger(&self, trigger: SnapshotTrigger) -> bool {
1042 let guard = self.process.inner.0.lock().unwrap();
1043 guard.snapshot_on.contains(&trigger)
1044 }
1045
1046 #[cfg(feature = "journal")]
1048 pub fn pop_snapshot_trigger(&mut self, trigger: SnapshotTrigger) -> bool {
1049 let mut guard = self.process.inner.0.lock().unwrap();
1050 if trigger.only_once() {
1051 guard.snapshot_on.remove(&trigger)
1052 } else {
1053 guard.snapshot_on.contains(&trigger)
1054 }
1055 }
1056
1057 pub fn std_dev_get(
1060 &self,
1061 fd: crate::syscalls::WasiFd,
1062 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1063 self.state.std_dev_get(fd)
1064 }
1065
1066 pub(crate) unsafe fn get_memory_and_wasi_state<'a>(
1072 &'a self,
1073 store: &'a impl AsStoreRef,
1074 _mem_index: u32,
1075 ) -> (MemoryView<'a>, &'a WasiState) {
1076 let memory = unsafe { self.memory_view(store) };
1077 let state = self.state.deref();
1078 (memory, state)
1079 }
1080
1081 pub(crate) unsafe fn get_memory_and_wasi_state_and_inodes<'a>(
1087 &'a self,
1088 store: &'a impl AsStoreRef,
1089 _mem_index: u32,
1090 ) -> (MemoryView<'a>, &'a WasiState, &'a WasiInodes) {
1091 let memory = unsafe { self.memory_view(store) };
1092 let state = self.state.deref();
1093 let inodes = &state.inodes;
1094 (memory, state, inodes)
1095 }
1096
1097 pub(crate) fn get_wasi_state_and_inodes(&self) -> (&WasiState, &WasiInodes) {
1098 let state = self.state.deref();
1099 let inodes = &state.inodes;
1100 (state, inodes)
1101 }
1102
1103 pub(crate) fn get_wasi_state(&self) -> &WasiState {
1104 self.state.deref()
1105 }
1106
1107 pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
1108 block_on(self.use_package_async(pkg))
1109 }
1110
1111 pub async fn use_package_async(
1123 &self,
1124 pkg: &BinaryPackage,
1125 ) -> Result<(), WasiStateCreationError> {
1126 tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment");
1127 let root_fs = &self.state.fs.root_fs;
1128
1129 if let Err(e) = self.state.fs.conditional_union(pkg).await {
1132 tracing::warn!(
1133 error = &e as &dyn std::error::Error,
1134 "Unable to merge the package mounts into the main filesystem",
1135 );
1136 }
1137
1138 if !pkg.commands.is_empty() {
1141 let _ = root_fs.create_dir(Path::new("/bin"));
1142 let _ = root_fs.create_dir(Path::new("/usr"));
1143 let _ = root_fs.create_dir(Path::new("/usr/bin"));
1144
1145 for command in &pkg.commands {
1146 let path = format!("/bin/{}", command.name());
1147 let path2 = format!("/usr/bin/{}", command.name());
1148 let path = Path::new(path.as_str());
1149 let path2 = Path::new(path2.as_str());
1150
1151 let atom = command.atom();
1152
1153 if let Err(err) = write_readonly_buffer_to_fs(root_fs, path, &atom).await {
1154 tracing::debug!(
1155 "failed to add package [{}] command [{}] - {}",
1156 pkg.id,
1157 command.name(),
1158 err
1159 );
1160 continue;
1161 }
1162 if let Err(err) = write_readonly_buffer_to_fs(root_fs, path2, &atom).await {
1163 tracing::debug!(
1164 "failed to add package [{}] command [{}] - {}",
1165 pkg.id,
1166 command.name(),
1167 err
1168 );
1169 continue;
1170 }
1171
1172 let mut package = pkg.clone();
1173 package.entrypoint_cmd = Some(command.name().to_string());
1174 let package_arc = Arc::new(package);
1175 self.bin_factory
1176 .set_binary(path.to_string_lossy().as_ref(), &package_arc);
1177 self.bin_factory
1178 .set_binary(path2.to_string_lossy().as_ref(), &package_arc);
1179
1180 tracing::debug!(
1181 package=%pkg.id,
1182 command_name=command.name(),
1183 path=%path.display(),
1184 "Injected a command into the filesystem",
1185 );
1186 }
1187 }
1188
1189 Ok(())
1190 }
1191
1192 pub fn uses<I>(&self, uses: I) -> Result<(), WasiStateCreationError>
1195 where
1196 I: IntoIterator<Item = String>,
1197 {
1198 let rt = self.runtime();
1199
1200 for package_name in uses {
1201 let specifier = package_name.parse::<PackageSource>().map_err(|e| {
1202 WasiStateCreationError::WasiIncludePackageError(format!(
1203 "package_name={package_name}, {e}",
1204 ))
1205 })?;
1206 let pkg = block_on(BinaryPackage::from_registry(&specifier, rt)).map_err(|e| {
1207 WasiStateCreationError::WasiIncludePackageError(format!(
1208 "package_name={package_name}, {e}",
1209 ))
1210 })?;
1211 self.use_package(&pkg)?;
1212 }
1213
1214 Ok(())
1215 }
1216
1217 #[cfg(feature = "sys")]
1218 pub fn map_commands(
1219 &self,
1220 map_commands: std::collections::HashMap<String, std::path::PathBuf>,
1221 ) -> Result<(), WasiStateCreationError> {
1222 #[allow(unused_imports)]
1224 use std::path::Path;
1225
1226 use shared_buffer::OwnedBuffer;
1227 #[allow(unused_imports)]
1228 use virtual_fs::FileSystem;
1229
1230 #[cfg(feature = "sys")]
1231 for (command, target) in map_commands.iter() {
1232 let file = std::fs::read(target).map_err(|err| {
1234 WasiStateCreationError::WasiInheritError(format!(
1235 "failed to read local binary [{}] - {}",
1236 target.as_os_str().to_string_lossy(),
1237 err
1238 ))
1239 })?;
1240 let file = OwnedBuffer::from(file);
1241
1242 let path = format!("/bin/{command}");
1243 let path = Path::new(path.as_str());
1244 if let Err(err) = block_on(write_readonly_buffer_to_fs(
1245 &self.state.fs.root_fs,
1246 path,
1247 &file,
1248 )) {
1249 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1250 continue;
1251 }
1252
1253 let path = format!("/usr/bin/{command}");
1254 let path = Path::new(path.as_str());
1255 if let Err(err) = block_on(write_readonly_buffer_to_fs(
1256 &self.state.fs.root_fs,
1257 path,
1258 &file,
1259 )) {
1260 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1261 continue;
1262 }
1263 }
1264 Ok(())
1265 }
1266
1267 #[allow(clippy::await_holding_lock)]
1269 pub fn blocking_on_exit(&self, process_exit_code: Option<ExitCode>) {
1270 let cleanup = self.on_exit(process_exit_code);
1271 block_on(cleanup);
1272 }
1273
1274 #[allow(clippy::await_holding_lock)]
1276 pub fn on_exit(&self, process_exit_code: Option<ExitCode>) -> BoxFuture<'static, ()> {
1277 const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
1278
1279 #[cfg(feature = "journal")]
1281 if self.should_journal() && self.has_active_journal() {
1282 if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), process_exit_code)
1283 {
1284 tracing::warn!("failed to save snapshot event for thread exit - {}", err);
1285 }
1286
1287 if self.thread.is_main()
1288 && let Err(err) = JournalEffector::save_process_exit(self, process_exit_code)
1289 {
1290 tracing::warn!("failed to save snapshot event for process exit - {}", err);
1291 }
1292 }
1293
1294 if let Some(process_exit_code) = process_exit_code {
1296 let process = self.process.clone();
1297 let disable_fs_cleanup = self.disable_fs_cleanup;
1298 let pid = self.pid();
1299
1300 let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT);
1301 let state = self.state.clone();
1302 Box::pin(async move {
1303 if process.try_start_cleanup() {
1304 if !disable_fs_cleanup {
1305 tracing::trace!(pid = %pid, "cleaning up open file handles");
1306
1307 tokio::select! {
1309 _ = timeout => {
1310 tracing::debug!(
1311 "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}"
1312 );
1313 },
1314 _ = state.fs.close_all() => { }
1315 }
1316 }
1317
1318 process.terminate(process_exit_code);
1322 process.signal_process(Signal::Sigquit);
1323 }
1324 })
1325 } else {
1326 Box::pin(async {})
1327 }
1328 }
1329
1330 pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) {
1331 if let Ok(Some(Wasi {
1332 main_args,
1333 env: env_vars,
1334 exec_name,
1335 ..
1336 })) = cmd.metadata().wasi()
1337 {
1338 if let Some(env_vars) = env_vars {
1339 let env_vars = env_vars
1340 .into_iter()
1341 .map(|env_var| {
1342 let (k, v) = env_var.split_once('=').unwrap();
1343
1344 (k.to_string(), v.as_bytes().to_vec())
1345 })
1346 .collect::<Vec<_>>();
1347
1348 let env_vars = conv_env_vars(env_vars);
1349
1350 self.state
1351 .envs
1352 .lock()
1353 .unwrap()
1354 .extend_from_slice(env_vars.as_slice());
1355 }
1356
1357 if let Some(main_args) = main_args {
1358 let mut args: std::sync::MutexGuard<'_, Vec<String>> =
1359 self.state.args.lock().unwrap();
1360 args.splice(1..1, main_args);
1362 }
1363
1364 if let Some(exec_name) = exec_name {
1365 self.state.args.lock().unwrap()[0] = exec_name;
1366 }
1367 }
1368 }
1369}