use std::fs;
use std::io;
use std::path::Path;
use std::process;
use std::result;
use errno_result;
use libc::{c_long, pid_t, syscall, CLONE_NEWPID, CLONE_NEWUSER, SIGCHLD};
use syscall_defines::linux::LinuxSyscall::SYS_clone;
#[repr(u32)]
pub enum CloneNamespace {
Inherit = 0,
NewUserPid = CLONE_NEWUSER as u32 | CLONE_NEWPID as u32,
}
#[derive(Debug)]
pub enum CloneError {
IterateTasks(io::Error),
Multithreaded(usize),
Sys(::Error),
}
unsafe fn do_clone(flags: i32) -> ::Result<pid_t> {
let pid = syscall(SYS_clone as c_long, flags | SIGCHLD as i32, 0);
if pid < 0 {
errno_result()
} else {
Ok(pid as pid_t)
}
}
fn count_dir_entries<P: AsRef<Path>>(path: P) -> io::Result<usize> {
Ok(fs::read_dir(path)?.count())
}
pub fn clone_process<F>(ns: CloneNamespace, post_clone_cb: F) -> result::Result<pid_t, CloneError>
where
F: FnOnce(),
{
match count_dir_entries("/proc/self/task") {
Ok(1) => {}
Ok(thread_count) => {
let _ = thread_count;
#[cfg(not(test))]
return Err(CloneError::Multithreaded(thread_count));
}
Err(e) => return Err(CloneError::IterateTasks(e)),
}
let ret = unsafe { do_clone(ns as i32) }.map_err(CloneError::Sys)?;
if ret == 0 {
struct ExitGuard;
impl Drop for ExitGuard {
fn drop(&mut self) {
process::exit(101);
}
}
#[allow(unused_variables)]
let exit_guard = ExitGuard {};
post_clone_cb();
process::exit(0);
}
Ok(ret)
}
#[cfg(test)]
mod tests {
use super::*;
use libc;
use {getpid, EventFd};
fn wait_process(pid: libc::pid_t) -> ::Result<libc::c_int> {
let mut status: libc::c_int = 0;
unsafe {
if libc::waitpid(pid, &mut status as *mut libc::c_int, 0) < 0 {
errno_result()
} else {
Ok(libc::WEXITSTATUS(status))
}
}
}
#[test]
fn pid_diff() {
let evt_fd = EventFd::new().expect("failed to create EventFd");
let evt_fd_fork = evt_fd.try_clone().expect("failed to clone EventFd");
let pid = getpid();
clone_process(CloneNamespace::Inherit, || {
if pid != getpid() {
evt_fd_fork.write(1).unwrap()
} else {
evt_fd_fork.write(2).unwrap()
}
})
.expect("failed to clone");
assert_eq!(evt_fd.read(), Ok(1));
}
#[test]
fn panic_safe() {
let pid = getpid();
assert_ne!(pid, 0);
let clone_pid = clone_process(CloneNamespace::Inherit, || {
assert!(false);
})
.expect("failed to clone");
if pid != getpid() {
process::exit(2);
}
let status = wait_process(clone_pid).expect("wait_process failed");
assert!(status == 101 || status == 0);
}
}