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