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