wasmer_wasix/state/
env.rs

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