feature/ci-debian
rs 119 lines 4.9 KB
Raw
1 //! Classic Unix double-fork daemonization.
2 //!
3 //! Only `libc` is used for all syscalls so that this module has zero
4 //! dependency on any async runtime and can safely be called before Tokio
5 //! is ever created.
6
7 use std::io;
8
9 /// Double-fork daemonization.
10 ///
11 /// ## Process tree after the call returns
12 ///
13 /// ```text
14 /// original caller
15 /// ├── [fork #1] first child
16 /// │ ├── setsid() — new session, no controlling terminal
17 /// │ └── [fork #2] grandchild ← the actual daemon
18 /// │ ├── dup2 stdin/stdout/stderr → /dev/null
19 /// │ └── daemon_fn()
20 /// └── waitpid(first child) → returns normally to caller
21 /// ```
22 ///
23 /// * **Original process** — waits for the first child (so it does not
24 /// become a zombie) then **returns normally**. The `daemon_fn` closure
25 /// is dropped (not called) here.
26 ///
27 /// * **First child** — calls `setsid(2)` to detach from the controlling
28 /// terminal, forks a second time, then exits immediately via `_exit(0)`
29 /// so that the grandchild is re-parented to `init` / `systemd`.
30 ///
31 /// * **Grandchild** — the daemon proper: its standard file descriptors are
32 /// redirected to `/dev/null` so that stray reads/writes cannot interact
33 /// with a terminal, then `daemon_fn()` is invoked.
34 ///
35 /// # Panics
36 ///
37 /// Panics in the original process if `fork()` fails.
38 ///
39 /// # Safety
40 ///
41 /// This function **must** be called before any multi-threaded runtime
42 /// (Tokio, Rayon, …) is started. Forking a multi-threaded process is
43 /// undefined behaviour.
44 pub fn spawn_daemon<F: FnOnce()>(daemon_fn: F) {
45 // ── Fork #1 ───────────────────────────────────────────────────────────
46 let pid1 = unsafe { libc::fork() };
47
48 if pid1 < 0 {
49 panic!("fork() #1 failed: {}", io::Error::last_os_error());
50 }
51
52 if pid1 > 0 {
53 // ── Original process (parent) ─────────────────────────────────────
54 // Reap the first child so it does not linger as a zombie.
55 unsafe {
56 let mut status: libc::c_int = 0;
57 libc::waitpid(pid1, &mut status, 0);
58 }
59 // `daemon_fn` is intentionally *dropped* here — it must only run in
60 // the grandchild.
61 return;
62 }
63
64 // ── First child ───────────────────────────────────────────────────────
65 // Become the leader of a brand-new session. This severs the connection
66 // to any controlling terminal inherited from the parent.
67 if unsafe { libc::setsid() } < 0 {
68 // Cannot recover; exit without running Rust destructors so we do not
69 // flush/corrupt stdio buffers shared with the parent.
70 unsafe { libc::_exit(1) };
71 }
72
73 // ── Fork #2 ───────────────────────────────────────────────────────────
74 let pid2 = unsafe { libc::fork() };
75
76 if pid2 < 0 {
77 unsafe { libc::_exit(1) };
78 }
79
80 if pid2 > 0 {
81 // First child exits immediately. The grandchild is now an orphan
82 // and will be re-parented to PID 1 (init / systemd), ensuring it
83 // can never accidentally acquire a controlling terminal.
84 // `_exit` is used (rather than `exit`) to avoid double-flushing
85 // any stdio buffers inherited from the original process.
86 unsafe { libc::_exit(0) };
87 }
88
89 // ── Grandchild — the actual daemon ────────────────────────────────────
90
91 // Redirect the standard file descriptors to /dev/null so that any
92 // accidental reads or writes do not interact with a terminal or pipe.
93 unsafe {
94 let dev_null = libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char, libc::O_RDWR);
95
96 if dev_null >= 0 {
97 // It is fine if any of these dup2 calls fail — we are already
98 // detached, and the daemon will soon open its own log files.
99 libc::dup2(dev_null, libc::STDIN_FILENO);
100 libc::dup2(dev_null, libc::STDOUT_FILENO);
101 libc::dup2(dev_null, libc::STDERR_FILENO);
102
103 // Close the temporary fd only if it is not one of the three
104 // standard slots (which would have been installed above).
105 if dev_null > libc::STDERR_FILENO {
106 libc::close(dev_null);
107 }
108 }
109 }
110
111 // Hand control to the daemon entry point.
112 //
113 // In normal usage `daemon_fn` is `|| server::run()`, which is `-> !`
114 // and never returns. The `_exit` below is a safety net for any
115 // `daemon_fn` that does return.
116 daemon_fn();
117
118 unsafe { libc::_exit(0) };
119 }