From 642364d1ea33d8e44bcaa36624013f6c1a8cba18 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 15 May 2024 21:04:35 -0600 Subject: [PATCH] Add Flock::relock It can upgrade or downgrade a lock. Fixes #2356 --- changelog/2407.added.md | 1 + src/fcntl.rs | 24 +++++++++++++++++++ test/test_fcntl.rs | 52 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 changelog/2407.added.md diff --git a/changelog/2407.added.md b/changelog/2407.added.md new file mode 100644 index 0000000000..26e2cd2a21 --- /dev/null +++ b/changelog/2407.added.md @@ -0,0 +1 @@ +Added `Flock::relock` for upgrading and downgrading locks. diff --git a/src/fcntl.rs b/src/fcntl.rs index fd8d4b847c..cf87926c7b 100644 --- a/src/fcntl.rs +++ b/src/fcntl.rs @@ -955,6 +955,30 @@ impl Flock { std::mem::forget(self); Ok(inner) } + + /// Relock the file. This can upgrade or downgrade the lock type. + /// + /// # Example + /// ``` + /// # use std::fs::File; + /// # use nix::fcntl::{Flock, FlockArg}; + /// # use tempfile::tempfile; + /// let f: std::fs::File = tempfile().unwrap(); + /// let locked_file = Flock::lock(f, FlockArg::LockExclusive).unwrap(); + /// // Do stuff, then downgrade the lock + /// locked_file.relock(FlockArg::LockShared).unwrap(); + /// ``` + pub fn relock(&self, arg: FlockArg) -> Result<()> { + let flags = match arg { + FlockArg::LockShared => libc::LOCK_SH, + FlockArg::LockExclusive => libc::LOCK_EX, + FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB, + FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB, + #[allow(deprecated)] + FlockArg::Unlock | FlockArg::UnlockNonblock => return Err(Errno::EINVAL), + }; + Errno::result(unsafe { libc::flock(self.as_raw_fd(), flags) }).map(drop) + } } // Safety: `File` is not [std::clone::Clone]. diff --git a/test/test_fcntl.rs b/test/test_fcntl.rs index 2131f46c5c..5d320769d3 100644 --- a/test/test_fcntl.rs +++ b/test/test_fcntl.rs @@ -686,7 +686,7 @@ mod test_flock { /// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop. #[test] - fn verify_lock_and_drop() { + fn lock_and_drop() { // Get 2 `File` handles to same underlying file. let file1 = NamedTempFile::new().unwrap(); let file2 = file1.reopen().unwrap(); @@ -710,9 +710,32 @@ mod test_flock { } } + /// An exclusive lock can be downgraded + #[test] + fn downgrade() { + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap(); + + // Attempt to lock second handle + let file2 = Flock::lock(file2, FlockArg::LockSharedNonblock) + .unwrap_err() + .0; + + // Downgrade the lock + lock1.relock(FlockArg::LockShared).unwrap(); + + // Attempt to lock second handle again (but successfully) + Flock::lock(file2, FlockArg::LockSharedNonblock) + .expect("Expected locking to be successful."); + } + /// Verify that `Flock::unlock()` correctly obtains unlocks. #[test] - fn verify_unlock() { + fn unlock() { // Get 2 `File` handles to same underlying file. let file1 = NamedTempFile::new().unwrap(); let file2 = file1.reopen().unwrap(); @@ -729,4 +752,29 @@ mod test_flock { panic!("Expected locking to be successful."); } } + + /// A shared lock can be upgraded + #[test] + fn upgrade() { + let file1 = NamedTempFile::new().unwrap(); + let file2 = file1.reopen().unwrap(); + let file3 = file1.reopen().unwrap(); + let file1 = file1.into_file(); + + // Lock first handle + let lock1 = Flock::lock(file1, FlockArg::LockShared).unwrap(); + + // Attempt to lock second handle + { + Flock::lock(file2, FlockArg::LockSharedNonblock) + .expect("Locking should've succeeded"); + } + + // Upgrade the lock + lock1.relock(FlockArg::LockExclusive).unwrap(); + + // Acquiring an additional shared lock should fail + Flock::lock(file3, FlockArg::LockSharedNonblock) + .expect_err("Should not have been able to lock the file"); + } }