use std::path::{Path, PathBuf};
use prikk_error::Result;
use crate::fsutil::{EntryKind, list_directory, read_file_if_exists, remove_file_required};
use crate::layout::{LockableContainer, RepositoryLayout};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PidLiveness {
AppearsRunning,
DoesNotAppearRunning,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeldLock {
pub path: PathBuf,
pub kind: String,
pub recorded_pid: Option<u32>,
pub liveness: PidLiveness,
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn check_pid_liveness(pid: u32) -> PidLiveness {
let Ok(raw) = i32::try_from(pid) else {
return PidLiveness::Unknown;
};
let Some(rustix_pid) = rustix::process::Pid::from_raw(raw) else {
return PidLiveness::Unknown;
};
match rustix::process::test_kill_process(rustix_pid) {
Ok(()) => PidLiveness::AppearsRunning,
Err(rustix::io::Errno::PERM) => PidLiveness::AppearsRunning,
Err(rustix::io::Errno::SRCH) => PidLiveness::DoesNotAppearRunning,
Err(_) => PidLiveness::Unknown,
}
}
#[cfg(target_os = "windows")]
fn check_pid_liveness(pid: u32) -> PidLiveness {
match prikk_ffi::process_liveness(pid) {
prikk_ffi::ProcessLiveness::Exists => PidLiveness::AppearsRunning,
prikk_ffi::ProcessLiveness::DoesNotExist => PidLiveness::DoesNotAppearRunning,
prikk_ffi::ProcessLiveness::Indeterminate => PidLiveness::Unknown,
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
fn check_pid_liveness(_pid: u32) -> PidLiveness {
PidLiveness::Unknown
}
fn parse_lock_body(bytes: &[u8]) -> (String, Option<u32>) {
let body = String::from_utf8_lossy(bytes);
let mut kind = String::new();
let mut recorded_pid = None;
for line in body.lines() {
if let Some(value) = line.strip_prefix("kind=") {
kind = value.to_string();
} else if let Some(value) = line.strip_prefix("pid=") {
recorded_pid = value.parse::<u32>().ok();
}
}
(kind, recorded_pid)
}
fn read_lock_if_present(layout: &RepositoryLayout, path: &Path) -> Result<Option<HeldLock>> {
let relative = layout.repository_relative(path)?;
let Some(bytes) = read_file_if_exists(layout.repository_mutation_root(), &relative)? else {
return Ok(None);
};
let (kind, recorded_pid) = parse_lock_body(&bytes);
let liveness = recorded_pid.map_or(PidLiveness::Unknown, check_pid_liveness);
Ok(Some(HeldLock {
path: path.to_path_buf(),
kind,
recorded_pid,
liveness,
}))
}
pub fn list_held_locks(layout: &RepositoryLayout) -> Result<Vec<HeldLock>> {
let mut locks = Vec::new();
if let Some(lock) = read_lock_if_present(layout, &layout.default_active_lock_path())? {
locks.push(lock);
}
let ref_locks_dir = layout.refs_dir().join("locks");
let ref_locks_relative = layout.repository_relative(&ref_locks_dir)?;
for entry in list_directory(layout.repository_mutation_root(), &ref_locks_relative)? {
if entry.kind != EntryKind::Regular {
continue;
}
let path = ref_locks_dir.join(&entry.name);
if let Some(lock) = read_lock_if_present(layout, &path)? {
locks.push(lock);
}
}
for container in LockableContainer::ALL {
if let Some(lock) =
read_lock_if_present(layout, &layout.lockable_container_lock_path(container))?
{
locks.push(lock);
}
}
Ok(locks)
}
#[must_use]
pub fn find_held_lock<'a>(locks: &'a [HeldLock], target: &Path) -> Option<&'a HeldLock> {
locks
.iter()
.find(|lock| paths_name_the_same_file(&lock.path, target))
}
fn paths_name_the_same_file(a: &Path, b: &Path) -> bool {
match (std::fs::canonicalize(a), std::fs::canonicalize(b)) {
(Ok(a), Ok(b)) => a == b,
_ => a == b,
}
}
pub fn clear_lock(layout: &RepositoryLayout, path: &Path) -> Result<()> {
let relative = layout.repository_relative(path)?;
remove_file_required(layout.repository_mutation_root(), &relative)
}
#[cfg(test)]
mod tests;