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