use std::fs::File; use std::io; use std::os::unix::io::AsRawFd; /// Try to acquire an exclusive (write) lock on `file` without blocking. /// /// Returns `Ok(true)` if the lock was acquired, `Ok(false)` if another /// process/fd already holds the lock, and `Err` for unexpected errors. pub fn try_flock_exclusive(file: &File) -> io::Result { // SAFETY: flock() on a valid fd is safe; LOCK_NB makes it non-blocking. let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }; if ret == 0 { Ok(true) } else { let err = io::Error::last_os_error(); match err.raw_os_error() { Some(libc::EWOULDBLOCK) => Ok(false), _ => Err(err), } } } /// Try to acquire a shared (read) lock on `file` without blocking. /// /// Returns `Ok(true)` if the lock was acquired, `Ok(false)` if another /// process/fd already holds an incompatible lock, and `Err` for unexpected /// errors. pub fn try_flock_shared(file: &File) -> io::Result { // SAFETY: flock() on a valid fd is safe; LOCK_NB makes it non-blocking. let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) }; if ret == 0 { Ok(true) } else { let err = io::Error::last_os_error(); match err.raw_os_error() { Some(libc::EWOULDBLOCK) => Ok(false), _ => Err(err), } } } /// Release any lock held on `file`. pub fn flock_unlock(file: &File) -> io::Result<()> { // SAFETY: flock() with LOCK_UN on a valid fd is safe. let ret = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) }; if ret == 0 { Ok(()) } else { Err(io::Error::last_os_error()) } } #[cfg(test)] #[expect( clippy::disallowed_methods, reason = "tests exercise sync flock semantics on real temp files; uses std::fs::File directly" )] mod tests { use std::fs::File; use super::*; #[test] fn acquire_exclusive_lock() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("lock"); let file = File::create(&path).unwrap(); let acquired = try_flock_exclusive(&file).unwrap(); assert!(acquired); } #[test] fn unlock_then_reacquire() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("lock"); let file = File::create(&path).unwrap(); assert!(try_flock_exclusive(&file).unwrap()); flock_unlock(&file).unwrap(); assert!(try_flock_exclusive(&file).unwrap()); } #[test] fn second_fd_blocked() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("lock"); let file1 = File::create(&path).unwrap(); let file2 = File::open(&path).unwrap(); assert!(try_flock_exclusive(&file1).unwrap()); assert!(!try_flock_exclusive(&file2).unwrap()); } }