feature/ci-debian
rs 173 lines 5.04 KB
Raw
1 //! CLI-facing daemon lifecycle management.
2 //!
3 //! All functions here use only `std` (no Tokio) and `libc` for signal
4 //! operations, so they can be called from the synchronous CLI binary.
5
6 pub mod fork;
7 pub mod ipc;
8 pub mod server;
9
10 use crate::config;
11 use std::{fs, thread, time::Duration};
12
13 // ─── State helpers ────────────────────────────────────────────────────────────
14
15 /// Returns `true` if the daemon's PID file exists **and** the recorded PID
16 /// corresponds to a live OS process (`kill(pid, 0) == 0`).
17 pub fn is_running() -> bool {
18 let cfg = config::get();
19
20 let pid_str = match fs::read_to_string(&cfg.pid_path) {
21 Ok(s) => s,
22 Err(_) => return false,
23 };
24
25 let pid: libc::pid_t = match pid_str.trim().parse() {
26 Ok(p) => p,
27 Err(_) => return false,
28 };
29
30 if pid <= 0 {
31 return false;
32 }
33
34 // kill(pid, 0) succeeds (returns 0) when the process exists.
35 unsafe { libc::kill(pid, 0) == 0 }
36 }
37
38 // ─── Lifecycle commands ───────────────────────────────────────────────────────
39
40 /// Auto-start the daemon if it is not already running.
41 ///
42 /// After forking, waits up to 3 s (30 × 100 ms) for the Unix socket to
43 /// appear, which indicates that the daemon is ready to accept connections.
44 /// Exits the process with an error message if the daemon never comes up.
45 pub fn ensure_running() {
46 if is_running() {
47 return;
48 }
49
50 fork::spawn_daemon(|| server::run());
51
52 let cfg = config::get();
53 for _ in 0..30 {
54 thread::sleep(Duration::from_millis(100));
55 if cfg.sock_path.exists() {
56 return;
57 }
58 }
59
60 eprintln!(
61 "error: daemon did not start within 3 seconds \
62 (socket {:?} never appeared)",
63 cfg.sock_path
64 );
65 std::process::exit(1);
66 }
67
68 /// Handler for `proses daemon start`.
69 ///
70 /// Starts the daemon if not already running, then waits for the socket to
71 /// appear. Prints a status message and exits cleanly.
72 pub fn start_cmd() {
73 config::init();
74
75 if is_running() {
76 let pid = read_pid().unwrap_or(0);
77 println!("Daemon is already running (pid {}).", pid);
78 return;
79 }
80
81 println!("Starting daemon…");
82 fork::spawn_daemon(|| server::run());
83
84 let cfg = config::get();
85 for _ in 0..30 {
86 thread::sleep(Duration::from_millis(100));
87 if cfg.sock_path.exists() {
88 let pid = read_pid().unwrap_or(0);
89 println!("Daemon started (pid {}).", pid);
90 return;
91 }
92 }
93
94 eprintln!(
95 "error: daemon did not start within 3 seconds \
96 (socket {:?} never appeared)",
97 cfg.sock_path
98 );
99 std::process::exit(1);
100 }
101
102 /// Handler for `proses daemon stop`.
103 ///
104 /// Reads the PID file, sends `SIGTERM`, waits up to 2 s for the process to
105 /// exit, then cleans up the PID and socket files.
106 pub fn stop_cmd() {
107 config::init();
108
109 let pid: libc::pid_t = match read_pid() {
110 Some(p) => p,
111 None => {
112 println!("Daemon is not running (no PID file).");
113 return;
114 }
115 };
116
117 // Check the process is actually alive before trying to kill it.
118 if unsafe { libc::kill(pid, 0) } != 0 {
119 println!("Daemon is not running (pid {} not found).", pid);
120 cleanup_files();
121 return;
122 }
123
124 println!("Stopping daemon (pid {})…", pid);
125 unsafe { libc::kill(pid, libc::SIGTERM) };
126
127 // Wait up to 2 s for the process to exit.
128 for _ in 0..20 {
129 thread::sleep(Duration::from_millis(100));
130 if unsafe { libc::kill(pid, 0) } != 0 {
131 break;
132 }
133 }
134
135 cleanup_files();
136 println!("Daemon stopped.");
137 }
138
139 /// Handler for `proses daemon status`.
140 ///
141 /// Prints whether the daemon is running and, if so, its PID.
142 pub fn status_cmd() {
143 config::init();
144
145 match read_pid() {
146 None => println!("Daemon: stopped (no PID file)"),
147 Some(pid) => {
148 if unsafe { libc::kill(pid, 0) } == 0 {
149 println!("Daemon: running (pid {})", pid);
150 } else {
151 println!("Daemon: stopped (pid {} not found)", pid);
152 cleanup_files();
153 }
154 }
155 }
156 }
157
158 // ─── Private helpers ──────────────────────────────────────────────────────────
159
160 /// Read and parse the daemon PID from the PID file.
161 /// Returns `None` if the file is missing or contains an invalid integer.
162 fn read_pid() -> Option<libc::pid_t> {
163 let cfg = config::get();
164 let content = fs::read_to_string(&cfg.pid_path).ok()?;
165 content.trim().parse::<libc::pid_t>().ok()
166 }
167
168 /// Remove the PID and socket files, ignoring errors (they may already be gone).
169 fn cleanup_files() {
170 let cfg = config::get();
171 let _ = fs::remove_file(&cfg.pid_path);
172 let _ = fs::remove_file(&cfg.sock_path);
173 }