lx 0.4.0

A no_std crate to use Linux system calls
Documentation
//! Easy to use helpers to spawn processes built using the `clone` and `execve` system calls, kind
//! of like the `posix_spawn` functions provided by libc.

use core::{
    ffi::CStr,
    mem,
};

use crate::{
    clone3,
    clone_args,
    eprintln,
    execve,
    exit,
    pid_t,
    wait4,
    CStrArray,
    CLONE_VFORK,
    CLONE_VM,
    SIGCHLD,
};

struct CallbackData<'a> {
    filename: &'a CStr,
    argv: &'a CStrArray,
    envp: &'a CStrArray,
    pre_exec: unsafe fn(data: usize) -> bool,
    pre_exec_data: usize,
}

unsafe fn callback(arg: usize) -> ! {
    let arg = &*(arg as *const CallbackData);
    if (arg.pre_exec)(arg.pre_exec_data) {
        if let Err(err) = execve(arg.filename, arg.argv, arg.envp) {
            // Note: do not panic or return when execve fails, as we still want to exit.
            eprintln!("failed to execve: {err}");
        }
    }
    exit(1).unwrap();
}

fn noop(_data: usize) -> bool {
    true
}

pub struct SpawnOptions<'a> {
    clone_flags: u32,
    callback_data: CallbackData<'a>,
}

impl<'a> SpawnOptions<'a> {
    pub fn new(filename: &'a CStr, argv: &'a CStrArray, envp: &'a CStrArray) -> Self {
        Self {
            clone_flags: 0,
            callback_data: CallbackData {
                filename,
                argv,
                envp,
                pre_exec: noop,
                pre_exec_data: 0,
            },
        }
    }

    /// Sets a function to be called with the `data` argument before `execve` is called. This
    /// allows the caller to modify the environment for the new process.
    ///
    /// # Safety
    ///
    /// The function must not introduce undefined behavior.
    ///
    /// The function must not break library guarantees. This could happen since it will be executed
    /// in another process which shares some things with the current process like the virtual
    /// memory, but not others like the file descriptor table, the threads, etc.
    pub unsafe fn pre_exec(&mut self, f: unsafe fn(data: usize) -> bool, data: usize) -> &mut Self {
        self.callback_data.pre_exec = f;
        self.callback_data.pre_exec_data = data;
        self
    }

    /// Sets additional clone flags to use when spawning the process. This can be used to spawn the
    /// process in a new namespace for example.
    pub fn clone_flags(&mut self, flags: u32) -> &mut Self {
        self.clone_flags = flags;
        self
    }

    /// Tries to spawn the new process and returns its PID on success.
    pub fn spawn(&self) -> crate::Result<pid_t> {
        let mut args: clone_args = unsafe { mem::zeroed() };
        args.flags = u64::from(self.clone_flags | CLONE_VM | CLONE_VFORK);
        args.exit_signal = u64::try_from(SIGCHLD).unwrap();

        unsafe {
            clone3(
                &mut args,
                callback,
                &self.callback_data as *const _ as usize,
            )
        }
    }

    /// Spawns a new process, waits for it to die and returns its status code.
    pub fn spawn_and_wait(&self) -> crate::Result<i32> {
        let pid = self.spawn()?;

        let mut status = 0;
        wait4(pid, Some(&mut status), 0, None)?;

        Ok(status)
    }
}