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}