Skip to content

Commit 9b39cf9

Browse files
Added Flock object for automatic unlock on drop. (#2170)
* Added object for automatic unlock on drop. * Added appropriate changelog file. * Fixed doc error on `Flock`. * Requested changes to make `fcntl::flock` private and OwnedFd instead of RawFd. * Indent fix. * Removed doc links to private item, updated imports. * More import fixes. * Format changes. * Remove unused import for redox. * Added `Flockable` trait per discussions, added tests for `Flock`. * Simplified flock error conditionals. * Added missing cfg flags for `Flockable`. * Added missing cfg flags for impl of `Flockable` on `File`. * Update src/fcntl.rs Co-authored-by: SteveLauC <stevelauc@outlook.com> * Updated `Flockable` comment per suggestion. * Finalized `FlockArg` as enum and removed TODO accordingly, removed flock wrapper entirely. * Implemented `Flockable` for `OwnedFd` as well. * Fixed linting error. * Updated changelog accordingly. * Corrected errno logic in `Flock::lock`. * Properly dropped inner type for `Flock`. * Replaced `ManuallyDrop` with `Option` as inner `Flock` type to avoid double-free. * Fixed linting errors. * Removed unnecessary cfg condition, updated documentation. * Modified Flock behavior for drop() and unlock(). * Reverted changes to original `flock()` and `FlockArg` for deprecation period, added EINVAL case if the unlock operation is given to `Flock::lock()`. * Refactored `Flock` to wrap T directly and avoid a double-free after `unlock()` is used. * Fixed linting errors. * More linting fixes. * Fixed example code for `Flock::unlock()`. * Made requested changes. * Removed duplicate import. * Format change. --------- Co-authored-by: SteveLauC <stevelauc@outlook.com>
1 parent 2ad5573 commit 9b39cf9

File tree

3 files changed

+178
-6
lines changed

3 files changed

+178
-6
lines changed

changelog/2170.added.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added newtype `Flock` to automatically unlock a held flock upon drop.
2+
Added `Flockable` trait to represent valid types for `Flock`.

src/fcntl.rs

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ use libc::{self, c_int, c_uint, size_t, ssize_t};
1010
))]
1111
use std::ffi::CStr;
1212
use std::ffi::OsString;
13+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
14+
use std::ops::{Deref, DerefMut};
1315
#[cfg(not(target_os = "redox"))]
1416
use std::os::raw;
1517
use std::os::unix::ffi::OsStringExt;
1618
use std::os::unix::io::RawFd;
17-
// For splice and copy_file_range
19+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
20+
use std::os::unix::io::{AsRawFd, OwnedFd};
1821
#[cfg(any(
1922
target_os = "netbsd",
2023
apple_targets,
@@ -23,10 +26,7 @@ use std::os::unix::io::RawFd;
2326
))]
2427
use std::path::PathBuf;
2528
#[cfg(any(linux_android, target_os = "freebsd"))]
26-
use std::{
27-
os::unix::io::{AsFd, AsRawFd},
28-
ptr,
29-
};
29+
use std::{os::unix::io::AsFd, ptr};
3030

3131
#[cfg(feature = "fs")]
3232
use crate::{sys::stat::Mode, NixPath, Result};
@@ -578,7 +578,6 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
578578
Errno::result(res)
579579
}
580580

