Skip to main content

retro_core/
lock.rs

1use crate::errors::CoreError;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5/// A PID-based lockfile for mutual exclusion.
6pub struct LockFile {
7    path: PathBuf,
8}
9
10impl LockFile {
11    /// Try to acquire the lockfile. Returns error if already locked by a running process.
12    pub fn acquire(path: &Path) -> Result<Self, CoreError> {
13        if path.exists() {
14            // Check if the holding process is still alive
15            let contents = fs::read_to_string(path)
16                .map_err(|e| CoreError::Lock(format!("reading lockfile: {e}")))?;
17            if let Ok(pid) = contents.trim().parse::<i32>() {
18                if is_process_alive(pid) {
19                    return Err(CoreError::Lock(format!(
20                        "another retro process is running (PID {pid})"
21                    )));
22                }
23            }
24            // Stale lockfile — remove it
25            let _ = fs::remove_file(path);
26        }
27
28        let pid = std::process::id();
29        fs::write(path, pid.to_string())
30            .map_err(|e| CoreError::Lock(format!("writing lockfile: {e}")))?;
31
32        Ok(LockFile {
33            path: path.to_path_buf(),
34        })
35    }
36
37    /// Try to acquire the lockfile, returning None if already locked (instead of an error).
38    /// Used by --auto mode to silently skip when another process is running.
39    pub fn try_acquire(path: &Path) -> Option<Self> {
40        match Self::acquire(path) {
41            Ok(lock) => Some(lock),
42            Err(_) => None,
43        }
44    }
45}
46
47impl Drop for LockFile {
48    fn drop(&mut self) {
49        let _ = fs::remove_file(&self.path);
50    }
51}
52
53/// Check if a process is alive using kill(pid, 0) — portable across Linux and macOS.
54fn is_process_alive(pid: i32) -> bool {
55    // kill with signal 0 checks process existence without sending a signal.
56    // Returns 0 if process exists, -1 with ESRCH if it doesn't.
57    unsafe { libc::kill(pid, 0) == 0 }
58}