sys_util/
fork.rs

1// Copyright 2017 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::fs;
6use std::io;
7use std::path::Path;
8use std::process;
9use std::result;
10
11use errno_result;
12
13use libc::{c_long, pid_t, syscall, CLONE_NEWPID, CLONE_NEWUSER, SIGCHLD};
14
15use syscall_defines::linux::LinuxSyscall::SYS_clone;
16
17/// Controls what namespace `clone_process` will have. See NAMESPACES(7).
18#[repr(u32)]
19pub enum CloneNamespace {
20    /// The new process will inherit the namespace from the old process.
21    Inherit = 0,
22    /// The new process with be in a new user and PID namespace.
23    NewUserPid = CLONE_NEWUSER as u32 | CLONE_NEWPID as u32,
24}
25
26#[derive(Debug)]
27pub enum CloneError {
28    /// There was an error trying to iterate this process's threads.
29    IterateTasks(io::Error),
30    /// There are multiple threads running. The `usize` indicates how many threads.
31    Multithreaded(usize),
32    /// There was an error while cloning.
33    Sys(::Error),
34}
35
36unsafe fn do_clone(flags: i32) -> ::Result<pid_t> {
37    // Forking is unsafe, this function must be unsafe as there is no way to guarantee safety
38    // without more context about the state of the program.
39    let pid = syscall(SYS_clone as c_long, flags | SIGCHLD as i32, 0);
40    if pid < 0 {
41        errno_result()
42    } else {
43        Ok(pid as pid_t)
44    }
45}
46
47fn count_dir_entries<P: AsRef<Path>>(path: P) -> io::Result<usize> {
48    Ok(fs::read_dir(path)?.count())
49}
50
51/// Clones this process and calls a closure in the new process.
52///
53/// After `post_clone_cb` returns or panics, the new process exits. Similar to how a `fork` syscall
54/// works, the new process is the same as the current process with the exception of the namespace
55/// controlled with the `ns` argument.
56///
57/// # Arguments
58/// * `ns` - What namespace the new process will have (see NAMESPACES(7)).
59/// * `post_clone_cb` - Callback to run in the new process
60pub fn clone_process<F>(ns: CloneNamespace, post_clone_cb: F) -> result::Result<pid_t, CloneError>
61where
62    F: FnOnce(),
63{
64    match count_dir_entries("/proc/self/task") {
65        Ok(1) => {}
66        Ok(thread_count) => {
67            // Test cfg gets a free pass on this because tests generally have multiple independent
68            // test threads going.
69            let _ = thread_count;
70            #[cfg(not(test))]
71            return Err(CloneError::Multithreaded(thread_count));
72        }
73        Err(e) => return Err(CloneError::IterateTasks(e)),
74    }
75    // Forking is considered unsafe in mutlithreaded programs, but we just checked for other threads
76    // in this process. We also only allow valid flags from CloneNamespace and check the return
77    // result for errors. We also never let the cloned process return from this function.
78    let ret = unsafe { do_clone(ns as i32) }.map_err(CloneError::Sys)?;
79    if ret == 0 {
80        struct ExitGuard;
81        impl Drop for ExitGuard {
82            fn drop(&mut self) {
83                process::exit(101);
84            }
85        }
86        // Prevents a panic in post_clone_cb from bypassing the process::exit.
87        #[allow(unused_variables)]
88        let exit_guard = ExitGuard {};
89        post_clone_cb();
90        // ! Never returns
91        process::exit(0);
92    }
93
94    Ok(ret)
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use libc;
101    use {getpid, EventFd};
102
103    fn wait_process(pid: libc::pid_t) -> ::Result<libc::c_int> {
104        let mut status: libc::c_int = 0;
105        unsafe {
106            if libc::waitpid(pid, &mut status as *mut libc::c_int, 0) < 0 {
107                errno_result()
108            } else {
109                Ok(libc::WEXITSTATUS(status))
110            }
111        }
112    }
113
114    #[test]
115    fn pid_diff() {
116        let evt_fd = EventFd::new().expect("failed to create EventFd");
117        let evt_fd_fork = evt_fd.try_clone().expect("failed to clone EventFd");
118        let pid = getpid();
119        clone_process(CloneNamespace::Inherit, || {
120            // checks that this is a genuine fork with a new PID
121            if pid != getpid() {
122                evt_fd_fork.write(1).unwrap()
123            } else {
124                evt_fd_fork.write(2).unwrap()
125            }
126        })
127        .expect("failed to clone");
128        assert_eq!(evt_fd.read(), Ok(1));
129    }
130
131    #[test]
132    fn panic_safe() {
133        let pid = getpid();
134        assert_ne!(pid, 0);
135
136        let clone_pid = clone_process(CloneNamespace::Inherit, || {
137            assert!(false);
138        })
139        .expect("failed to clone");
140
141        // This should never happen;
142        if pid != getpid() {
143            process::exit(2);
144        }
145
146        let status = wait_process(clone_pid).expect("wait_process failed");
147        assert!(status == 101 || status == 0);
148    }
149}