581-
// TODO: convert to libc_enum
582581
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
583582
#[non_exhaustive]
584583
pub enum FlockArg {
@@ -591,6 +590,7 @@ pub enum FlockArg {
591590
}
592591

593592
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
593+
#[deprecated(since = "0.28.0", note = "`fcntl::Flock` should be used instead.")]
594594
pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
595595
use self::FlockArg::*;
596596

@@ -611,6 +611,122 @@ pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
611611

612612
Errno::result(res).map(drop)
613613
}
614+
615+
/// Represents valid types for flock.
616+
///
617+
/// # Safety
618+
/// Types implementing this must not be `Clone`.
619+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
620+
pub unsafe trait Flockable: AsRawFd {}
621+
622+
/// Represents an owned flock, which unlocks on drop.
623+
///
624+
/// See [flock(2)](https://linux.die.net/man/2/flock) for details on locking semantics.
625+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
626+
#[derive(Debug)]
627+
pub struct Flock<T: Flockable>(T);
628+
629+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
630+
impl<T: Flockable> Drop for Flock<T> {
631+
fn drop(&mut self) {
632+
let res = Errno::result(unsafe { libc::flock(self.0.as_raw_fd(), libc::LOCK_UN) });
633+
if res.is_err() && !std::thread::panicking() {
634+
panic!("Failed to remove flock: {}", res.unwrap_err());
635+
}
636+
}
637+
}
638+
639+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
640+
impl<T: Flockable> Deref for Flock<T> {
641+
type Target = T;
642+
643+
fn deref(&self) -> &Self::Target {
644+
&self.0
645+
}
646+
}
647+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
648+
impl<T: Flockable> DerefMut for Flock<T> {
649+
fn deref_mut(&mut self) -> &mut Self::Target {
650+
&mut self.0
651+
}
652+
}
653+
654+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
655+
impl<T: Flockable> Flock<T> {
656+
/// Obtain a/an flock.
657+
///
658+
/// # Example
659+
/// ```
660+
/// # use std::io::Write;
661+
/// # use std::fs::File;
662+
/// # use nix::fcntl::{Flock, FlockArg};
663+
/// # fn do_stuff(file: File) {
664+
/// let mut file = match Flock::lock(file, FlockArg::LockExclusive) {
665+
/// Ok(l) => l,
666+
/// Err(_) => return,
667+
/// };
668+
///
669+
/// // Do stuff
670+
/// let data = "Foo bar";
671+
/// _ = file.write(data.as_bytes());
672+
/// _ = file.sync_data();
673+
/// # }
674+
pub fn lock(t: T, args: FlockArg) -> std::result::Result<Self, (T, Errno)> {
675+
let flags = match args {
676+
FlockArg::LockShared => libc::LOCK_SH,
677+
FlockArg::LockExclusive => libc::LOCK_EX,
678+
FlockArg::LockSharedNonblock => libc::LOCK_SH | libc::LOCK_NB,
679+
FlockArg::LockExclusiveNonblock => libc::LOCK_EX | libc::LOCK_NB,
680+
FlockArg::Unlock | FlockArg::UnlockNonblock => return Err((t, Errno::EINVAL)),
681+
};
682+
match Errno::result(unsafe { libc::flock(t.as_raw_fd(), flags) }) {
683+
Ok(_) => Ok(Self(t)),
684+
Err(errno) => Err((t, errno)),
685+
}
686+
}
687+
688+
/// Remove the lock and return the object wrapped within.
689+
///
690+
/// # Example
691+
/// ```
692+
/// # use std::fs::File;
693+
/// # use nix::fcntl::{Flock, FlockArg};
694+
/// fn do_stuff(file: File) -> nix::Result<()> {
695+
/// let mut lock = match Flock::lock(file, FlockArg::LockExclusive) {
696+
/// Ok(l) => l,
697+
/// Err((_,e)) => return Err(e),
698+
/// };
699+
///
700+
/// // Do critical section
701+
///
702+
/// // Unlock
703+
/// let file = match lock.unlock() {
704+
/// Ok(f) => f,
705+
/// Err((_, e)) => return Err(e),
706+
/// };
707+
///
708+
/// // Do anything else
709+
///
710+
/// Ok(())
711+
/// }
712+
pub fn unlock(self) -> std::result::Result<T, (Self, Errno)> {
713+
let inner = unsafe { match Errno::result(libc::flock(self.0.as_raw_fd(), libc::LOCK_UN)) {
714+
Ok(_) => std::ptr::read(&self.0),
715+
Err(errno) => return Err((self, errno)),
716+
}};
717+
718+
std::mem::forget(self);
719+
Ok(inner)
720+
}
721+
}
722+
723+
// Safety: `File` is not [std::clone::Clone].
724+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
725+
unsafe impl Flockable for std::fs::File {}
726+
727+
// Safety: `OwnedFd` is not [std::clone::Clone].
728+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
729+
unsafe impl Flockable for OwnedFd {}
614730
}
615731

616732
#[cfg(linux_android)]

test/test_fcntl.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,3 +630,57 @@ fn test_f_kinfo() {
630630
assert_ne!(res, -1);
631631
assert_eq!(path, tmp.path());
632632
}
633+
634+
/// Test `Flock` and associated functions.
635+
///
636+
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
637+
mod test_flock {
638+
use nix::fcntl::*;
639+
use tempfile::NamedTempFile;
640+
641+
/// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop.
642+
#[test]
643+
fn verify_lock_and_drop() {
644+
// Get 2 `File` handles to same underlying file.
645+
let file1 = NamedTempFile::new().unwrap();
646+
let file2 = file1.reopen().unwrap();
647+
let file1 = file1.into_file();
648+
649+
// Lock first handle
650+
let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
651+
652+
// Attempt to lock second handle
653+
let file2 = match Flock::lock(file2, FlockArg::LockExclusiveNonblock) {
654+
Ok(_) => panic!("Expected second exclusive lock to fail."),
655+
Err((f, _)) => f,
656+
};
657+
658+
// Drop first lock
659+
std::mem::drop(lock1);
660+
661+
// Attempt to lock second handle again (but successfully)
662+
if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
663+
panic!("Expected locking to be successful.");
664+
}
665+
}
666+
667+
/// Verify that `Flock::unlock()` correctly obtains unlocks.
668+
#[test]
669+
fn verify_unlock() {
670+
// Get 2 `File` handles to same underlying file.
671+
let file1 = NamedTempFile::new().unwrap();
672+
let file2 = file1.reopen().unwrap();
673+
let file1 = file1.into_file();
674+
675+
// Lock first handle
676+
let lock1 = Flock::lock(file1, FlockArg::LockExclusive).unwrap();
677+
678+
// Unlock and retain file so any erroneous flocks also remain present.
679+
let _file1 = lock1.unlock().unwrap();
680+
681+
// Attempt to lock second handle.
682+
if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
683+
panic!("Expected locking to be successful.");
684+
}
685+
}
686+
}

0 commit comments

Comments
 (0)