use crate::persistence::error::{PersistenceError, PersistenceResult};
use std::fs::File;
use std::io;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockType {
Shared,
Exclusive,
}
pub struct FileLock {
file: File,
#[allow(dead_code)]
path: std::path::PathBuf,
lock_type: LockType,
}
impl FileLock {
pub fn acquire<P: AsRef<Path>>(path: P, lock_type: LockType) -> PersistenceResult<Self> {
let path = path.as_ref().to_path_buf();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false) .open(&path)?;
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
let lock = libc::flock {
l_type: match lock_type {
LockType::Shared => libc::LOCK_SH as i16,
LockType::Exclusive => libc::LOCK_EX as i16,
},
l_whence: libc::SEEK_SET as i16,
l_start: 0,
l_len: 0,
l_pid: 0,
};
if unsafe { libc::fcntl(fd, libc::F_SETLK, &lock) } != 0 {
return Err(PersistenceError::LockFailed {
resource: path.display().to_string(),
reason: format!("fcntl failed: {}", io::Error::last_os_error()),
});
}
}
#[cfg(target_os = "windows")]
{
use std::os::windows::io::AsRawHandle;
use winapi::um::fileapi::LockFileEx;
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
use winapi::um::winnt::HANDLE;
let handle = file.as_raw_handle() as HANDLE;
let mut overlapped = std::mem::zeroed();
let flags = match lock_type {
LockType::Exclusive => LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
LockType::Shared => LOCKFILE_FAIL_IMMEDIATELY,
};
if unsafe { LockFileEx(handle, flags, 0, !0, !0, &mut overlapped) } == 0 {
return Err(PersistenceError::LockFailed {
resource: path.display().to_string(),
reason: format!("LockFileEx failed: {}", io::Error::last_os_error()),
});
}
}
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
{
#[cfg(feature = "persistence")]
{
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
let operation = match lock_type {
LockType::Shared => libc::LOCK_SH,
LockType::Exclusive => libc::LOCK_EX,
} | libc::LOCK_NB;
if unsafe { libc::flock(fd, operation) } != 0 {
return Err(PersistenceError::LockFailed {
resource: path.display().to_string(),
reason: format!("flock failed: {}", io::Error::last_os_error()),
});
}
}
}
Ok(Self {
file,
path,
lock_type,
})
}
pub fn file(&self) -> &File {
&self.file
}
pub fn lock_type(&self) -> LockType {
self.lock_type
}
}
impl Drop for FileLock {
fn drop(&mut self) {
#[cfg(target_os = "linux")]
{
#[cfg(feature = "persistence")]
{
use std::os::unix::io::AsRawFd;
let fd = self.file.as_raw_fd();
let unlock = libc::flock {
l_type: libc::LOCK_UN as i16,
l_whence: libc::SEEK_SET as i16,
l_start: 0,
l_len: 0,
l_pid: 0,
};
unsafe {
libc::fcntl(fd, libc::F_SETLK, &unlock);
}
}
}
#[cfg(target_os = "windows")]
{
}
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
{
#[cfg(feature = "persistence")]
{
use std::os::unix::io::AsRawFd;
let fd = self.file.as_raw_fd();
unsafe {
libc::flock(fd, libc::LOCK_UN);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_file_lock_exclusive() {
let temp_dir = std::env::temp_dir().join("vicinity_lock_test");
fs::create_dir_all(&temp_dir).unwrap();
let lock_file = temp_dir.join("test.lock");
let lock1 = FileLock::acquire(&lock_file, LockType::Exclusive).unwrap();
drop(lock1);
fs::remove_file(&lock_file).ok();
fs::remove_dir_all(&temp_dir).ok();
}
}