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 DEFAULT_PID_FILE: &str = "/run/pwr/pwr-server.pid";
21
22pub fn daemonize(pid_file: &Path) -> Result<(), String> {
33 match unsafe { libc::fork() } {
35 -1 => return Err(format!("First fork failed: {}", io::Error::last_os_error())),
36 0 => {} _ => process::exit(0), }
39
40 if unsafe { libc::setsid() } == -1 {
42 return Err(format!("setsid failed: {}", io::Error::last_os_error()));
43 }
44
45 match unsafe { libc::fork() } {
48 -1 => return Err(format!("Second fork failed: {}", io::Error::last_os_error())),
49 0 => {} _ => process::exit(0), }
52
53 if unsafe { libc::chdir(b"/\0".as_ptr() as *const _) } == -1 {
58 return Err(format!("chdir(/) failed: {}", io::Error::last_os_error()));
59 }
60
61 unsafe {
63 libc::umask(0);
64 }
65
66 redirect_stdio_to_devnull()?;
68
69 write_pid_file(pid_file)?;
71
72 Ok(())
73}
74
75fn redirect_stdio_to_devnull() -> Result<(), String> {
77 let devnull = fs::OpenOptions::new()
78 .read(true)
79 .write(true)
80 .open("/dev/null")
81 .map_err(|e| format!("Cannot open /dev/null: {}", e))?;
82
83 let devnull_fd = devnull.as_raw_fd();
84
85 for fd in [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] {
86 if fd != devnull_fd {
87 if unsafe { libc::dup2(devnull_fd, fd) } == -1 {
88 return Err(format!(
89 "Cannot redirect fd {} to /dev/null: {}",
90 fd,
91 io::Error::last_os_error()
92 ));
93 }
94 }
95 }
96
97 Ok(())
100}
101
102fn write_pid_file(path: &Path) -> Result<(), String> {
106 if let Some(parent) = path.parent() {
107 fs::create_dir_all(parent)
108 .map_err(|e| format!("Cannot create PID directory {}: {}", parent.display(), e))?;
109 }
110
111 let pid = unsafe { libc::getpid() };
112 let contents = format!("{}\n", pid);
113
114 fs::write(path, &contents)
115 .map_err(|e| format!("Cannot write PID file {}: {}", path.display(), e))?;
116
117 Ok(())
118}
119
120pub fn stop_daemon(pid_file: &Path) -> Result<String, String> {
132 let pid = read_pid_file(pid_file)?;
133
134 if !is_process_running(pid) {
136 remove_pid_file(pid_file)?;
138 return Err(format!(
139 "PID {} is not running (stale PID file removed)",
140 pid
141 ));
142 }
143
144 send_signal(pid, libc::SIGTERM)?;
146
147 let grace = Duration::from_secs(5);
149 let poll = Duration::from_millis(100);
150 let deadline = std::time::Instant::now() + grace;
151
152 let mut clean_exit = false;
153 while std::time::Instant::now() < deadline {
154 if !is_process_running(pid) {
155 clean_exit = true;
156 break;
157 }
158 thread::sleep(poll);
159 }
160
161 if clean_exit {
162 remove_pid_file(pid_file)?;
163 return Ok(format!("pwr-server (PID {}) stopped gracefully", pid));
164 }
165
166 send_signal(pid, libc::SIGKILL)?;
168 thread::sleep(Duration::from_millis(200));
169
170 if is_process_running(pid) {
171 return Err(format!(
172 "Failed to kill pwr-server (PID {}) — process did not respond to SIGKILL",
173 pid
174 ));
175 }
176
177 remove_pid_file(pid_file)?;
178 Ok(format!(
179 "pwr-server (PID {}) killed (did not respond to SIGTERM within {}s)",
180 pid,
181 grace.as_secs()
182 ))
183}
184
185fn read_pid_file(path: &Path) -> Result<libc::pid_t, String> {
190 let contents = fs::read_to_string(path)
191 .map_err(|e| format!("Cannot read PID file {}: {}", path.display(), e))?;
192
193 let pid: libc::pid_t = contents
194 .trim()
195 .parse()
196 .map_err(|e| format!("Invalid PID in {}: {}", path.display(), e))?;
197
198 if pid <= 0 {
199 return Err(format!("Invalid PID ({}) in {}", pid, path.display()));
200 }
201
202 Ok(pid)
203}
204
205fn is_process_running(pid: libc::pid_t) -> bool {
210 unsafe { libc::kill(pid, 0) == 0 }
214}
215
216fn send_signal(pid: libc::pid_t, signal: libc::c_int) -> Result<(), String> {
218 if unsafe { libc::kill(pid, signal) } == -1 {
219 return Err(format!(
220 "Cannot send signal {} to PID {}: {}",
221 signal,
222 pid,
223 io::Error::last_os_error()
224 ));
225 }
226 Ok(())
227}
228
229fn remove_pid_file(path: &Path) -> Result<(), String> {
232 match fs::remove_file(path) {
233 Ok(()) => Ok(()),
234 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
235 Err(e) => Err(format!(
236 "Cannot remove PID file {}: {}",
237 path.display(),
238 e
239 )),
240 }
241}