rash/pidfile.rs
1//! The pid file, removed when the guard is dropped.
2//!
3//! autossh registers an `atexit()` handler for this (autossh.c:1721-1727), which
4//! it then has to work around in `xerrlog()` because `_exit()` skips those on
5//! most systems. A `Drop` guard needs no such special case.
6
7use std::fs::{self, File, OpenOptions};
8use std::io::{self, Write};
9use std::path::{Path, PathBuf};
10use std::process;
11use std::time::SystemTime;
12
13#[derive(Debug)]
14pub struct PidFile {
15 path: PathBuf,
16}
17
18impl PidFile {
19 /// Write the current pid. Call this *after* any daemonising fork, so the
20 /// file records the daemon's pid rather than that of the process that
21 /// forked it.
22 pub fn create(path: &Path) -> io::Result<Self> {
23 let mut f = File::create(path)?;
24 writeln!(f, "{}", std::process::id())?;
25 f.flush()?;
26 Ok(Self {
27 path: path.to_path_buf(),
28 })
29 }
30
31 /// Bump the modification time so a watchdog can see rash is still alive.
32 ///
33 /// autossh only does this when built with `-DTOUCH_PIDFILE`, which is off in
34 /// the stock build; rash exposes it as `RASH_TOUCH_PIDFILE`.
35 pub fn touch(&self) -> io::Result<()> {
36 OpenOptions::new()
37 .write(true)
38 .open(&self.path)?
39 .set_modified(SystemTime::now())
40 }
41}
42
43impl Drop for PidFile {
44 /// Remove the file, but only while it is still ours.
45 ///
46 /// Nothing stops a second rash being pointed at the same path: it truncates
47 /// the file and writes its own pid, and without this check the first one to
48 /// exit would then delete the *survivor's* pid file, leaving a live
49 /// supervisor that no watchdog can find.
50 fn drop(&mut self) {
51 let ours = fs::read_to_string(&self.path)
52 .ok()
53 .and_then(|s| s.trim().parse::<u32>().ok())
54 .is_some_and(|pid| pid == process::id());
55 if ours {
56 let _ = fs::remove_file(&self.path);
57 }
58 }
59}