1use std::fs;
12use std::io;
13use std::os::unix::io::AsRawFd;
14use std::path::Path;
15use std::process;
16use std::thread;
17use std::time::Duration;
18
19pub const SYSTEM_PID_FILE: &str = "/run/pwr/pwr-server.pid";
22
23pub fn user_pid_file() -> std::path::PathBuf {
25 crate::config::user_runtime_dir().join("pwr-server.pid")
26}
27
28pub fn pid_file_for_config(config_path: &Path) -> std::path::PathBuf {
33 let system_base = crate::config::system_config_dir();
34 if config_path.starts_with(&system_base) {
35 std::path::PathBuf::from(SYSTEM_PID_FILE)
36 } else {
37 user_pid_file()
38 }
39}
40
41pub fn daemonize(pid_file: &Path) -> Result<(), String> {
52 match unsafe { libc::fork() } {
54 -1 => return Err(format!("First fork failed: {}", io::Error::last_os_error())),
55 0 => {} _ => process::exit(0), }
58
59 if unsafe { libc::setsid() } == -1 {
61 return Err(format!("setsid failed: {}", io::Error::last_os_error()));
62 }
63
64 match unsafe { libc::fork() } {
67 -1 => return Err(format!("Second fork failed: {}", io::Error::last_os_error())),
68 0 => {} _ => process::exit(0), }
71
72 if unsafe { libc::chdir(b"/\0".as_ptr() as *const _) } == -1 {
77 return Err(format!("chdir(/) failed: {}", io::Error::last_os_error()));
78 }
79
80 unsafe {
82 libc::umask(0);
83 }
84
85 redirect_stdio_to_devnull()?;
87
88 write_pid_file(pid_file)?;
90
91 Ok(())
92}
93
94fn redirect_stdio_to_devnull() -> Result<(), String> {
96 let devnull = fs::OpenOptions::new()
97 .read(true)
98 .write(true)
99 .open("/dev/null")
100 .map_err(|e| format!("Cannot open /dev/null: {}", e))?;
101
102 let devnull_fd = devnull.as_raw_fd();
103
104 for fd in [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] {
105 if fd != devnull_fd {
106 if unsafe { libc::dup2(devnull_fd, fd) } == -1 {
107 return Err(format!(
108 "Cannot redirect fd {} to /dev/null: {}",
109 fd,
110 io::Error::last_os_error()
111 ));
112 }
113 }
114 }
115
116 Ok(())
119}
120
121fn write_pid_file(path: &Path) -> Result<(), String> {
125 if let Some(parent) = path.parent() {
126 fs::create_dir_all(parent)
127 .map_err(|e| format!("Cannot create PID directory {}: {}", parent.display(), e))?;
128 }
129
130 let pid = unsafe { libc::getpid() };
131 let contents = format!("{}\n", pid);
132
133 fs::write(path, &contents)
134 .map_err(|e| format!("Cannot write PID file {}: {}", path.display(), e))?;
135
136 Ok(())
137}
138
139pub fn stop_daemon(pid_file: &Path) -> Result<String, String> {
151 let pid = read_pid_file(pid_file)?;
152
153 if !is_process_running(pid) {
155 remove_pid_file(pid_file)?;
157 return Err(format!(
158 "PID {} is not running (stale PID file removed)",
159 pid
160 ));
161 }
162
163 send_signal(pid, libc::SIGTERM)?;
165
166 let grace = Duration::from_secs(5);
168 let poll = Duration::from_millis(100);
169 let deadline = std::time::Instant::now() + grace;
170
171 let mut clean_exit = false;
172 while std::time::Instant::now() < deadline {
173 if !is_process_running(pid) {
174 clean_exit = true;
175 break;
176 }
177 thread::sleep(poll);
178 }
179
180 if clean_exit {
181 remove_pid_file(pid_file)?;
182 return Ok(format!("pwr-server (PID {}) stopped gracefully", pid));
183 }
184
185 send_signal(pid, libc::SIGKILL)?;
187 thread::sleep(Duration::from_millis(200));
188
189 if is_process_running(pid) {
190 return Err(format!(
191 "Failed to kill pwr-server (PID {}) — process did not respond to SIGKILL",
192 pid
193 ));
194 }
195
196 remove_pid_file(pid_file)?;
197 Ok(format!(
198 "pwr-server (PID {}) killed (did not respond to SIGTERM within {}s)",
199 pid,
200 grace.as_secs()
201 ))
202}
203
204fn read_pid_file(path: &Path) -> Result<libc::pid_t, String> {
209 let contents = fs::read_to_string(path)
210 .map_err(|e| format!("Cannot read PID file {}: {}", path.display(), e))?;
211
212 let pid: libc::pid_t = contents
213 .trim()
214 .parse()
215 .map_err(|e| format!("Invalid PID in {}: {}", path.display(), e))?;
216
217 if pid <= 0 {
218 return Err(format!("Invalid PID ({}) in {}", pid, path.display()));
219 }
220
221 Ok(pid)
222}
223
224fn is_process_running(pid: libc::pid_t) -> bool {
229 unsafe { libc::kill(pid, 0) == 0 }
233}
234
235fn send_signal(pid: libc::pid_t, signal: libc::c_int) -> Result<(), String> {
237 if unsafe { libc::kill(pid, signal) } == -1 {
238 return Err(format!(
239 "Cannot send signal {} to PID {}: {}",
240 signal,
241 pid,
242 io::Error::last_os_error()
243 ));
244 }
245 Ok(())
246}
247
248fn remove_pid_file(path: &Path) -> Result<(), String> {
251 match fs::remove_file(path) {
252 Ok(()) => Ok(()),
253 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
254 Err(e) => Err(format!(
255 "Cannot remove PID file {}: {}",
256 path.display(),
257 e
258 )),
259 }
260}