//! Filesystem-backed `SessionLockRepository` implementation using a PID file //! (`/.lock`) with atomic `O_CREAT|O_EXCL` acquisition. use std::convert::TryInto; use std::io::Write; use std::path::Path; use zesdex_domain::auth::{RepositoryError, SessionLockRepository}; /// Concrete filesystem session-lock repository. #[derive(Debug, Clone, Default)] pub struct FileSystemSessionLockRepository; impl FileSystemSessionLockRepository { pub fn new() -> Self { FileSystemSessionLockRepository } } impl SessionLockRepository for FileSystemSessionLockRepository { fn try_lock(&self, session_dir: &Path) -> Result { let path = session_dir.join(".lock"); let pid = std::process::id(); match std::fs::OpenOptions::new() .create_new(true) .write(true) .open(&path) { Ok(mut file) => { write!(file, "{pid}")?; file.sync_all()?; return Ok(true); } Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => {} Err(e) => return Err(RepositoryError::Io(e)), } let content = std::fs::read_to_string(&path).unwrap_or_default(); if let Ok(existing_pid) = content.trim().parse::() { if self.is_alive(existing_pid) { return Ok(false); } } let tmp = path.with_extension("lock.tmp"); { let mut tmp_file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(&tmp)?; write!(tmp_file, "{pid}")?; tmp_file.sync_all()?; } std::fs::rename(&tmp, &path)?; if let Some(parent) = path.parent() { let _ = std::fs::File::open(parent).and_then(|d| d.sync_all()); } Ok(true) } fn unlock(&self, session_dir: &Path) -> Result<(), RepositoryError> { let path = session_dir.join(".lock"); let _ = std::fs::remove_file(path); Ok(()) } fn is_alive(&self, pid: u32) -> bool { let pid_signed: i32 = match pid.try_into() { Ok(p) => p, Err(_) => return false, }; if unsafe { libc::kill(pid_signed, 0) != 0 } { return false; } let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe")); if let Ok(target) = std::fs::read_link(&proc_exe) { if let Ok(exe) = std::env::current_exe() { if target != exe { return false; } } } true } }