safe_fork/
lib.rs

1use std::io::{Error, Result};
2use std::os::unix::process::ExitStatusExt;
3use std::process::ExitStatus;
4
5/// Ensures the current process is single-threaded.
6pub fn ensure_single_threaded() -> Result<()> {
7    // SAFETY: `unshare` does not have special safety requirements.
8    if unsafe { libc::unshare(libc::CLONE_VM) } == 0 {
9        Ok(())
10    } else {
11        Err(Error::last_os_error())
12    }
13}
14
15/// Check if the current process is single-threaded.
16pub fn is_single_threaded() -> bool {
17    ensure_single_threaded().is_ok()
18}
19
20/// Representation of a forked child process.
21///
22/// This is a thin wrapper of the raw PID to provide the `join` helper function.
23pub struct Child {
24    pid: libc::pid_t,
25}
26
27impl Child {
28    /// Returns the OS-assigned process identifier associated with this child.
29    pub fn pid(&self) -> u32 {
30        self.pid as _
31    }
32
33    /// Waits for the child to exit completely, returning the status that it
34    /// exited with.
35    pub fn join(self) -> Result<ExitStatus> {
36        // SAFETY: `waitpid` does not have special safety requirements.
37        let mut status = 0;
38        let ret = unsafe { libc::waitpid(self.pid, &mut status, 0) };
39        if ret < 0 {
40            return Err(std::io::Error::last_os_error());
41        }
42        Ok(ExitStatus::from_raw(status))
43    }
44}
45
46/// Fork the current process.
47///
48/// The forking process must be single-threaded. Otherwise, this call will fail.
49pub fn fork() -> Result<Option<Child>> {
50    ensure_single_threaded()?;
51
52    // SAFETY: fork is safe for single-threaded process.
53    match unsafe { libc::fork() } {
54        -1 => Err(std::io::Error::last_os_error()),
55        0 => Ok(None),
56        pid => Ok(Some(Child { pid })),
57    }
58}
59
60/// Fork the current process, and execute the provided closure within child process.
61pub fn fork_spawn(f: impl FnOnce() -> i32) -> Result<Child> {
62    Ok(match fork()? {
63        Some(c) => c,
64        None => {
65            std::process::exit(f());
66        }
67    })
68}
69
70/// Fork the current process, and execute the provided closure within child process, and wait for it to complete.
71pub fn fork_join(f: impl FnOnce() -> i32) -> Result<i32> {
72    let exit = fork_spawn(f)?.join()?;
73    Ok(exit
74        .code()
75        .or_else(|| exit.signal().map(|x| x + 128))
76        .unwrap_or(1))
77}