use nix::fcntl::{flock, FlockArg};
use std::fs::{metadata, File, OpenOptions};
use std::io::Result;
use std::os::unix::{fs::MetadataExt, io::AsRawFd};
use std::path::Path;
pub trait OpenAndLock {
fn open_and_lock<P: AsRef<Path>>(&self, path: P) -> Result<File>;
fn try_open_and_lock<P: AsRef<Path>>(&self, path: P) -> Result<File>;
}
fn open_and_lock<P: AsRef<Path>>(
options: &OpenOptions,
path: P,
lock_mode: FlockArg,
) -> Result<File> {
loop {
let file = options.open(&path)?;
flock(file.as_raw_fd(), lock_mode)?;
if let Ok(metadata_at_path) = metadata(&path) {
let file_metadata = file.metadata()?;
if metadata_at_path.dev() != file_metadata.dev() ||
metadata_at_path.ino() != file_metadata.ino()
{
continue;
}
return Ok(file);
} else {
continue;
}
}
}
impl OpenAndLock for OpenOptions {
fn open_and_lock<P: AsRef<Path>>(&self, path: P) -> Result<File> {
open_and_lock(self, path, FlockArg::LockExclusive)
}
fn try_open_and_lock<P: AsRef<Path>>(&self, path: P) -> Result<File> {
open_and_lock(self, path, FlockArg::LockExclusiveNonblock)
}
}
#[cfg(test)]
mod tests {
use crate::OpenAndLock;
use std::fs::OpenOptions;
use std::io;
use tempfile::tempdir;
#[test]
fn try_lock_locked() {
let dir = tempdir().unwrap();
let mut lock_path = dir.path().to_owned();
lock_path.push("foo.lock");
let _file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open_and_lock(&lock_path)
.unwrap();
let error = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.try_open_and_lock(&lock_path)
.unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::WouldBlock);
}
}