Skip to content

Commit

Permalink
Add Flock::relock (#2407)
Browse files Browse the repository at this point in the history
It can upgrade or downgrade a lock.

Fixes #2356
  • Loading branch information
asomers authored May 16, 2024
1 parent e7acaff commit c5af4ad
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog/2407.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `Flock::relock` for upgrading and downgrading locks.
24 changes: 24 additions & 0 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,30 @@ impl<T: Flockable> Flock<T> {
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].
Expand Down
52 changes: 50 additions & 2 deletions test/test_fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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");
}
}

0 comments on commit c5af4ad

Please sign in to comment.