wasmer_wasix/state/
env.rs

1use std::{
2    collections::HashMap,
3    ops::Deref,
4    path::{Path, PathBuf},
5    str,
6    sync::Arc,
7    time::Duration,
8};
9
10use futures::future::BoxFuture;
11use rand::Rng;
12use virtual_fs::{FileSystem, FsError, VirtualFile};
13use virtual_net::DynVirtualNetworking;
14use wasmer::{
15    AsStoreMut, AsStoreRef, ExportError, FunctionEnvMut, Instance, Memory, MemoryType, MemoryView,
16    Module,
17};
18use wasmer_config::package::PackageSource;
19use wasmer_wasix_types::{
20    types::Signal,
21    wasi::{Errno, ExitCode, Snapshot0Clockid},
22    wasix::ThreadStartType,
23};
24use webc::metadata::annotations::Wasi;
25
26#[cfg(feature = "journal")]
27use crate::journal::{DynJournal, JournalEffector, SnapshotTrigger};
28use crate::{
29    Runtime, VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, WasiFunctionEnv,
30    WasiResult, WasiRuntimeError, WasiStateCreationError, WasiThreadError, WasiVFork,
31    bin_factory::{BinFactory, BinaryPackage, BinaryPackageCommand},
32    capabilities::Capabilities,
33    fs::{WasiFsRoot, WasiInodes},
34    import_object_for_all_wasi_versions,
35    os::task::{
36        control_plane::ControlPlaneError,
37        process::{WasiProcess, WasiProcessId},
38        thread::{WasiMemoryLayout, WasiThread, WasiThreadHandle, WasiThreadId},
39    },
40    runtime::task_manager::InlineWaker,
41    syscalls::platform_clock_time_get,
42};
43use wasmer_types::ModuleHash;
44
45pub use super::handles::*;
46use super::{Linker, WasiState, conv_env_vars};
47
48/// Data required to construct a [`WasiEnv`].
49#[derive(Debug)]
50pub struct WasiEnvInit {
51    pub(crate) state: WasiState,
52    pub runtime: Arc<dyn Runtime + Send + Sync>,
53    pub webc_dependencies: Vec<BinaryPackage>,
54    pub mapped_commands: HashMap<String, PathBuf>,
55    pub bin_factory: BinFactory,
56    pub capabilities: Capabilities,
57
58    pub control_plane: WasiControlPlane,
59    pub memory_ty: Option<MemoryType>,
60    pub process: Option<WasiProcess>,
61    pub thread: Option<WasiThreadHandle>,
62
63    /// Whether to call the `_initialize` function in the WASI module.
64    /// Will be true for regular new instances, but false for threads.
65    pub call_initialize: bool,
66
67    /// Indicates if the calling environment is capable of deep sleeping
68    pub can_deep_sleep: bool,
69
70    /// Indicates if extra tracing should be output
71    pub extra_tracing: bool,
72
73    /// Indicates triggers that will cause a snapshot to be taken
74    #[cfg(feature = "journal")]
75    pub snapshot_on: Vec<SnapshotTrigger>,
76
77    /// Stop running after the first snapshot is taken
78    #[cfg(feature = "journal")]
79    pub stop_running_after_snapshot: bool,
80
81    /// Skip writes to stdout and stderr when bootstrapping from a journal
82    pub skip_stdio_during_bootstrap: bool,
83}
84
85impl WasiEnvInit {
86    pub fn duplicate(&self) -> Self {
87        let inodes = WasiInodes::new();
88
89        // TODO: preserve preopens?
90        let fs =
91            crate::fs::WasiFs::new_with_preopen(&inodes, &[], &[], self.state.fs.root_fs.clone())
92                .unwrap();
93
94        Self {
95            state: WasiState {
96                secret: rand::thread_rng().r#gen::<[u8; 32]>(),
97                inodes,
98                fs,
99                futexs: Default::default(),
100                clock_offset: std::sync::Mutex::new(
101                    self.state.clock_offset.lock().unwrap().clone(),
102                ),
103                args: std::sync::Mutex::new(self.state.args.lock().unwrap().clone()),
104                envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()),
105                signals: std::sync::Mutex::new(self.state.signals.lock().unwrap().deref().clone()),
106                preopen: self.state.preopen.clone(),
107            },
108            runtime: self.runtime.clone(),
109            webc_dependencies: self.webc_dependencies.clone(),
110            mapped_commands: self.mapped_commands.clone(),
111            bin_factory: self.bin_factory.clone(),
112            capabilities: self.capabilities.clone(),
113            control_plane: self.control_plane.clone(),
114            memory_ty: None,
115            process: None,
116            thread: None,
117            call_initialize: self.call_initialize,
118            can_deep_sleep: self.can_deep_sleep,
119            extra_tracing: false,
120            #[cfg(feature = "journal")]
121            snapshot_on: self.snapshot_on.clone(),
122            #[cfg(feature = "journal")]
123            stop_running_after_snapshot: self.stop_running_after_snapshot,
124            skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
125        }
126    }
127}
128
129/// The environment provided to the WASI imports.
130pub struct WasiEnv {
131    pub control_plane: WasiControlPlane,
132    /// Represents the process this environment is attached to
133    pub process: WasiProcess,
134    /// Represents the thread this environment is attached to
135    pub thread: WasiThread,
136    /// Represents the layout of the memory
137    pub layout: WasiMemoryLayout,
138    /// Represents a fork of the process that is currently in play
139    pub vfork: Option<WasiVFork>,
140    /// Seed used to rotate around the events returned by `poll_oneoff`
141    pub poll_seed: u64,
142    /// Shared state of the WASI system. Manages all the data that the
143    /// executing WASI program can see.
144    pub(crate) state: Arc<WasiState>,
145    /// Binary factory attached to this environment
146    pub bin_factory: BinFactory,
147    /// List of the handles that are owned by this context
148    /// (this can be used to ensure that threads own themselves or others)
149    pub owned_handles: Vec<WasiThreadHandle>,
150    /// Implementation of the WASI runtime.
151    pub runtime: Arc<dyn Runtime + Send + Sync + 'static>,
152
153    pub capabilities: Capabilities,
154
155    /// Is this environment capable and setup for deep sleeping
156    pub enable_deep_sleep: bool,
157
158    /// Enables the snap shotting functionality
159    pub enable_journal: bool,
160
161    /// Enables an exponential backoff of the process CPU usage when there
162    /// are no active run tokens (when set holds the maximum amount of
163    /// time that it will pause the CPU)
164    pub enable_exponential_cpu_backoff: Option<Duration>,
165
166    /// Flag that indicates if the environment is currently replaying the journal
167    /// (and hence it should not record new events)
168    pub replaying_journal: bool,
169
170    /// Should stdio be skipped when bootstrapping this module from an existing journal?
171    pub skip_stdio_during_bootstrap: bool,
172
173    /// Flag that indicates the cleanup of the environment is to be disabled
174    /// (this is normally used so that the instance can be reused later on)
175    pub(crate) disable_fs_cleanup: bool,
176
177    /// Inner functions and references that are loaded before the environment starts
178    /// (inner is not safe to send between threads and so it is private and will
179    ///  not be cloned when `WasiEnv` is cloned)
180    /// TODO: We should move this outside of `WasiEnv` with some refactoring
181    inner: WasiInstanceHandlesPointer,
182}
183
184impl std::fmt::Debug for WasiEnv {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        write!(f, "env(pid={}, tid={})", self.pid().raw(), self.tid().raw())
187    }
188}
189
190impl Clone for WasiEnv {
191    fn clone(&self) -> Self {
192        Self {
193            control_plane: self.control_plane.clone(),
194            process: self.process.clone(),
195            poll_seed: self.poll_seed,
196            thread: self.thread.clone(),
197            layout: self.layout.clone(),
198            vfork: self.vfork.clone(),
199            state: self.state.clone(),
200            bin_factory: self.bin_factory.clone(),
201            inner: Default::default(),
202            owned_handles: self.owned_handles.clone(),
203            runtime: self.runtime.clone(),
204            capabilities: self.capabilities.clone(),
205            enable_deep_sleep: self.enable_deep_sleep,
206            enable_journal: self.enable_journal,
207            enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
208            replaying_journal: self.replaying_journal,
209            skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
210            disable_fs_cleanup: self.disable_fs_cleanup,
211        }
212    }
213}
214
215impl WasiEnv {
216    /// Construct a new [`WasiEnvBuilder`] that allows customizing an environment.
217    pub fn builder(program_name: impl Into<String>) -> WasiEnvBuilder {
218        WasiEnvBuilder::new(program_name)
219    }
220
221    /// Forking the WasiState is used when either fork or vfork is called
222    pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> {
223        let process = self.control_plane.new_process(self.process.module_hash)?;
224        let handle = process.new_thread(self.layout.clone(), ThreadStartType::MainThread)?;
225
226        let thread = handle.as_thread();
227        thread.copy_stack_from(&self.thread);
228
229        let state = Arc::new(self.state.fork());
230
231        let bin_factory = self.bin_factory.clone();
232
233        let new_env = Self {
234            control_plane: self.control_plane.clone(),
235            process,
236            thread,
237            layout: self.layout.clone(),
238            vfork: None,
239            poll_seed: 0,
240            bin_factory,
241            state,
242            inner: Default::default(),
243            owned_handles: Vec::new(),
244            runtime: self.runtime.clone(),
245            capabilities: self.capabilities.clone(),
246            enable_deep_sleep: self.enable_deep_sleep,
247            enable_journal: self.enable_journal,
248            enable_exponential_cpu_backoff: self.enable_exponential_cpu_backoff,
249            replaying_journal: false,
250            skip_stdio_during_bootstrap: self.skip_stdio_during_bootstrap,
251            disable_fs_cleanup: self.disable_fs_cleanup,
252        };
253        Ok((new_env, handle))
254    }
255
256    pub fn pid(&self) -> WasiProcessId {
257        self.process.pid()
258    }
259
260    pub fn tid(&self) -> WasiThreadId {
261        self.thread.tid()
262    }
263
264    /// Returns true if this WASM process will need and try to use
265    /// asyncify while its running which normally means.
266    pub fn will_use_asyncify(&self) -> bool {
267        self.inner()
268            .static_module_instance_handles()
269            .map(|handles| self.enable_deep_sleep || handles.has_stack_checkpoint)
270            .unwrap_or(false)
271    }
272
273    /// Re-initializes this environment so that it can be executed again
274    pub fn reinit(&mut self) -> Result<(), WasiStateCreationError> {
275        // If the cleanup logic is enabled then we need to rebuild the
276        // file descriptors which would have been destroyed when the
277        // main thread exited
278        if !self.disable_fs_cleanup {
279            // First we clear any open files as the descriptors would
280            // otherwise clash
281            if let Ok(mut map) = self.state.fs.fd_map.write() {
282                map.clear();
283            }
284            self.state.fs.preopen_fds.write().unwrap().clear();
285            *self.state.fs.current_dir.lock().unwrap() = "/".to_string();
286
287            // We need to rebuild the basic file descriptors
288            self.state.fs.create_stdin(&self.state.inodes);
289            self.state.fs.create_stdout(&self.state.inodes);
290            self.state.fs.create_stderr(&self.state.inodes);
291            self.state
292                .fs
293                .create_rootfd()
294                .map_err(WasiStateCreationError::WasiFsSetupError)?;
295            self.state
296                .fs
297                .create_preopens(&self.state.inodes, true)
298                .map_err(WasiStateCreationError::WasiFsSetupError)?;
299        }
300
301        // The process and thread state need to be reset
302        self.process = WasiProcess::new(
303            self.process.pid,
304            self.process.module_hash,
305            self.process.compute.clone(),
306        );
307        self.thread = WasiThread::new(
308            self.thread.pid(),
309            self.thread.tid(),
310            self.thread.is_main(),
311            self.process.finished.clone(),
312            self.process.compute.must_upgrade().register_task()?,
313            self.thread.memory_layout().clone(),
314            self.thread.thread_start_type(),
315        );
316
317        Ok(())
318    }
319
320    /// Returns true if this module is capable of deep sleep
321    /// (needs asyncify to unwind and rewind)
322    ///
323    /// # Safety
324    ///
325    /// This function should only be called from within a syscall
326    /// as it accessed objects that are a thread local (functions)
327    pub unsafe fn capable_of_deep_sleep(&self) -> bool {
328        if !self.control_plane.config().enable_asynchronous_threading {
329            return false;
330        }
331        self.inner()
332            .static_module_instance_handles()
333            .map(|handles| {
334                handles.asyncify_get_state.is_some()
335                    && handles.asyncify_start_rewind.is_some()
336                    && handles.asyncify_start_unwind.is_some()
337            })
338            .unwrap_or(false)
339    }
340
341    /// Returns true if this thread can go into a deep sleep
342    pub fn layout(&self) -> &WasiMemoryLayout {
343        &self.layout
344    }
345
346    #[allow(clippy::result_large_err)]
347    pub(crate) fn from_init(
348        init: WasiEnvInit,
349        module_hash: ModuleHash,
350    ) -> Result<Self, WasiRuntimeError> {
351        let process = if let Some(p) = init.process {
352            p
353        } else {
354            init.control_plane.new_process(module_hash)?
355        };
356
357        #[cfg(feature = "journal")]
358        {
359            let mut guard = process.inner.0.lock().unwrap();
360            guard.snapshot_on = init.snapshot_on.into_iter().collect();
361            guard.stop_running_after_checkpoint = init.stop_running_after_snapshot;
362        }
363
364        let layout = WasiMemoryLayout::default();
365        let thread = if let Some(t) = init.thread {
366            t
367        } else {
368            process.new_thread(layout.clone(), ThreadStartType::MainThread)?
369        };
370
371        let mut env = Self {
372            control_plane: init.control_plane,
373            process,
374            thread: thread.as_thread(),
375            layout,
376            vfork: None,
377            poll_seed: 0,
378            state: Arc::new(init.state),
379            inner: Default::default(),
380            owned_handles: Vec::new(),
381            #[cfg(feature = "journal")]
382            enable_journal: init.runtime.active_journal().is_some(),
383            #[cfg(not(feature = "journal"))]
384            enable_journal: false,
385            replaying_journal: false,
386            skip_stdio_during_bootstrap: init.skip_stdio_during_bootstrap,
387            enable_deep_sleep: init.capabilities.threading.enable_asynchronous_threading,
388            enable_exponential_cpu_backoff: init
389                .capabilities
390                .threading
391                .enable_exponential_cpu_backoff,
392            runtime: init.runtime,
393            bin_factory: init.bin_factory,
394            capabilities: init.capabilities,
395            disable_fs_cleanup: false,
396        };
397        env.owned_handles.push(thread);
398
399        // TODO: should not be here - should be callers responsibility!
400        for pkg in &init.webc_dependencies {
401            env.use_package(pkg)?;
402        }
403
404        #[cfg(feature = "sys")]
405        env.map_commands(init.mapped_commands.clone())?;
406
407        Ok(env)
408    }
409
410    // FIXME: use custom error type
411    #[allow(clippy::result_large_err)]
412    pub(crate) fn instantiate(
413        self,
414        module: Module,
415        store: &mut impl AsStoreMut,
416        memory: Option<Memory>,
417        update_layout: bool,
418        call_initialize: bool,
419        parent_linker_and_ctx: Option<(Linker, &mut FunctionEnvMut<WasiEnv>)>,
420    ) -> Result<(Instance, WasiFunctionEnv), WasiThreadError> {
421        let pid = self.process.pid();
422
423        let mut store = store.as_store_mut();
424        let engine = self.runtime().engine();
425        let mut func_env = WasiFunctionEnv::new(&mut store, self);
426
427        let is_dl = super::linker::is_dynamically_linked(&module);
428        if is_dl {
429            let linker = match parent_linker_and_ctx {
430                Some((linker, ctx)) => linker.create_instance_group(ctx, &mut store, &mut func_env),
431                None => {
432                    // FIXME: should we be storing envs as raw byte arrays?
433                    let ld_library_path_owned;
434                    let ld_library_path = {
435                        let envs = func_env.data(&store).state.envs.lock().unwrap();
436                        ld_library_path_owned = match envs
437                            .iter()
438                            .find_map(|env| env.strip_prefix(b"LD_LIBRARY_PATH="))
439                        {
440                            Some(path) => path
441                                .split(|b| *b == b':')
442                                .filter_map(|p| str::from_utf8(p).ok())
443                                .map(PathBuf::from)
444                                .collect::<Vec<_>>(),
445                            None => vec![],
446                        };
447                        ld_library_path_owned
448                            .iter()
449                            .map(AsRef::as_ref)
450                            .collect::<Vec<_>>()
451                    };
452
453                    // TODO: make stack size configurable
454                    Linker::new(
455                        engine,
456                        &module,
457                        &mut store,
458                        memory,
459                        &mut func_env,
460                        8 * 1024 * 1024,
461                        &ld_library_path,
462                    )
463                }
464            };
465
466            match linker {
467                Ok((_, linked_module)) => {
468                    return Ok((linked_module.instance, func_env));
469                }
470                Err(e) => {
471                    tracing::error!(
472                        %pid,
473                        error = &e as &dyn std::error::Error,
474                        "Failed to link DL main module",
475                    );
476                    func_env
477                        .data(&store)
478                        .blocking_on_exit(Some(Errno::Noexec.into()));
479                    return Err(WasiThreadError::LinkError(Arc::new(e)));
480                }
481            }
482        }
483
484        // Let's instantiate the module with the imports.
485        let mut import_object =
486            import_object_for_all_wasi_versions(&module, &mut store, &func_env.env);
487
488        let imported_memory = if let Some(memory) = memory {
489            import_object.define("env", "memory", memory.clone());
490            Some(memory)
491        } else {
492            None
493        };
494
495        // Construct the instance.
496        let instance = match Instance::new(&mut store, &module, &import_object) {
497            Ok(a) => a,
498            Err(err) => {
499                tracing::error!(
500                    %pid,
501                    error = &err as &dyn std::error::Error,
502                    "Instantiation failed",
503                );
504                func_env
505                    .data(&store)
506                    .blocking_on_exit(Some(Errno::Noexec.into()));
507                return Err(WasiThreadError::InstanceCreateFailed(Box::new(err)));
508            }
509        };
510
511        let handles = match imported_memory {
512            Some(memory) => WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
513                memory,
514                &store,
515                instance.clone(),
516                None,
517            )),
518            None => {
519                let exported_memory = instance
520                    .exports
521                    .iter()
522                    .filter_map(|(_, export)| {
523                        if let wasmer::Extern::Memory(memory) = export {
524                            Some(memory.clone())
525                        } else {
526                            None
527                        }
528                    })
529                    .next()
530                    .ok_or(WasiThreadError::ExportError(ExportError::Missing(
531                        "No imported or exported memory found".to_owned(),
532                    )))?;
533                WasiModuleTreeHandles::Static(WasiModuleInstanceHandles::new(
534                    exported_memory,
535                    &store,
536                    instance.clone(),
537                    None,
538                ))
539            }
540        };
541
542        // Initialize the WASI environment
543        if let Err(err) = func_env.initialize_handles_and_layout(
544            &mut store,
545            instance.clone(),
546            handles,
547            None,
548            update_layout,
549        ) {
550            tracing::error!(
551                %pid,
552                error = &err as &dyn std::error::Error,
553                "Initialization failed",
554            );
555            func_env
556                .data(&store)
557                .blocking_on_exit(Some(Errno::Noexec.into()));
558            return Err(WasiThreadError::ExportError(err));
559        }
560
561        // If this module exports an _initialize function, run that first.
562        if call_initialize {
563            if let Ok(initialize) = instance.exports.get_function("_initialize") {
564                if let Err(err) = crate::run_wasi_func_start(initialize, &mut store) {
565                    func_env
566                        .data(&store)
567                        .blocking_on_exit(Some(Errno::Noexec.into()));
568                    return Err(WasiThreadError::InitFailed(Arc::new(anyhow::Error::from(
569                        err,
570                    ))));
571                }
572            }
573        }
574
575        Ok((instance, func_env))
576    }
577
578    /// Returns a copy of the current runtime implementation for this environment
579    pub fn runtime(&self) -> &(dyn Runtime + Send + Sync) {
580        self.runtime.deref()
581    }
582
583    /// Returns a copy of the current tasks implementation for this environment
584    pub fn tasks(&self) -> &Arc<dyn VirtualTaskManager> {
585        self.runtime.task_manager()
586    }
587
588    pub fn fs_root(&self) -> &WasiFsRoot {
589        &self.state.fs.root_fs
590    }
591
592    /// Overrides the runtime implementation for this environment
593    pub fn set_runtime<R>(&mut self, runtime: R)
594    where
595        R: Runtime + Send + Sync + 'static,
596    {
597        self.runtime = Arc::new(runtime);
598    }
599
600    /// Returns the number of active threads
601    pub fn active_threads(&self) -> u32 {
602        self.process.active_threads()
603    }
604
605    /// Called by most (if not all) syscalls to process pending operations that are
606    /// cross-cutting, such as signals, thread/process exit, DL operations, etc.
607    pub fn do_pending_operations(ctx: &mut FunctionEnvMut<'_, Self>) -> Result<(), WasiError> {
608        Self::do_pending_link_operations(ctx, true)?;
609        _ = Self::process_signals_and_exit(ctx)?;
610        Ok(())
611    }
612
613    pub fn do_pending_link_operations(
614        ctx: &mut FunctionEnvMut<'_, Self>,
615        fast: bool,
616    ) -> Result<(), WasiError> {
617        if let Some(linker) = ctx.data().inner().linker().cloned() {
618            if let Err(e) = linker.do_pending_link_operations(ctx, fast) {
619                tracing::warn!(err = ?e, "Failed to process pending link operations");
620                return Err(WasiError::Exit(Errno::Noexec.into()));
621            }
622        }
623        Ok(())
624    }
625
626    /// Porcesses any signals that are batched up or any forced exit codes
627    pub fn process_signals_and_exit(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
628        // If a signal handler has never been set then we need to handle signals
629        // differently
630        let env = ctx.data();
631        let env_inner = env
632            .try_inner()
633            .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
634        let inner = env_inner.main_module_instance_handles();
635        if !inner.signal_set {
636            let signals = env.thread.pop_signals();
637            if !signals.is_empty() {
638                for sig in signals {
639                    if sig == Signal::Sigint
640                        || sig == Signal::Sigquit
641                        || sig == Signal::Sigkill
642                        || sig == Signal::Sigabrt
643                        || sig == Signal::Sigpipe
644                    {
645                        let exit_code = env.thread.set_or_get_exit_code_for_signal(sig);
646                        return Err(WasiError::Exit(exit_code));
647                    } else {
648                        tracing::trace!(pid=%env.pid(), ?sig, "Signal ignored");
649                    }
650                }
651                return Ok(Ok(true));
652            }
653        }
654
655        // Check for forced exit
656        if let Some(forced_exit) = env.should_exit() {
657            return Err(WasiError::Exit(forced_exit));
658        }
659
660        Self::process_signals(ctx)
661    }
662
663    /// Porcesses any signals that are batched up
664    pub(crate) fn process_signals(ctx: &mut FunctionEnvMut<'_, Self>) -> WasiResult<bool> {
665        // If a signal handler has never been set then we need to handle signals
666        // differently
667        let env = ctx.data();
668        let env_inner = env
669            .try_inner()
670            .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
671        let inner = env_inner.main_module_instance_handles();
672        if !inner.signal_set {
673            return Ok(Ok(false));
674        }
675
676        // Check for any signals that we need to trigger
677        // (but only if a signal handler is registered)
678        let ret = if inner.signal.as_ref().is_some() {
679            let signals = env.thread.pop_signals();
680            Self::process_signals_internal(ctx, signals)?
681        } else {
682            false
683        };
684
685        Ok(Ok(ret))
686    }
687
688    pub(crate) fn process_signals_internal(
689        ctx: &mut FunctionEnvMut<'_, Self>,
690        mut signals: Vec<Signal>,
691    ) -> Result<bool, WasiError> {
692        let env = ctx.data();
693        let env_inner = env
694            .try_inner()
695            .ok_or_else(|| WasiError::Exit(Errno::Fault.into()))?;
696        let inner = env_inner.main_module_instance_handles();
697        if let Some(handler) = inner.signal.clone() {
698            // We might also have signals that trigger on timers
699            let mut now = 0;
700            {
701                let mut has_signal_interval = false;
702                let inner = env.process.inner.0.lock().unwrap();
703                if !inner.signal_intervals.is_empty() {
704                    now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap()
705                        as u128;
706                    for signal in inner.signal_intervals.values() {
707                        let elapsed = now - signal.last_signal;
708                        if elapsed >= signal.interval.as_nanos() {
709                            has_signal_interval = true;
710                            break;
711                        }
712                    }
713                }
714                if has_signal_interval {
715                    let mut inner = env.process.inner.0.lock().unwrap();
716                    for signal in inner.signal_intervals.values_mut() {
717                        let elapsed = now - signal.last_signal;
718                        if elapsed >= signal.interval.as_nanos() {
719                            signal.last_signal = now;
720                            signals.push(signal.signal);
721                        }
722                    }
723                }
724            }
725
726            for signal in signals {
727                // Skip over Sigwakeup, which is host-side-only
728                if matches!(signal, Signal::Sigwakeup) {
729                    continue;
730                }
731
732                tracing::trace!(
733                    pid=%ctx.data().pid(),
734                    ?signal,
735                    "processing signal via handler",
736                );
737                if let Err(err) = handler.call(ctx, signal as i32) {
738                    match err.downcast::<WasiError>() {
739                        Ok(wasi_err) => {
740                            tracing::warn!(
741                                pid=%ctx.data().pid(),
742                                wasi_err=&wasi_err as &dyn std::error::Error,
743                                "signal handler wasi error",
744                            );
745                            return Err(wasi_err);
746                        }
747                        Err(runtime_err) => {
748                            // anything other than a kill command should report
749                            // the error, killed things may not gracefully close properly
750                            if signal != Signal::Sigkill {
751                                tracing::warn!(
752                                    pid=%ctx.data().pid(),
753                                    runtime_err=&runtime_err as &dyn std::error::Error,
754                                    "signal handler runtime error",
755                                );
756                            }
757                            return Err(WasiError::Exit(Errno::Intr.into()));
758                        }
759                    }
760                }
761                tracing::trace!(
762                    pid=%ctx.data().pid(),
763                    "signal processed",
764                );
765            }
766            Ok(true)
767        } else {
768            tracing::trace!("no signal handler");
769            Ok(false)
770        }
771    }
772
773    /// Returns an exit code if the thread or process has been forced to exit
774    pub fn should_exit(&self) -> Option<ExitCode> {
775        // Check for forced exit
776        if let Some(forced_exit) = self.thread.try_join() {
777            return Some(forced_exit.unwrap_or_else(|err| {
778                tracing::debug!(
779                    error = &*err as &dyn std::error::Error,
780                    "exit runtime error",
781                );
782                Errno::Child.into()
783            }));
784        }
785        if let Some(forced_exit) = self.process.try_join() {
786            return Some(forced_exit.unwrap_or_else(|err| {
787                tracing::debug!(
788                    error = &*err as &dyn std::error::Error,
789                    "exit runtime error",
790                );
791                Errno::Child.into()
792            }));
793        }
794        None
795    }
796
797    /// Accesses the virtual networking implementation
798    pub fn net(&self) -> &DynVirtualNetworking {
799        self.runtime.networking()
800    }
801
802    /// Providers safe access to the initialized part of WasiEnv
803    /// (it must be initialized before it can be used)
804    pub(crate) fn inner(&self) -> WasiInstanceGuard<'_> {
805        self.inner.get().expect(
806            "You must initialize the WasiEnv before using it and can not pass it between threads",
807        )
808    }
809
810    /// Provides safe access to the initialized part of WasiEnv
811    /// (it must be initialized before it can be used)
812    pub(crate) fn inner_mut(&mut self) -> WasiInstanceGuardMut<'_> {
813        self.inner.get_mut().expect(
814            "You must initialize the WasiEnv before using it and can not pass it between threads",
815        )
816    }
817
818    /// Providers safe access to the initialized part of WasiEnv
819    pub(crate) fn try_inner(&self) -> Option<WasiInstanceGuard<'_>> {
820        self.inner.get()
821    }
822
823    /// Providers safe access to the initialized part of WasiEnv
824    /// (it must be initialized before it can be used)
825    #[allow(dead_code)]
826    pub(crate) fn try_inner_mut(&mut self) -> Option<WasiInstanceGuardMut<'_>> {
827        self.inner.get_mut()
828    }
829
830    /// Sets the inner object (this should only be called when
831    /// creating the instance and eventually should be moved out
832    /// of the WasiEnv)
833    #[doc(hidden)]
834    pub(crate) fn set_inner(&mut self, handles: WasiModuleTreeHandles) {
835        self.inner.set(handles)
836    }
837
838    /// Swaps this inner with the WasiEnvironment of another, this
839    /// is used by the vfork so that the inner handles can be restored
840    /// after the vfork finishes.
841    #[doc(hidden)]
842    pub(crate) fn swap_inner(&mut self, other: &mut Self) {
843        std::mem::swap(&mut self.inner, &mut other.inner);
844    }
845
846    /// Helper function to ensure the module isn't dynamically linked, needed since
847    /// we only support a subset of WASIX functionality for dynamically linked modules.
848    /// Specifically, anything that requires asyncify is not supported right now.
849    pub(crate) fn ensure_static_module(&self) -> Result<(), ()> {
850        self.inner.get().unwrap().ensure_static_module()
851    }
852
853    /// Tries to clone the instance from this environment, but only if it's a static
854    /// module, since dynamically linked modules are made up of multiple instances.
855    pub fn try_clone_instance(&self) -> Option<Instance> {
856        let guard = self.inner.get();
857        match guard {
858            Some(guard) => guard
859                .static_module_instance_handles()
860                .map(|instance| instance.instance.clone()),
861            None => None,
862        }
863    }
864
865    /// Providers safe access to the memory
866    /// (it must be initialized before it can be used)
867    pub fn try_memory(&self) -> Option<WasiInstanceGuardMemory<'_>> {
868        self.try_inner().map(|i| i.memory())
869    }
870
871    /// Providers safe access to the memory
872    /// (it must be initialized before it can be used)
873    ///
874    /// # Safety
875    /// This has been marked as unsafe as it will panic if its executed
876    /// on the wrong thread or before the inner is set
877    pub unsafe fn memory(&self) -> WasiInstanceGuardMemory<'_> {
878        self.try_memory().expect(
879            "You must initialize the WasiEnv before using it and can not pass it between threads",
880        )
881    }
882
883    /// Providers safe access to the memory
884    /// (it must be initialized before it can be used)
885    pub fn try_memory_view<'a>(
886        &self,
887        store: &'a (impl AsStoreRef + ?Sized),
888    ) -> Option<MemoryView<'a>> {
889        self.try_memory().map(|m| m.view(store))
890    }
891
892    /// Providers safe access to the memory
893    /// (it must be initialized before it can be used)
894    ///
895    /// # Safety
896    /// This has been marked as unsafe as it will panic if its executed
897    /// on the wrong thread or before the inner is set
898    pub unsafe fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
899        self.try_memory_view(store).expect(
900            "You must initialize the WasiEnv before using it and can not pass it between threads",
901        )
902    }
903
904    /// Copy the lazy reference so that when it's initialized during the
905    /// export phase, all the other references get a copy of it
906    #[allow(dead_code)]
907    pub(crate) fn try_memory_clone(&self) -> Option<Memory> {
908        self.try_inner()
909            .map(|i| i.main_module_instance_handles().memory_clone())
910    }
911
912    /// Get the WASI state
913    pub(crate) fn state(&self) -> &WasiState {
914        &self.state
915    }
916
917    /// Get the `VirtualFile` object at stdout
918    pub fn stdout(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
919        self.state.stdout()
920    }
921
922    /// Get the `VirtualFile` object at stderr
923    pub fn stderr(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
924        self.state.stderr()
925    }
926
927    /// Get the `VirtualFile` object at stdin
928    pub fn stdin(&self) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
929        self.state.stdin()
930    }
931
932    /// Returns true if the process should perform snapshots or not
933    pub fn should_journal(&self) -> bool {
934        self.enable_journal && !self.replaying_journal
935    }
936
937    /// Returns true if the environment has an active journal
938    #[cfg(feature = "journal")]
939    pub fn has_active_journal(&self) -> bool {
940        self.runtime().active_journal().is_some()
941    }
942
943    /// Returns the active journal or fails with an error
944    #[cfg(feature = "journal")]
945    pub fn active_journal(&self) -> Result<&DynJournal, Errno> {
946        self.runtime().active_journal().ok_or_else(|| {
947            tracing::debug!("failed to save thread exit as there is not active journal");
948            Errno::Fault
949        })
950    }
951
952    /// Returns true if a particular snapshot trigger is enabled
953    #[cfg(feature = "journal")]
954    pub fn has_snapshot_trigger(&self, trigger: SnapshotTrigger) -> bool {
955        let guard = self.process.inner.0.lock().unwrap();
956        guard.snapshot_on.contains(&trigger)
957    }
958
959    /// Returns true if a particular snapshot trigger is enabled
960    #[cfg(feature = "journal")]
961    pub fn pop_snapshot_trigger(&mut self, trigger: SnapshotTrigger) -> bool {
962        let mut guard = self.process.inner.0.lock().unwrap();
963        if trigger.only_once() {
964            guard.snapshot_on.remove(&trigger)
965        } else {
966            guard.snapshot_on.contains(&trigger)
967        }
968    }
969
970    /// Internal helper function to get a standard device handle.
971    /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`.
972    pub fn std_dev_get(
973        &self,
974        fd: crate::syscalls::WasiFd,
975    ) -> Result<Option<Box<dyn VirtualFile + Send + Sync + 'static>>, FsError> {
976        self.state.std_dev_get(fd)
977    }
978
979    /// Unsafe:
980    ///
981    /// This will access the memory of the WASM process and create a view into it which is
982    /// inherently unsafe as it could corrupt the memory. Also accessing the memory is not
983    /// thread safe.
984    pub(crate) unsafe fn get_memory_and_wasi_state<'a>(
985        &'a self,
986        store: &'a impl AsStoreRef,
987        _mem_index: u32,
988    ) -> (MemoryView<'a>, &'a WasiState) {
989        let memory = unsafe { self.memory_view(store) };
990        let state = self.state.deref();
991        (memory, state)
992    }
993
994    /// Unsafe:
995    ///
996    /// This will access the memory of the WASM process and create a view into it which is
997    /// inherently unsafe as it could corrupt the memory. Also accessing the memory is not
998    /// thread safe.
999    pub(crate) unsafe fn get_memory_and_wasi_state_and_inodes<'a>(
1000        &'a self,
1001        store: &'a impl AsStoreRef,
1002        _mem_index: u32,
1003    ) -> (MemoryView<'a>, &'a WasiState, &'a WasiInodes) {
1004        let memory = unsafe { self.memory_view(store) };
1005        let state = self.state.deref();
1006        let inodes = &state.inodes;
1007        (memory, state, inodes)
1008    }
1009
1010    pub(crate) fn get_wasi_state_and_inodes(&self) -> (&WasiState, &WasiInodes) {
1011        let state = self.state.deref();
1012        let inodes = &state.inodes;
1013        (state, inodes)
1014    }
1015
1016    pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> {
1017        InlineWaker::block_on(self.use_package_async(pkg))
1018    }
1019
1020    /// Make all the commands in a [`BinaryPackage`] available to the WASI
1021    /// instance.
1022    ///
1023    /// The [`BinaryPackageCommand::atom()`][cmd-atom] will be saved to
1024    /// `/bin/command`.
1025    ///
1026    /// This will also merge the command's filesystem
1027    /// ([`BinaryPackage::webc_fs`][pkg-fs]) into the current filesystem.
1028    ///
1029    /// [cmd-atom]: crate::bin_factory::BinaryPackageCommand::atom()
1030    /// [pkg-fs]: crate::bin_factory::BinaryPackage::webc_fs
1031    pub async fn use_package_async(
1032        &self,
1033        pkg: &BinaryPackage,
1034    ) -> Result<(), WasiStateCreationError> {
1035        tracing::trace!(package=%pkg.id, "merging package dependency into wasi environment");
1036        let root_fs = &self.state.fs.root_fs;
1037
1038        // We first need to merge the filesystem in the package into the
1039        // main file system, if it has not been merged already.
1040        if let Err(e) = self.state.fs.conditional_union(pkg).await {
1041            tracing::warn!(
1042                error = &e as &dyn std::error::Error,
1043                "Unable to merge the package's filesystem into the main one",
1044            );
1045        }
1046
1047        // Next, make sure all commands will be available
1048
1049        if !pkg.commands.is_empty() {
1050            let _ = root_fs.create_dir(Path::new("/bin"));
1051            let _ = root_fs.create_dir(Path::new("/usr"));
1052            let _ = root_fs.create_dir(Path::new("/usr/bin"));
1053
1054            for command in &pkg.commands {
1055                let path = format!("/bin/{}", command.name());
1056                let path2 = format!("/usr/bin/{}", command.name());
1057                let path = Path::new(path.as_str());
1058                let path2 = Path::new(path2.as_str());
1059
1060                let atom = command.atom();
1061
1062                match root_fs {
1063                    WasiFsRoot::Sandbox(root_fs) => {
1064                        if let Err(err) = root_fs
1065                            .new_open_options_ext()
1066                            .insert_ro_file(path, atom.clone())
1067                        {
1068                            tracing::debug!(
1069                                "failed to add package [{}] command [{}] - {}",
1070                                pkg.id,
1071                                command.name(),
1072                                err
1073                            );
1074                            continue;
1075                        }
1076                        if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path2, atom)
1077                        {
1078                            tracing::debug!(
1079                                "failed to add package [{}] command [{}] - {}",
1080                                pkg.id,
1081                                command.name(),
1082                                err
1083                            );
1084                            continue;
1085                        }
1086                    }
1087                    WasiFsRoot::Backing(fs) => {
1088                        // FIXME: we're counting on the fs being a mem_fs here. Otherwise, memory
1089                        // usage will be very high.
1090                        let mut f = fs.new_open_options().create(true).write(true).open(path)?;
1091                        if let Err(e) = f.copy_from_owned_buffer(&atom).await {
1092                            tracing::warn!(
1093                                error = &e as &dyn std::error::Error,
1094                                "Unable to copy file reference",
1095                            );
1096                        }
1097                        let mut f = fs.new_open_options().create(true).write(true).open(path2)?;
1098                        if let Err(e) = f.copy_from_owned_buffer(&atom).await {
1099                            tracing::warn!(
1100                                error = &e as &dyn std::error::Error,
1101                                "Unable to copy file reference",
1102                            );
1103                        }
1104                    }
1105                }
1106
1107                let mut package = pkg.clone();
1108                package.entrypoint_cmd = Some(command.name().to_string());
1109                let package_arc = Arc::new(package);
1110                self.bin_factory
1111                    .set_binary(path.to_string_lossy().as_ref(), &package_arc);
1112                self.bin_factory
1113                    .set_binary(path2.to_string_lossy().as_ref(), &package_arc);
1114
1115                tracing::debug!(
1116                    package=%pkg.id,
1117                    command_name=command.name(),
1118                    path=%path.display(),
1119                    "Injected a command into the filesystem",
1120                );
1121            }
1122        }
1123
1124        Ok(())
1125    }
1126
1127    /// Given a list of packages, load them from the registry and make them
1128    /// available.
1129    pub fn uses<I>(&self, uses: I) -> Result<(), WasiStateCreationError>
1130    where
1131        I: IntoIterator<Item = String>,
1132    {
1133        let rt = self.runtime();
1134
1135        for package_name in uses {
1136            let specifier = package_name.parse::<PackageSource>().map_err(|e| {
1137                WasiStateCreationError::WasiIncludePackageError(format!(
1138                    "package_name={package_name}, {e}",
1139                ))
1140            })?;
1141            let pkg = InlineWaker::block_on(BinaryPackage::from_registry(&specifier, rt)).map_err(
1142                |e| {
1143                    WasiStateCreationError::WasiIncludePackageError(format!(
1144                        "package_name={package_name}, {e}",
1145                    ))
1146                },
1147            )?;
1148            self.use_package(&pkg)?;
1149        }
1150
1151        Ok(())
1152    }
1153
1154    #[cfg(feature = "sys")]
1155    pub fn map_commands(
1156        &self,
1157        map_commands: std::collections::HashMap<String, std::path::PathBuf>,
1158    ) -> Result<(), WasiStateCreationError> {
1159        // Load all the mapped atoms
1160        #[allow(unused_imports)]
1161        use std::path::Path;
1162
1163        use shared_buffer::OwnedBuffer;
1164        #[allow(unused_imports)]
1165        use virtual_fs::FileSystem;
1166
1167        #[cfg(feature = "sys")]
1168        for (command, target) in map_commands.iter() {
1169            // Read the file
1170            let file = std::fs::read(target).map_err(|err| {
1171                WasiStateCreationError::WasiInheritError(format!(
1172                    "failed to read local binary [{}] - {}",
1173                    target.as_os_str().to_string_lossy(),
1174                    err
1175                ))
1176            })?;
1177            let file = OwnedBuffer::from(file);
1178
1179            if let WasiFsRoot::Sandbox(root_fs) = &self.state.fs.root_fs {
1180                let _ = root_fs.create_dir(Path::new("/bin"));
1181                let _ = root_fs.create_dir(Path::new("/usr"));
1182                let _ = root_fs.create_dir(Path::new("/usr/bin"));
1183
1184                let path = format!("/bin/{command}");
1185                let path = Path::new(path.as_str());
1186                if let Err(err) = root_fs
1187                    .new_open_options_ext()
1188                    .insert_ro_file(path, file.clone())
1189                {
1190                    tracing::debug!("failed to add atom command [{}] - {}", command, err);
1191                    continue;
1192                }
1193                let path = format!("/usr/bin/{command}");
1194                let path = Path::new(path.as_str());
1195                if let Err(err) = root_fs.new_open_options_ext().insert_ro_file(path, file) {
1196                    tracing::debug!("failed to add atom command [{}] - {}", command, err);
1197                    continue;
1198                }
1199            } else {
1200                tracing::debug!(
1201                    "failed to add atom command [{}] to the root file system as it is not sandboxed",
1202                    command
1203                );
1204                continue;
1205            }
1206        }
1207        Ok(())
1208    }
1209
1210    /// Cleans up all the open files (if this is the main thread)
1211    #[allow(clippy::await_holding_lock)]
1212    pub fn blocking_on_exit(&self, process_exit_code: Option<ExitCode>) {
1213        let cleanup = self.on_exit(process_exit_code);
1214        InlineWaker::block_on(cleanup);
1215    }
1216
1217    /// Cleans up all the open files (if this is the main thread)
1218    #[allow(clippy::await_holding_lock)]
1219    pub fn on_exit(&self, process_exit_code: Option<ExitCode>) -> BoxFuture<'static, ()> {
1220        const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
1221
1222        // If snap-shooting is enabled then we should record an event that the thread has exited.
1223        #[cfg(feature = "journal")]
1224        if self.should_journal() && self.has_active_journal() {
1225            if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), process_exit_code)
1226            {
1227                tracing::warn!("failed to save snapshot event for thread exit - {}", err);
1228            }
1229
1230            if self.thread.is_main() {
1231                if let Err(err) = JournalEffector::save_process_exit(self, process_exit_code) {
1232                    tracing::warn!("failed to save snapshot event for process exit - {}", err);
1233                }
1234            }
1235        }
1236
1237        // If the process wants to exit, also close all files and terminate it
1238        if let Some(process_exit_code) = process_exit_code {
1239            let process = self.process.clone();
1240            let disable_fs_cleanup = self.disable_fs_cleanup;
1241            let pid = self.pid();
1242
1243            let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT);
1244            let state = self.state.clone();
1245            Box::pin(async move {
1246                if !disable_fs_cleanup {
1247                    tracing::trace!(pid = %pid, "cleaning up open file handles");
1248
1249                    // Perform the clean operation using the asynchronous runtime
1250                    tokio::select! {
1251                        _ = timeout => {
1252                            tracing::debug!(
1253                                "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}"
1254                            );
1255                        },
1256                        _ = state.fs.close_all() => { }
1257                    }
1258
1259                    // Now send a signal that the thread is terminated
1260                    process.signal_process(Signal::Sigquit);
1261                }
1262
1263                // Terminate the process
1264                process.terminate(process_exit_code);
1265            })
1266        } else {
1267            Box::pin(async {})
1268        }
1269    }
1270
1271    pub fn prepare_spawn(&self, cmd: &BinaryPackageCommand) {
1272        if let Ok(Some(Wasi {
1273            main_args,
1274            env: env_vars,
1275            exec_name,
1276            ..
1277        })) = cmd.metadata().wasi()
1278        {
1279            if let Some(env_vars) = env_vars {
1280                let env_vars = env_vars
1281                    .into_iter()
1282                    .map(|env_var| {
1283                        let (k, v) = env_var.split_once('=').unwrap();
1284
1285                        (k.to_string(), v.as_bytes().to_vec())
1286                    })
1287                    .collect::<Vec<_>>();
1288
1289                let env_vars = conv_env_vars(env_vars);
1290
1291                self.state
1292                    .envs
1293                    .lock()
1294                    .unwrap()
1295                    .extend_from_slice(env_vars.as_slice());
1296            }
1297
1298            if let Some(main_args) = main_args {
1299                let mut args: std::sync::MutexGuard<'_, Vec<String>> =
1300                    self.state.args.lock().unwrap();
1301                // Insert main-args before user args
1302                args.splice(1..1, main_args);
1303            }
1304
1305            if let Some(exec_name) = exec_name {
1306                self.state.args.lock().unwrap()[0] = exec_name;
1307            }
1308        }
1309    }
1310}