use std::io::Write as _;
use std::path::PathBuf;
use prikk_store::{HeldLock, PidLiveness, clear_lock, find_held_lock, list_held_locks};
pub(crate) fn run_unlock(root: PathBuf, args: Vec<String>) -> std::result::Result<(), String> {
let mut args = args.into_iter();
let mut target: Option<String> = None;
let mut skip_confirmation = false;
while let Some(arg) = args.next() {
match arg.as_str() {
"--lock" => {
target = Some(
args.next()
.ok_or_else(|| "--lock requires a path".to_string())?,
);
}
"--yes" | "--force" => skip_confirmation = true,
other => return Err(format!("unknown unlock argument: {other}")),
}
}
let layout = crate::open_repository(root)?;
let locks = list_held_locks(&layout).map_err(|err| err.to_string())?;
let Some(target) = target else {
print_locks(&locks);
return Ok(());
};
let target_path = PathBuf::from(&target);
let Some(lock) = find_held_lock(&locks, &target_path) else {
let resolved_note = match std::fs::canonicalize(&target_path) {
Ok(resolved) if resolved != target_path => {
format!(
" (resolves to {}, which also has no held lock)",
resolved.display()
)
}
_ => String::new(),
};
return Err(format!(
"no held lock at {target}{resolved_note} -- run `prikk unlock` with no arguments to list \
what is currently held"
));
};
print_locks(std::slice::from_ref(lock));
if !skip_confirmation && !confirm_interactively(lock) {
println!("aborted: lock not cleared");
return Ok(());
}
clear_lock(&layout, &lock.path).map_err(|err| err.to_string())?;
println!("cleared: {}", lock.path.display());
Ok(())
}
fn print_locks(locks: &[HeldLock]) {
if locks.is_empty() {
println!("no locks currently held");
return;
}
for lock in locks {
println!("{}", lock.path.display());
println!(" kind: {}", lock.kind);
match lock.recorded_pid {
Some(pid) => println!(" recorded pid: {pid}"),
None => println!(" recorded pid: (unparseable)"),
}
println!(" liveness: {}", describe_liveness(lock.liveness));
}
println!();
println!(
"liveness is advisory only: a positive result is reliable evidence the process is still \
running, but a negative or unknown result is NOT proof it is safe to clear -- PID reuse \
and container namespace isolation can both make a genuinely running process appear absent. \
Clear a lock only if you have independently confirmed the process that created it is gone."
);
}
fn describe_liveness(liveness: PidLiveness) -> &'static str {
match liveness {
PidLiveness::AppearsRunning => "appears running -- do not clear",
PidLiveness::DoesNotAppearRunning => {
"does not appear to be running (not proof it is safe to clear)"
}
PidLiveness::Unknown => "unknown (not proof it is safe to clear)",
}
}
fn confirm_interactively(lock: &HeldLock) -> bool {
print!(
"Clearing this lock while its process is still running can corrupt this repository. \
Type 'yes' to confirm clearing {}: ",
lock.path.display()
);
if std::io::stdout().flush().is_err() {
return false;
}
let mut input = String::new();
if std::io::stdin().read_line(&mut input).is_err() {
return false;
}
input.trim() == "yes"
}