use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
#[must_use]
pub fn lock_file_path(storage_root: &Path) -> PathBuf {
storage_root.join("mahbot.lock")
}
#[cfg(unix)]
pub fn try_flock(file: &File) -> io::Result<bool> {
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
let result = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
if result == 0 {
Ok(true)
} else {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EAGAIN) => Ok(false),
_ => Err(err),
}
}
}
#[cfg(windows)]
pub fn try_flock(file: &File) -> io::Result<bool> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::{ERROR_LOCK_VIOLATION, HANDLE};
use windows_sys::Win32::Storage::FileSystem::{
LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx,
};
let handle = file.as_raw_handle() as HANDLE;
const LOCK_VIOLATION: i32 = ERROR_LOCK_VIOLATION as i32;
let mut overlapped =
unsafe { std::mem::zeroed::<windows_sys::Win32::System::IO::OVERLAPPED>() };
let locked = unsafe {
LockFileEx(
handle,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
0,
0,
&mut overlapped,
)
};
if locked != 0 {
Ok(true)
} else {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(LOCK_VIOLATION) => Ok(false),
_ => Err(err),
}
}
}
#[must_use]
pub fn daemon_holds_lock(storage_root: &Path) -> bool {
let lock_path = lock_file_path(storage_root);
let Ok(file) = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&lock_path)
else {
return false;
};
try_flock(&file).is_ok_and(|acquired| !acquired)
}
const LOCK_SETTLE_RECHECKS: usize = 3;
const LOCK_SETTLE_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);
#[must_use]
pub fn daemon_holds_lock_settled(storage_root: &Path) -> bool {
for i in 0..=LOCK_SETTLE_RECHECKS {
if daemon_holds_lock(storage_root) {
return true;
}
if i < LOCK_SETTLE_RECHECKS {
std::thread::sleep(LOCK_SETTLE_INTERVAL);
}
}
false
}