Skip to main content

wasmer_wasix/state/
env.rs

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