use std::path::Path;
use anyhow::{Context, Result};
use tracing::{info, warn};
use super::cleanup::{is_arcbox_daemon, terminate_stale_daemon};
pub struct DaemonLock {
_file: std::fs::File,
displaced_stale: bool,
}
impl DaemonLock {
pub fn acquire(lock_path: &Path) -> Result<Self> {
use std::io::{Seek, Write};
let mut file = std::fs::OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(lock_path)
.context("Failed to open daemon lock")?;
let displaced_stale = !try_flock_exclusive(&file);
if !displaced_stale {
info!("Daemon lock acquired (no stale daemon)");
} else {
let old_pid = read_pid_from_file(&mut file);
if let Some(pid) = old_pid {
if is_arcbox_daemon(pid) {
terminate_stale_daemon(pid);
} else {
warn!(pid, "Lock held by non-arcbox process, waiting for release");
}
} else {
warn!("Lock held but could not read PID, waiting for release");
}
flock_exclusive(&file).context("Failed to acquire daemon lock")?;
info!("Daemon lock acquired after stale daemon exited");
}
if let Err(e) = file.set_len(0) {
warn!(%e, "Failed to truncate daemon.lock");
}
if let Err(e) = file.seek(std::io::SeekFrom::Start(0)) {
warn!(%e, "Failed to seek daemon.lock");
}
if let Err(e) = writeln!(file, "{}", std::process::id()) {
warn!(%e, "Failed to write PID to daemon.lock");
}
Ok(Self {
_file: file,
displaced_stale,
})
}
#[must_use]
pub const fn displaced_stale_daemon(&self) -> bool {
self.displaced_stale
}
}
fn read_pid_from_file(file: &mut std::fs::File) -> Option<i32> {
use std::io::{Read, Seek};
file.seek(std::io::SeekFrom::Start(0)).ok()?;
let mut buf = String::new();
file.read_to_string(&mut buf).ok()?;
buf.trim().parse().ok()
}
fn try_flock_exclusive(file: &std::fs::File) -> bool {
use std::os::unix::io::AsRawFd;
let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
if ret == 0 {
return true;
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::EWOULDBLOCK) {
warn!(%err, "Unexpected flock error, assuming lock is held");
}
false
}
fn flock_exclusive(file: &std::fs::File) -> std::io::Result<()> {
use std::os::unix::io::AsRawFd;
let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) };
if ret == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}