Skip to main content

acp_cli/session/
pid.rs

1use std::path::PathBuf;
2
3use super::scoping::session_dir;
4
5/// Return the path for a session's PID file: `~/.acp-cli/sessions/<key>.pid`.
6fn pid_path(session_key: &str) -> PathBuf {
7    session_dir().join(format!("{session_key}.pid"))
8}
9
10/// Write the current process PID to the pid file for a session key.
11pub fn write_pid(session_key: &str) -> std::io::Result<()> {
12    let path = pid_path(session_key);
13    if let Some(parent) = path.parent() {
14        std::fs::create_dir_all(parent)?;
15    }
16    std::fs::write(&path, std::process::id().to_string())
17}
18
19/// Remove the pid file for a session key.
20pub fn remove_pid(session_key: &str) -> std::io::Result<()> {
21    let path = pid_path(session_key);
22    match std::fs::remove_file(&path) {
23        Ok(()) => Ok(()),
24        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
25        Err(e) => Err(e),
26    }
27}
28
29/// Read the PID from the pid file and check if the process is alive.
30///
31/// Returns `Some(pid)` if the file exists and the process is running,
32/// `None` if the file is missing, unreadable, or the process is dead.
33pub fn read_pid(session_key: &str) -> Option<u32> {
34    let path = pid_path(session_key);
35    let contents = std::fs::read_to_string(&path).ok()?;
36    let pid: u32 = contents.trim().parse().ok()?;
37    if is_process_alive(pid) {
38        Some(pid)
39    } else {
40        // Stale PID file — clean it up
41        let _ = std::fs::remove_file(&path);
42        None
43    }
44}
45
46/// Check if a process with the given PID is alive using `kill(pid, 0)`.
47///
48/// Signal 0 checks for process existence without actually sending a signal.
49fn is_process_alive(pid: u32) -> bool {
50    // kill with signal 0 is a standard POSIX existence check.
51    // Returns 0 if the process exists and we have permission to signal it.
52    // Returns -1 with ESRCH if the process does not exist,
53    // or -1 with EPERM if it exists but we lack permission (still alive).
54    let ret = unsafe { libc::kill(pid as libc::pid_t, 0) };
55    if ret == 0 {
56        return true;
57    }
58    // Use std::io::Error to portably retrieve errno
59    std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn current_process_is_alive() {
68        assert!(is_process_alive(std::process::id()));
69    }
70
71    #[test]
72    fn bogus_pid_is_not_alive() {
73        // PID 4_000_000 is almost certainly unused
74        assert!(!is_process_alive(4_000_000));
75    }
76}