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::{
11 TtyBridge,
12 task::{
13 control_plane::ControlPlaneError,
14 process::{WasiProcess, WasiProcessId},
15 thread::{WasiMemoryLayout, WasiThread, WasiThreadHandle, WasiThreadId},
16 },
17 },
18 state::PreparedInstanceGroupData,
19 syscalls::platform_clock_time_get,
20};
21use futures::future::BoxFuture;
22use rand::RngExt;
23use std::{
24 collections::{HashMap, HashSet},
25 ops::Deref,
26 path::{Path, PathBuf},
27 str,
28 sync::Arc,
29 time::Duration,
30};
31use virtual_fs::{FileSystem, FsError, VirtualFile};
32use virtual_mio::block_on;
33use virtual_net::DynVirtualNetworking;
34use wasmer::{
35 AsStoreMut, AsStoreRef, ExportError, FunctionEnvMut, Instance, Memory, MemoryType, MemoryView,
36 Module,
37};
38use wasmer_config::package::PackageSource;
39use wasmer_types::ModuleHash;
40use wasmer_wasix_types::{
41 types::Signal,
42 wasi::{Errno, ExitCode, Snapshot0Clockid},
43 wasix::ThreadStartType,
44};
45use webc::metadata::annotations::Wasi;
46
47pub use super::handles::*;
48use super::{Linker, WasiState, context_switching::ContextSwitchingEnvironment};
49
50fn add_command_env_defaults(environment: &mut Vec<Vec<u8>>, defaults: Vec<String>) {
51 let mut names = environment
52 .iter()
53 .filter_map(|entry| {
54 entry
55 .iter()
56 .position(|byte| *byte == b'=')
57 .map(|separator| entry[..separator].to_vec())
58 })
59 .collect::<HashSet<_>>();
60
61 for default in defaults {
62 let Some((key, value)) = default.split_once('=') else {
63 continue;
64 };
65 if names.insert(key.as_bytes().to_vec()) {
66 environment.push([key.as_bytes(), b"=", value.as_bytes()].concat());
67 }
68 }
69}
70
71async fn write_readonly_buffer_to_fs(
72 fs: &WasiFsRoot,
73 path: &Path,
74 contents: &shared_buffer::OwnedBuffer,
75) -> Result<(), FsError> {
76 if let Some(parent) = path.parent() {
77 virtual_fs::create_dir_all(fs, parent)?;
78 }
79
80 if let Some(root_fs) = fs.writable_root() {
81 return root_fs
82 .new_open_options_ext()
83 .insert_ro_file(path, contents.clone());
84 }
85
86 let mut file = fs
87 .new_open_options()
88 .create(true)
89 .truncate(true)
90 .write(true)
91 .open(path)?;
92 file.copy_from_owned_buffer(contents)
93 .await
94 .map_err(virtual_fs::FsError::from)
95}
96
97#[derive(Debug)]
99pub struct WasiEnvInit {
100 pub(crate) state: WasiState,
101 pub runtime: Arc<dyn Runtime + Send + Sync>,
102 pub tty: Option<Arc<dyn TtyBridge + Send + Sync>>,
103 pub webc_dependencies: Vec<BinaryPackage>,
104 pub mapped_commands: HashMap<String, PathBuf>,
105 pub bin_factory: BinFactory,
106 pub capabilities: Capabilities,
107
108 pub control_plane: WasiControlPlane,
109 pub memory_ty: Option<MemoryType>,
110 pub process: Option<WasiProcess>,
111 pub thread: Option<WasiThreadHandle>,
112
113 pub call_initialize: bool,
116
117 pub can_deep_sleep: bool,
119
120 pub extra_tracing: bool,
122
123 #[cfg(feature = "journal")]
125 pub snapshot_on: Vec<SnapshotTrigger>,
126
127 #[cfg(feature = "journal")]
129 pub stop_running_after_snapshot: bool,
130
131 pub skip_stdio_during_bootstrap: bool,
133}
134
135impl WasiEnvInit {
136 pub fn duplicate(&self) -> Self {
137 let inodes = WasiInodes::new();
138
139 let fs =
141 crate::fs::WasiFs::new_with_preopen(&inodes, &[], &[], self.state.fs.root_fs.clone())
142 .unwrap();
143
144 Self {
145 state: WasiState {
146 secret: rand::rng().random::<[u8; 32]>(),
147 inodes,
148 fs,
149 futexs: Default::default(),
150 clock_offset: std::sync::Mutex::new(
151 self.state.clock_offset.lock().unwrap().clone(),
152 ),
153 args: std::sync::Mutex::new(self.state.args.lock().unwrap().clone()),
154 envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()),
155 signals: std::sync::Mutex::new(self.state.signals.lock().unwrap().deref().clone()),
156 signal_handler_registered: std::sync::atomic::AtomicBool::new(false),
157 preopen: self.state.preopen.clone(),
158 },
159 runtime: self.runtime.clone(),
160 tty: self.tty.clone(),
161 webc_dependencies: self.webc_dependencies.clone(),
162 mapped_commands: self.mapped_commands.clone(),
163 bin_factory: self.bin_factory.clone(),
164 capabilities: self.capabilities.clone(),
165 control_plane: self.control_plane.clone(),
166 memory_ty: None,
167 process: None,
168 thread: None,
169 call_initialize: self.call_initialize,
170 can_deep_sleep: self.can_deep_sleep,
171 extra_tracing: false,
172 #[cfg(feature = "journal")]
173 snapshot_on: self.snapshot_on.clone(),
174 #[cfg(feature = "journal")]
175 stop_running_after_snapshot: self.stop_running_after_snapshot,
176 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
177 }
178 }
179}
180
181pub struct WasiEnv {
183 pub control_plane: WasiControlPlane,
184 pub process: WasiProcess,
186 pub thread: WasiThread,
188 pub layout: WasiMemoryLayout,
190 pub vfork: Option<WasiVFork>,
192 pub poll_seed: u64,
194 pub(crate) state: Arc<WasiState>,
197 pub bin_factory: BinFactory,
199 pub owned_handles: Vec<WasiThreadHandle>,
202 pub runtime: Arc<dyn Runtime + Send + Sync + 'static>,
204 pub tty: Option<Arc<dyn TtyBridge + Send + Sync + 'static>>,
206
207 pub capabilities: Capabilities,
208
209 pub enable_deep_sleep: bool,
211
212 pub enable_journal: bool,
214
215 pub enable_exponential_cpu_backoff: Option<Duration>,
219
220 pub replaying_journal: bool,
223
224 pub skip_stdio_during_bootstrap: bool,
226
227 pub(crate) disable_fs_cleanup: bool,
230
231 inner: WasiInstanceHandlesPointer,
236
237 pub(crate) context_switching_environment: Option<ContextSwitchingEnvironment>,
243}
244
245impl std::fmt::Debug for WasiEnv {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 write!(f, "env(pid={}, tid={})", self.pid().raw(), self.tid().raw())
248 }
249}
250
251impl Clone for WasiEnv {
252 fn clone(&self) -> Self {
253 Self {
254 control_plane: self.control_plane.clone(),
255 process: self.process.clone(),
256 poll_seed: self.poll_seed,
257 thread: self.thread.clone(),
258 layout: self.layout.clone(),
259 vfork: self.vfork.clone(),
260 state: self.state.clone(),
261 bin_factory: self.bin_factory.clone(),
262 inner: Default::default(),
263 owned_handles: self.owned_handles.clone(),
264 runtime: self.runtime.clone(),
265 tty: self.tty.clone(),
266 capabilities: self.capabilities.clone(),
267 enable_deep_sleep: self.enable_deep_sleep,
268 enable_journal: self.enable_journal,
269 enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
270 replaying_journal: self.replaying_journal,
271 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
272 disable_fs_cleanup: self.disable_fs_cleanup,
273 context_switching_environment: None,
274 }
275 }
276}
277
278impl WasiEnv {
279 pub fn builder(program_name: impl Into<String>) -> WasiEnvBuilder {
281 WasiEnvBuilder::new(program_name)
282 }
283
284 pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> {
286 let process = self.control_plane.new_process(self.process.module_hash)?;
287 let handle = process.new_thread(self.layout.clone(), ThreadStartType::MainThread)?;
288
289 let thread = handle.as_thread();
290 thread.copy_stack_from(&self.thread);
291
292 let state = Arc::new(self.state.fork());
293
294 let bin_factory = self.bin_factory.clone();
295
296 let new_env = Self {
297 control_plane: self.control_plane.clone(),
298 process,
299 thread,
300 layout: self.layout.clone(),
301 vfork: None,
302 poll_seed: 0,
303 bin_factory,
304 state,
305 inner: Default::default(),
306 owned_handles: Vec::new(),
307 runtime: self.runtime.clone(),
308 tty: self.tty.clone(),
309 capabilities: self.capabilities.clone(),
310 enable_deep_sleep: self.enable_deep_sleep,
311 enable_journal: self.enable_journal,
312 enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
313 replaying_journal: false,
314 skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
315 disable_fs_cleanup: self.disable_fs_cleanup,
316 context_switching_environment: None,
317 };
318 Ok((new_env, handle))
319 }
320
321 pub fn pid(&self) -> WasiProcessId {
322 self.process.pid()
323 }
324
325 pub fn tid(&self) -> WasiThreadId {
326 self.thread.tid()
327 }
328
329 pub fn will_use_asyncify(&self) -> bool {
332 self.inner()
333 .static_module_instance_handles()
334 .map(|handles| self.enable_deep_sleep || handles.has_stack_checkpoint)
335 .unwrap_or(false)
336 }
337
338 pub fn reinit(&mut self) -> Result<(), WasiStateCreationError> {
340 if !self.disable_fs_cleanup {
344 if let Ok(mut map) = self.state.fs.fd_map.write() {
347 map.clear();
348 }
349 self.state.fs.preopen_fds.write().unwrap().clear();
350 *self.state.fs.current_dir.lock().unwrap() = "/".to_string();
351
352 self.state.fs.create_stdin(&self.state.inodes);
354 self.state.fs.create_stdout(&self.state.inodes);
355 self.state.fs.create_stderr(&self.state.inodes);
356 self.state
357 .fs
358 .create_rootfd()
359 .map_err(WasiStateCreationError::WasiFsSetupError)?;
360 self.state
361 .fs
362 .create_preopens(&self.state.inodes, true)
363 .map_err(WasiStateCreationError::WasiFsSetupError)?;
364 }
365
366 self.process = WasiProcess::new(
368 self.process.pid,
369 self.process.module_hash,
370 self.process.compute.clone(),
371 );
372 self.thread = WasiThread::new(
373 self.thread.pid(),
374 self.thread.tid(),
375 self.thread.is_main(),
376 self.process.finished.clone(),
377 self.process.compute.must_upgrade().register_task()?,
378 self.thread.memory_layout().clone(),
379 self.thread.thread_start_type(),
380 );
381
382 Ok(())
383 }
384
385 pub unsafe fn capable_of_deep_sleep(&self) -> bool {
393 self.deep_sleep_capability_requested() && self.deep_sleep_supported_by_module()
394 }
395
396 pub(crate) fn refresh_deep_sleep_capability(&mut self) {
397 self.enable_deep_sleep = if cfg!(feature = "js") {
398 false
399 } else {
400 self.deep_sleep_capability_requested() && self.deep_sleep_supported_by_module()
401 };
402 }
403
404 fn deep_sleep_capability_requested(&self) -> bool {
405 self.capabilities.threading.enable_deep_sleep
406 }
407
408 fn deep_sleep_supported_by_module(&self) -> bool {
409 self.try_inner()
410 .map(|handles| {
411 handles
412 .static_module_instance_handles()
413 .map(|handles| {
414 handles.asyncify_get_state.is_some()
415 && handles.asyncify_start_rewind.is_some()
416 && handles.asyncify_start_unwind.is_some()
417 })
418 .unwrap_or(false)
419 })
420 .unwrap_or(false)
421 }
422
423 pub fn layout(&self) -> &WasiMemoryLayout {
425 &self.layout
426 }
427
428 #[allow(clippy::result_large_err)]
429 pub(crate) fn from_init(
430 init: WasiEnvInit,
431 module_hash: ModuleHash,
432 ) -> Result<Self, WasiRuntimeError> {
433 let process = if let Some(p) = init.process {
434 p
435 } else {
436 init.control_plane.new_process(module_hash)?
437 };
438
439 #[cfg(feature = "journal")]
440 {
441 let mut guard = process.inner.0.lock().unwrap();
442 guard.snapshot_on = init.snapshot_on.into_iter().collect();
443 guard.stop_running_after_checkpoint = init.stop_running_after_snapshot;
444 }
445
446 let layout = WasiMemoryLayout::default();
447 let thread = if let Some(t) = init.thread {
448 t
449 } else {
450 process.new_thread(layout.clone(), ThreadStartType::MainThread)?
451 };
452
453 let mut env = Self {
454 control_plane: init.control_plane,
455 process,
456 thread: thread.as_thread(),
457 layout,
458 vfork: None,
459 poll_seed: 0,
460 state: Arc::new(init.state),
461 inner: Default::default(),
462 owned_handles: Vec::new(),
463 #[cfg(feature = "journal")]
464 enable_journal: init.runtime.active_journal().is_some(),
465 #[cfg(not(feature = "journal"))]
466 enable_journal: false,
467 replaying_journal: false,
468 skip_stdio_during_bootstrap: init.skip_stdio_during_bootstrap,
469 enable_deep_sleep: false,
470 enable_exponential_cpu_backoff: init
471 .capabilities
472 .threading
473 .enable_exponential_cpu_backoff,
474 runtime: init.runtime,
475 tty: init.tty,
476 bin_factory: init.bin_factory,
477 capabilities: init.capabilities,
478 disable_fs_cleanup: false,
479 context_switching_environment: None,
480 };
481 env.owned_handles.push(thread);
482
483 for pkg in &init.webc_dependencies {
485 env.use_package(pkg)?;
486 }
487
488 #[cfg(feature = "sys")]
489 env.map_commands(init.mapped_commands.clone())?;
490
491 Ok(env)
492 }
493
494 #[allow(clippy::result_large_err)]
496 pub(crate) fn instantiate(
497 self,
498 module: Module,
499 store: &mut impl AsStoreMut,
500 memory: Option<Memory>,
501 update_layout: bool,
502 call_initialize: bool,
503 linker_instance_group_data: Option<PreparedInstanceGroupData>,
504 ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> {
505 let pid = self.process.pid();
506
507 let mut store = store.as_store_mut();
508 let engine = self.runtime().engine();
509 let mut func_env = WasiFunctionEnv::new(&mut store, self);
510
511 let is_dl = super::linker::is_dynamically_linked(&module);
512 if is_dl {
513 let linker = match linker_instance_group_data {
514 Some(instance_group_data) => {
515 Linker::create_instance_group(instance_group_data, &mut store, &mut func_env)
516 }
517 None => {
518 let ld_library_path_owned;
520 let ld_library_path = {
521 let envs = func_env.data(&store).state.envs.lock().unwrap();
522 ld_library_path_owned = match envs
523 .iter()
524 .find_map(|env| env.strip_prefix(b"LD_LIBRARY_PATH="))
525 {
526 Some(path) => path
527 .split(|b| *b == b':')
528 .filter_map(|p| str::from_utf8(p).ok())
529 .map(PathBuf::from)
530 .collect::<Vec<_>>(),
531 None => vec![],
532 };
533 ld_library_path_owned
534 .iter()
535 .map(AsRef::as_ref)
536 .collect::<Vec<_>>()
537 };
538
539 Linker::new(
541 engine,
542 &module,
543 &mut store,
544 memory,
545 &mut func_env,
546 8 * 1024 * 1024,
547 &ld_library_path,
548 )
549 }
550 };
551
552 match linker {
553 Ok((_, linked_module)) => {
554 return Ok((linked_module.instance, func_env));
555 }
556 Err(e) => {
557 tracing::error!(
558 %pid,
559 error = &e as &dyn std::error::Error,
560 "Failed to link DL main module",
561 );
562 func_env
563 .data(&store)
564 .blocking_on_exit(Some(Errno::Noexec.into()));
565 return Err(WasiThreadError::LinkError(Arc::new(e)));
566 }
567 }
568 }
569
570 let mut import_object =
572 import_object_for_all_wasi_versions(&module, &mut store, &func_env.env);
573 if let Some(memory) = memory.clone() {
574 import_object.define("env", "memory", memory);
575 }
576 let runtime = func_env.data(&store).runtime.clone();
577 let instantiation_state = runtime
578 .prepare_imports(&module, &mut store, &mut import_object)
579 .map_err(|err| WasiThreadError::ImportPreparationFailed(Arc::new(err)))?;
580
581 let imported_memory = import_object
582 .get_export("env", "memory")
583 .and_then(|ext| match ext {
584 wasmer::Extern::Memory(memory) => Some(memory),
585 _ => None,
586 });
587
588 let instance = match Instance::new(&mut store, &module, &import_object) {
590 Ok(a) => a,
591 Err(err) => {
592 tracing::error!(
593 %pid,
594 error = &err as &dyn std::error::Error,
595 "Instantiation failed",
596 );
597 func_env
598 .data(&store)
599 .blocking_on_exit(Some(Errno::Noexec.into()));
600 return Err(WasiThreadError::InstanceCreateFailed(Box::new(err)));
601 }
602 };
603
604 runtime
605 .configure_new_instance(
606 &module,
607 &mut store,
608 &instance,
609 imported_memory.as_ref(),
610 instantiation_state,
611 )
612 .map_err(|err| WasiThreadError::InstanceConfigurationFailed(Arc::new(err)))?;
613
614 let handles = match imported_memory {
615 Some(memory) => WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
616 memory,
617 &store,
618 instance.clone(),
619 None,
620 )),
621 None => {
622 let exported_memory = instance
623 .exports
624 .iter()
625 .filter_map(|(_, export)| {
626 if let wasmer::Extern::Memory(memory) = export {
627 Some(memory.clone())
628 } else {
629 None
630 }
631 })
632 .next()
633 .ok_or(WasiThreadError::ExportError(ExportError::Missing(
634 "No imported or exported memory found".to_owned(),
635 )))?;
636 WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
637 exported_memory,
638 &store,
639 instance.clone(),
640 None,
641 ))
642 }
643 };
644
645 if let Err(err) = func_env.initialize_handles_and_layout(
647 &mut store,
648 instance.clone(),
649 handles,
650 None,
651 update_layout,
652 ) {
653 tracing::error!(
654 %pid,
655 error = &err as &dyn std::error::Error,
656 "Initialization failed",
657 );
658 func_env
659 .data(&store)
660 .blocking_on_exit(Some(Errno::Noexec.into()));
661 return Err(WasiThreadError::ExportError(err));
662 }
663
664 if call_initialize && let Ok(initialize) = instance.exports.get_function("_initialize") {
666 let initialize_result = initialize.call(&mut store, &[]);
667 if let Err(err) = initialize_result {
668 func_env
669 .data(&store)
670 .blocking_on_exit(Some(Errno::Noexec.into()));
671 return Err(WasiThreadError::InitFailed(Arc::new(anyhow::Error::from(
672 err,
673 ))));
674 }
675 }
676
677 Ok((instance, func_env))
678 }
679
680 pub fn runtime(&self) -> &(dyn Runtime + Send + Sync) {
682 self.runtime.deref()
683 }
684
685 pub fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> {
687 self.tty.as_deref().or_else(|| self.runtime.tty())
688 }
689
690 pub fn tasks(&self) -> &Arc<dyn VirtualTaskManager> {
692 self.runtime.task_manager()
693 }
694
695 pub fn fs_root(&self) -> &WasiFsRoot {
696 &self.state.fs.root_fs
697 }
698
699 pub fn set_runtime<R>(&mut self, runtime: R)
701 where
702 R: Runtime + Send + Sync + 'static,
703 {
704 self.runtime = Arc::new(runtime);
705 }
706
707 pub fn active_threads(&self) -> u32 {
709 self.process.active_threads()
710 }
711
712 pub fn do_pending_operations(ctx: &mut FunctionEnvMut<'_, Self>) -> Result<(), WasiError> {
715 Self::do_pending_link_operations(ctx, true)?;
716 _ = Self::process_signals_and_exit(ctx)?;
717 Ok(())
718 }
719
720 pub fn do_pending_link_operations(
721 ctx: &mut FunctionEnvMut<'_, Self>,
722 fast: bool,
723 ) -> Result<(), WasiError> {
724 if let Some(linker) = ctx.data().inner().linker().cloned()
725 && let Err(e) = linker.do_pending_link_operations(ctx, fast)
726 {
727 tracing::warn!(err = ?e, "Failed to process pending link operations");
728 return Err(WasiError::Exit(Errno::Noexec.into()));
729 }
730 Ok(())
731 }
732
733 pub fn process_signals_and_exit(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
735 let env = ctx.data();
738 let env_inner = env
739 .try_inner()
740 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
741 let inner = env_inner.main_module_instance_handles();
742 let handler_registered = inner.signal_set
748 || env
749 .state
750 .signal_handler_registered
751 .load(std::sync::atomic::Ordering::SeqCst);
752 if !handler_registered {
753 let signals = env.thread.pop_signals();
754 if !signals.is_empty() {
755 for sig in signals {
756 if sig == Signal::Sigint
757 || sig == Signal::Sigquit
758 || sig == Signal::Sigkill
759 || sig == Signal::Sigabrt
760 || sig == Signal::Sigpipe
761 {
762 let exit_code = env.thread.set_or_get_exit_code_for_signal(sig);
763 return Err(WasiError::Exit(exit_code));
764 } else {
765 tracing::trace!(pid=%env.pid(), ?sig, "Signal ignored");
766 }
767 }
768 return Ok(Ok(true));
769 }
770 }
771
772 if let Some(forced_exit) = env.should_exit() {
774 return Err(WasiError::Exit(forced_exit));
775 }
776
777 Self::process_signals(ctx)
778 }
779
780 pub(crate) fn process_signals(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
782 let env = ctx.data();
785 let env_inner = env
786 .try_inner()
787 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
788 let inner = env_inner.main_module_instance_handles();
789 if !inner.signal_set {
790 let drained = !env.thread.pop_signals().is_empty();
796 return Ok(Ok(drained));
797 }
798
799 let ret = if inner.signal.as_ref().is_some() {
802 let signals = env.thread.pop_signals();
803 Self::process_signals_internal(ctx, signals)?
804 } else {
805 false
806 };
807
808 Ok(Ok(ret))
809 }
810
811 pub(crate) fn process_signals_internal(
812 ctx: &mut FunctionEnvMut<'_, Self>,
813 mut signals: Vec<Signal>,
814 ) -> Result<bool, WasiError> {
815 let env = ctx.data();
816 let env_inner = env
817 .try_inner()
818 .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
819 let inner = env_inner.main_module_instance_handles();
820 if let Some(handler) = inner.signal.clone() {
821 let mut now = 0;
823 {
824 let mut has_signal_interval = false;
825 let mut inner = env.process.inner.0.lock().unwrap();
826 if !inner.signal_intervals.is_empty() {
827 now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap()
828 as u128;
829 for signal in inner.signal_intervals.values() {
830 let elapsed = now - signal.last_signal;
831 if elapsed >= signal.interval.as_nanos() {
832 has_signal_interval = true;
833 break;
834 }
835 }
836 }
837 if has_signal_interval {
838 for signal in inner.signal_intervals.values_mut() {
839 let elapsed = now - signal.last_signal;
840 if elapsed >= signal.interval.as_nanos() {
841 signal.last_signal = now;
842 signals.push(signal.signal);
843 }
844 }
845 }
846 }
847
848 for signal in signals {
849 if matches!(signal, Signal::Sigwakeup) {
851 continue;
852 }
853
854 tracing::trace!(
855 pid=%ctx.data().pid(),
856 ?signal,
857 "processing signal via handler",
858 );
859 if let Err(err) = handler.call(ctx, signal as i32) {
860 match err.downcast::<WasiError>() {
861 Ok(wasi_err) => {
862 tracing::warn!(
863 pid=%ctx.data().pid(),
864 wasi_err=&wasi_err as &dyn std::error::Error,
865 "signal handler wasi error",
866 );
867 return Err(wasi_err);
868 }
869 Err(runtime_err) => {
870 if signal != Signal::Sigkill {
873 tracing::warn!(
874 pid=%ctx.data().pid(),
875 runtime_err=&runtime_err as &dyn std::error::Error,
876 "signal handler runtime error",
877 );
878 }
879 return Err(WasiError::Exit(Errno::Intr.into()));
880 }
881 }
882 }
883 tracing::trace!(
884 pid=%ctx.data().pid(),
885 "signal processed",
886 );
887 }
888 Ok(true)
889 } else {
890 tracing::trace!("no signal handler");
891 Ok(false)
892 }
893 }
894
895 pub fn should_exit(&self) -> Option<ExitCode> {
897 if let Some(forced_exit) = self.thread.try_join() {
899 return Some(forced_exit.unwrap_or_else(|err| {
900 tracing::debug!(
901 error = &*err as &dyn std::error::Error,
902 "exit runtime error",
903 );
904 Errno::Child.into()
905 }));
906 }
907 if let Some(forced_exit) = self.process.try_join() {
908 return Some(forced_exit.unwrap_or_else(|err| {
909 tracing::debug!(
910 error = &*err as &dyn std::error::Error,
911 "exit runtime error",
912 );
913 Errno::Child.into()
914 }));
915 }
916 None
917 }
918
919 pub fn net(&self) -> &DynVirtualNetworking {
921 self.runtime.networking()
922 }
923
924 pub(crate) fn inner(&self) -> WasiInstanceGuard<'_> {
927 self.inner.get().expect(
928 "You must initialize the WasiEnv before using it and can not pass it between threads",
929 )
930 }
931
932 pub(crate) fn inner_mut(&mut self) -> WasiInstanceGuardMut<'_> {
935 self.inner.get_mut().expect(
936 "You must initialize the WasiEnv before using it and can not pass it between threads",
937 )
938 }
939
940 pub(crate) fn try_inner(&self) -> Option<WasiInstanceGuard<'_>> {
942 self.inner.get()
943 }
944
945 #[allow(dead_code)]
948 pub(crate) fn try_inner_mut(&mut self) -> Option<WasiInstanceGuardMut<'_>> {
949 self.inner.get_mut()
950 }
951
952 #[doc(hidden)]
956 pub(crate) fn set_inner(&mut self, handles: WasiModuleTreeHandles) {
957 self.inner.set(handles);
958 self.refresh_deep_sleep_capability();
959 }
960
961 #[doc(hidden)]
965 pub(crate) fn swap_inner(&mut self, other: &mut Self) {
966 std::mem::swap(&mut self.inner, &mut other.inner);
967 }
968
969 pub(crate) fn ensure_static_module(&self) -> Result<(), ()> {
973 self.inner.get().unwrap().ensure_static_module()
974 }
975
976 pub fn try_clone_instance(&self) -> Option<Instance> {
979 let guard = self.inner.get();
980 match guard {
981 Some(guard) => guard
982 .static_module_instance_handles()
983 .map(|instance| instance.instance.clone()),
984 None => None,
985 }
986 }
987
988 pub fn try_memory(&self) -> Option<WasiInstanceGuardMemory<'_>> {
991 self.try_inner().map(|i| i.memory())
992 }
993
994 pub unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> {
1001 self.try_memory().expect(
1002 "You must initialize the WasiEnv before using it and can not pass it between threads",
1003 )
1004 }
1005
1006 pub fn try_memory_view<'a>(
1009 &self,
1010 store: &'a (impl AsStoreRef + ?Sized),
1011 ) -> Option<MemoryView<'a>> {
1012 self.try_memory().map(|m| m.view(store))
1013 }
1014
1015 pub unsafe fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
1022 self.try_memory_view(store).expect(
1023 "You must initialize the WasiEnv before using it and can not pass it between threads",
1024 )
1025 }
1026
1027 #[allow(dead_code)]
1030 pub(crate) fn try_memory_clone(&self) -> Option<Memory> {
1031 self.try_inner()
1032 .map(|i| i.main_module_instance_handles().memory_clone())
1033 }
1034
1035 pub(crate) fn state(&self) -> &WasiState {
1037 &self.state
1038 }
1039
1040 pub fn stdout(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1042 self.state.stdout()
1043 }
1044
1045 pub fn stderr(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1047 self.state.stderr()
1048 }
1049
1050 pub fn stdin(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1052 self.state.stdin()
1053 }
1054
1055 pub fn should_journal(&self) -> bool {
1057 self.enable_journal && !self.replaying_journal
1058 }
1059
1060 #[cfg(feature = "journal")]
1062 pub fn has_active_journal(&self) -> bool {
1063 self.runtime().active_journal().is_some()
1064 }
1065
1066 #[cfg(feature = "journal")]
1068 pub fn active_journal(&self) -> Result<&DynJournal, Errno> {
1069 self.runtime().active_journal().ok_or_else(|| {
1070 tracing::debug!("failed to save thread exit as there is not active journal");
1071 Errno::Fault
1072 })
1073 }
1074
1075 #[cfg(feature = "journal")]
1077 pub fn has_snapshot_trigger(&self, trigger: SnapshotTrigger) -> bool {
1078 let guard = self.process.inner.0.lock().unwrap();
1079 guard.snapshot_on.contains(&trigger)
1080 }
1081
1082 #[cfg(feature = "journal")]
1084 pub fn pop_snapshot_trigger(&mut self, trigger: SnapshotTrigger) -> bool {
1085 let mut guard = self.process.inner.0.lock().unwrap();
1086 if trigger.only_once() {
1087 guard.snapshot_on.remove(&trigger)
1088 } else {
1089 guard.snapshot_on.contains(&trigger)
1090 }
1091 }
1092
1093 pub fn std_dev_get(
1096 &self,
1097 fd: crate::syscalls::WasiFd,
1098 ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
1099 self.state.std_dev_get(fd)
1100 }
1101
1102 pub(crate) unsafe fn get_memory_and_wasi_state<'a>(
1108 &'a self,
1109 store: &'a impl AsStoreRef,
1110 _mem_index: u32,
1111 ) -> (MemoryView<'a>, &'a WasiState) {
1112 let memory = unsafe { self.memory_view(store) };
1113 let state = self.state.deref();
1114 (memory, state)
1115 }
1116
1117 pub(crate) unsafe fn get_memory_and_wasi_state_and_inodes<'a>(
1123 &'a self,
1124 store: &'a impl AsStoreRef,
1125 _mem_index: u32,
1126 ) -> (MemoryView<'a>, &'a WasiState, &'a WasiInodes) {
1127 let memory = unsafe { self.memory_view(store) };
1128 let state = self.state.deref();
1129 let inodes = &state.inodes;
1130 (memory, state, inodes)
1131 }
1132
1133 pub(crate) fn get_wasi_state_and_inodes(&self) -> (&WasiState, &WasiInodes) {
1134 let state = self.state.deref();
1135 let inodes = &state.inodes;
1136 (state, inodes)
1137 }
1138
1139 pub(crate) fn get_wasi_state(&self) -> &WasiState {
1140 self.state.deref()
1141 }
1142
1143 pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
1144 block_on(self.use_package_async(pkg))
1145 }
1146
1147 pub async fn use_package_async(
1161 &self,
1162 pkg: &BinaryPackage,
1163 ) -> Result<(), WasiStateCreationError> {
1164 tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment");
1165 let root_fs = &self.state.fs.root_fs;
1166
1167 if let Err(e) = self.state.fs.conditional_union(pkg).await {
1170 tracing::warn!(
1171 error = &e as &dyn std::error::Error,
1172 "Unable to merge the package mounts into the main filesystem",
1173 );
1174 }
1175
1176 if !pkg.commands.is_empty() {
1179 let _ = root_fs.create_dir(Path::new("/bin"));
1180 let _ = root_fs.create_dir(Path::new("/usr"));
1181 let _ = root_fs.create_dir(Path::new("/usr/bin"));
1182
1183 for command in &pkg.commands {
1184 let atom = command.atom();
1185 let mut package = pkg.clone();
1186 package.entrypoint_cmd = Some(command.name().to_string());
1187 let package_arc = Arc::new(package);
1188
1189 for directory in ["/bin", "/usr/bin"] {
1190 let path = format!("{directory}/{}", command.name());
1191 if let Err(err) =
1192 write_readonly_buffer_to_fs(root_fs, Path::new(&path), &atom).await
1193 {
1194 tracing::debug!(
1195 package=%pkg.id,
1196 command_name=command.name(),
1197 path,
1198 error=%err,
1199 "Failed to inject a command into the filesystem",
1200 );
1201 continue;
1202 }
1203
1204 self.bin_factory.set_binary(&path, &package_arc);
1207 tracing::debug!(
1208 package=%pkg.id,
1209 command_name=command.name(),
1210 path,
1211 "Injected a command into the filesystem",
1212 );
1213 }
1214 }
1215 }
1216
1217 Ok(())
1218 }
1219
1220 pub fn uses<I>(&self, uses: I) -> Result<(), WasiStateCreationError>
1223 where
1224 I: IntoIterator<Item = String>,
1225 {
1226 let rt = self.runtime();
1227
1228 for package_name in uses {
1229 let specifier = package_name.parse::<PackageSource>().map_err(|e| {
1230 WasiStateCreationError::WasiIncludePackageError(format!(
1231 "package_name={package_name}, {e}",
1232 ))
1233 })?;
1234 let pkg = block_on(BinaryPackage::from_registry(&specifier, rt)).map_err(|e| {
1235 WasiStateCreationError::WasiIncludePackageError(format!(
1236 "package_name={package_name}, {e}",
1237 ))
1238 })?;
1239 self.use_package(&pkg)?;
1240 }
1241
1242 Ok(())
1243 }
1244
1245 #[cfg(feature = "sys")]
1246 pub fn map_commands(
1247 &self,
1248 map_commands: std::collections::HashMap<String, std::path::PathBuf>,
1249 ) -> Result<(), WasiStateCreationError> {
1250 #[allow(unused_imports)]
1252 use std::path::Path;
1253
1254 use shared_buffer::OwnedBuffer;
1255 #[allow(unused_imports)]
1256 use virtual_fs::FileSystem;
1257
1258 #[cfg(feature = "sys")]
1259 for (command, target) in map_commands.iter() {
1260 let file = std::fs::read(target).map_err(|err| {
1262 WasiStateCreationError::WasiInheritError(format!(
1263 "failed to read local binary [{}] - {}",
1264 target.as_os_str().to_string_lossy(),
1265 err
1266 ))
1267 })?;
1268 let file = OwnedBuffer::from(file);
1269
1270 let path = format!("/bin/{command}");
1271 let path = Path::new(path.as_str());
1272 if let Err(err) = block_on(write_readonly_buffer_to_fs(
1273 &self.state.fs.root_fs,
1274 path,
1275 &file,
1276 )) {
1277 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1278 continue;
1279 }
1280
1281 let path = format!("/usr/bin/{command}");
1282 let path = Path::new(path.as_str());
1283 if let Err(err) = block_on(write_readonly_buffer_to_fs(
1284 &self.state.fs.root_fs,
1285 path,
1286 &file,
1287 )) {
1288 tracing::debug!("failed to add atom command [{}] - {}", command, err);
1289 continue;
1290 }
1291 }
1292 Ok(())
1293 }
1294
1295 #[allow(clippy::await_holding_lock)]
1297 pub fn blocking_on_exit(&self, process_exit_code: Option<ExitCode>) {
1298 let cleanup = self.on_exit(process_exit_code);
1299 block_on(cleanup);
1300 }
1301
1302 #[allow(clippy::await_holding_lock)]
1304 pub fn on_exit(&self, process_exit_code: Option<ExitCode>) -> BoxFuture<'static, ()> {
1305 const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
1306
1307 #[cfg(feature = "journal")]
1309 if self.should_journal() && self.has_active_journal() {
1310 if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), process_exit_code)
1311 {
1312 tracing::warn!("failed to save snapshot event for thread exit - {}", err);
1313 }
1314
1315 if self.thread.is_main()
1316 && let Err(err) = JournalEffector::save_process_exit(self, process_exit_code)
1317 {
1318 tracing::warn!("failed to save snapshot event for process exit - {}", err);
1319 }
1320 }
1321
1322 if let Some(process_exit_code) = process_exit_code {
1324 let process = self.process.clone();
1325 let disable_fs_cleanup = self.disable_fs_cleanup;
1326 let pid = self.pid();
1327
1328 let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT);
1329 let state = self.state.clone();
1330 Box::pin(async move {
1331 if process.try_start_cleanup() {
1332 if !disable_fs_cleanup {
1333 tracing::trace!(pid = %pid, "cleaning up open file handles");
1334
1335 tokio::select! {
1337 _ = timeout => {
1338 tracing::debug!(
1339 "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}"
1340 );
1341 },
1342 _ = state.fs.close_all() => { }
1343 }
1344 }
1345
1346 process.terminate(process_exit_code);
1350 process.signal_process(Signal::Sigquit);
1351 }
1352 })
1353 } else {
1354 Box::pin(async {})
1355 }
1356 }
1357
1358 pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) {
1359 if let Ok(Some(Wasi {
1360 main_args,
1361 env: env_vars,
1362 exec_name,
1363 ..
1364 })) = cmd.metadata().wasi()
1365 {
1366 if let Some(env_vars) = env_vars {
1367 add_command_env_defaults(&mut self.state.envs.lock().unwrap(), env_vars);
1368 }
1369
1370 if let Some(main_args) = main_args {
1371 let mut args: std::sync::MutexGuard<'_, Vec<String>> =
1372 self.state.args.lock().unwrap();
1373 args.splice(1..1, main_args);
1375 }
1376
1377 if let Some(exec_name) = exec_name {
1378 self.state.args.lock().unwrap()[0] = exec_name;
1379 }
1380 }
1381 }
1382}
1383
1384#[cfg(test)]
1385mod tests {
1386 use super::*;
1387 use once_cell::sync::OnceCell;
1388 use shared_buffer::OwnedBuffer;
1389 use virtual_fs::{RootFileSystemBuilder, TmpFileSystem};
1390 use wasmer::Engine;
1391 use wasmer_config::package::PackageId;
1392
1393 #[tokio::test]
1394 async fn command_alias_registration_is_independent() {
1395 let id = PackageId::new_named("test/command-args", "1.0.0".parse().unwrap());
1396 let command = BinaryPackageCommand::new(
1397 "pi".to_string(),
1398 serde_json::from_value(serde_json::json!({
1399 "runner": "wasi",
1400 "annotations": { "wasi": {
1401 "atom": "pi", "main-args": ["/opt/pi/cli.js"]
1402 } }
1403 }))
1404 .unwrap(),
1405 OwnedBuffer::from(b"\0asm\x01\0\0\0".to_vec()),
1406 ModuleHash::random(),
1407 None,
1408 id.clone(),
1409 id.clone(),
1410 );
1411 let package = BinaryPackage {
1412 id,
1413 package_ids: vec![],
1414 webc_version: webc::Version::V3,
1415 when_cached: None,
1416 entrypoint_cmd: Some("pi".to_string()),
1417 hash: OnceCell::new(),
1418 package_mounts: None,
1419 commands: vec![command],
1420 uses: vec![],
1421 additional_host_mapped_directories: vec![],
1422 };
1423
1424 for (mount, available, unavailable) in [
1428 ("/usr/local/ssl", &["/bin/pi", "/usr/bin/pi"][..], &[][..]),
1429 ("/usr/bin", &["/bin/pi"][..], &["/usr/bin/pi"][..]),
1430 ("/bin", &["/usr/bin/pi"][..], &["/bin/pi"][..]),
1431 ] {
1432 let env = WasiEnvBuilder::new("pi")
1433 .arg("--provider")
1434 .arg("openai")
1435 .engine(Engine::default())
1436 .fs(
1437 Arc::new(RootFileSystemBuilder::default().build_tmp_ext(&["/bin"]))
1438 as Arc<dyn FileSystem + Send + Sync>,
1439 )
1440 .build()
1441 .unwrap();
1442 let fs = &env.state.fs.root_fs;
1443 fs.root()
1444 .mount(Path::new(mount), Arc::new(TmpFileSystem::new()))
1445 .unwrap();
1446
1447 env.use_package_async(&package).await.unwrap();
1448 for path in unavailable {
1449 assert!(fs.metadata(Path::new(path)).is_err(), "{path}");
1450 assert!(
1451 env.bin_factory.get_binary(path, None).await.is_none(),
1452 "{path}"
1453 );
1454 }
1455 let mut installed = Vec::new();
1456 for path in available {
1457 assert!(fs.metadata(Path::new(path)).unwrap().is_file(), "{path}");
1458 installed.push(
1459 env.bin_factory
1460 .get_binary(path, None)
1461 .await
1462 .expect("a successfully installed alias must retain its package metadata"),
1463 );
1464 }
1465 env.prepare_spawn(installed[0].get_command("pi").unwrap());
1467 assert_eq!(
1468 *env.state.args.lock().unwrap(),
1469 ["pi", "/opt/pi/cli.js", "--provider", "openai"]
1470 );
1471 }
1472 }
1473
1474 #[test]
1475 fn spawned_command_environment_is_default_only() {
1476 let mut environment = vec![b"HOME=/workspace".to_vec(), b"PATH=/bin".to_vec()];
1477
1478 add_command_env_defaults(
1479 &mut environment,
1480 vec![
1481 "HOME=/package".to_owned(),
1482 "PREFIX=/".to_owned(),
1483 "PREFIX=/duplicate".to_owned(),
1484 ],
1485 );
1486
1487 assert_eq!(
1488 environment,
1489 vec![
1490 b"HOME=/workspace".to_vec(),
1491 b"PATH=/bin".to_vec(),
1492 b"PREFIX=/".to_vec(),
1493 ]
1494 );
1495 }
1496}