Skip to main content

rash/
daemon.rs

1//! Detaching from the terminal, for `-f`.
2//!
3//! This has to run *before* the tokio runtime is created: `fork(2)` does not
4//! carry threads into the child, so a runtime built beforehand would be left
5//! with worker threads that no longer exist.
6//!
7//! autossh calls `daemon(3)` (autossh.c:448). macOS deprecates that function, so
8//! rash performs the same steps by hand — `daemon(0, 0)` means "do chdir to /"
9//! and "do close the standard descriptors".
10
11use std::io;
12
13/// Fork twice, start a new session, move to `/`, and point the standard
14/// descriptors at `/dev/null`.
15///
16/// Returns only in the final grandchild; the intermediate processes call
17/// `_exit(0)`.
18pub fn daemonize() -> io::Result<()> {
19    // First fork: the parent leaves, so the child is guaranteed not to be a
20    // process-group leader and setsid() can therefore succeed.
21    fork_and_leave_parent()?;
22
23    // SAFETY: setsid takes no arguments and only affects the calling process.
24    if unsafe { libc::setsid() } == -1 {
25        return Err(io::Error::last_os_error());
26    }
27
28    // Second fork: now that we lead a session, forking again means this process
29    // can never acquire a controlling terminal.
30    fork_and_leave_parent()?;
31
32    // SAFETY: `c"/"` is a 'static NUL-terminated string.
33    if unsafe { libc::chdir(c"/".as_ptr()) } == -1 {
34        return Err(io::Error::last_os_error());
35    }
36
37    redirect_std_to_dev_null()
38}
39
40/// Fork; the parent exits immediately, the child returns.
41fn fork_and_leave_parent() -> io::Result<()> {
42    // SAFETY: fork takes no arguments. The child returns to a single-threaded
43    // process — no tokio runtime exists yet — and the parent does nothing but
44    // `_exit`, which runs no destructors and touches no shared state.
45    match unsafe { libc::fork() } {
46        -1 => Err(io::Error::last_os_error()),
47        0 => Ok(()),
48        // SAFETY: `_exit` is async-signal-safe and always valid to call.
49        _ => unsafe { libc::_exit(0) },
50    }
51}
52
53fn redirect_std_to_dev_null() -> io::Result<()> {
54    // SAFETY: `c"/dev/null"` is a 'static NUL-terminated string, and O_RDWR is a
55    // valid flag combination for open(2).
56    let fd = unsafe { libc::open(c"/dev/null".as_ptr(), libc::O_RDWR) };
57    if fd == -1 {
58        return Err(io::Error::last_os_error());
59    }
60
61    for target in [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] {
62        // SAFETY: `fd` is a descriptor we just opened and `target` is one of the
63        // three standard descriptor numbers.
64        if unsafe { libc::dup2(fd, target) } == -1 {
65            let e = io::Error::last_os_error();
66            // SAFETY: closing the descriptor we opened above, once.
67            unsafe { libc::close(fd) };
68            return Err(e);
69        }
70    }
71
72    if fd > libc::STDERR_FILENO {
73        // SAFETY: `fd` is still open and is not one of the three we just aliased.
74        unsafe { libc::close(fd) };
75    }
76    Ok(())
77}