use std::fs::File;
use std::io;
#[cfg(target_os = "linux")]
pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {
ofd_set(file, libc::F_WRLCK)
}
#[cfg(target_os = "linux")]
pub fn unlock(file: &File) -> io::Result<()> {
ofd_set(file, libc::F_UNLCK).map(|_| ())
}
#[cfg(target_os = "linux")]
fn ofd_set(file: &File, l_type: libc::c_int) -> io::Result<bool> {
use std::os::unix::io::AsRawFd;
let fl = libc::flock {
l_type: l_type as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0,
l_pid: 0,
};
let ret = unsafe {
libc::fcntl(
file.as_raw_fd(),
libc::F_OFD_SETLK,
&fl as *const libc::flock,
)
};
if ret == 0 {
return Ok(true);
}
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EACCES) | Some(libc::EAGAIN) => Ok(false),
_ => Err(err),
}
}
#[cfg(not(target_os = "linux"))]
pub fn try_lock_exclusive(file: &File) -> io::Result<bool> {
use fs2::FileExt;
match FileExt::try_lock_exclusive(file) {
Ok(()) => Ok(true),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(false),
Err(e) => Err(e),
}
}
#[cfg(not(target_os = "linux"))]
pub fn unlock(file: &File) -> io::Result<()> {
use fs2::FileExt;
FileExt::unlock(file)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exclusive_lock_excludes_a_second_open_of_the_same_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("LOCK");
let a = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(&path)
.unwrap();
assert!(try_lock_exclusive(&a).unwrap(), "first lock acquires");
let b = std::fs::OpenOptions::new().write(true).open(&path).unwrap();
assert!(
!try_lock_exclusive(&b).unwrap(),
"second open must not acquire while the first holds the lock"
);
unlock(&a).unwrap();
assert!(
try_lock_exclusive(&b).unwrap(),
"lock is free after the holder releases"
);
}
}