lx/
spawn.rs

1//! Easy to use helpers to spawn processes built using the `clone` and `execve` system calls, kind
2//! of like the `posix_spawn` functions provided by libc.
3
4use core::{
5    ffi::CStr,
6    mem,
7};
8
9use crate::{
10    clone3,
11    clone_args,
12    eprintln,
13    execve,
14    exit,
15    pid_t,
16    wait4,
17    CStrArray,
18    CLONE_VFORK,
19    CLONE_VM,
20    SIGCHLD,
21};
22
23struct CallbackData<'a> {
24    filename: &'a CStr,
25    argv: &'a CStrArray,
26    envp: &'a CStrArray,
27    pre_exec: unsafe fn(data: usize) -> bool,
28    pre_exec_data: usize,
29}
30
31unsafe fn callback(arg: usize) -> ! {
32    let arg = &*(arg as *const CallbackData);
33    if (arg.pre_exec)(arg.pre_exec_data) {
34        if let Err(err) = execve(arg.filename, arg.argv, arg.envp) {
35            // Note: do not panic or return when execve fails, as we still want to exit.
36            eprintln!("failed to execve: {err}");
37        }
38    }
39    exit(1).unwrap();
40}
41
42fn noop(_data: usize) -> bool {
43    true
44}
45
46pub struct SpawnOptions<'a> {
47    clone_flags: u32,
48    callback_data: CallbackData<'a>,
49}
50
51impl<'a> SpawnOptions<'a> {
52    pub fn new(filename: &'a CStr, argv: &'a CStrArray, envp: &'a CStrArray) -> Self {
53        Self {
54            clone_flags: 0,
55            callback_data: CallbackData {
56                filename,
57                argv,
58                envp,
59                pre_exec: noop,
60                pre_exec_data: 0,
61            },
62        }
63    }
64
65    /// Sets a function to be called with the `data` argument before `execve` is called. This
66    /// allows the caller to modify the environment for the new process.
67    ///
68    /// # Safety
69    ///
70    /// The function must not introduce undefined behavior.
71    ///
72    /// The function must not break library guarantees. This could happen since it will be executed
73    /// in another process which shares some things with the current process like the virtual
74    /// memory, but not others like the file descriptor table, the threads, etc.
75    pub unsafe fn pre_exec(&mut self, f: unsafe fn(data: usize) -> bool, data: usize) -> &mut Self {
76        self.callback_data.pre_exec = f;
77        self.callback_data.pre_exec_data = data;
78        self
79    }
80
81    /// Sets additional clone flags to use when spawning the process. This can be used to spawn the
82    /// process in a new namespace for example.
83    pub fn clone_flags(&mut self, flags: u32) -> &mut Self {
84        self.clone_flags = flags;
85        self
86    }
87
88    /// Tries to spawn the new process and returns its PID on success.
89    pub fn spawn(&self) -> crate::Result<pid_t> {
90        let mut args: clone_args = unsafe { mem::zeroed() };
91        args.flags = u64::from(self.clone_flags | CLONE_VM | CLONE_VFORK);
92        args.exit_signal = u64::try_from(SIGCHLD).unwrap();
93
94        unsafe {
95            clone3(
96                &mut args,
97                callback,
98                &self.callback_data as *const _ as usize,
99            )
100        }
101    }
102
103    /// Spawns a new process, waits for it to die and returns its status code.
104    pub fn spawn_and_wait(&self) -> crate::Result<i32> {
105        let pid = self.spawn()?;
106
107        let mut status = 0;
108        wait4(pid, Some(&mut status), 0, None)?;
109
110        Ok(status)
111    }
112}