Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Flock object for automatic unlock on drop. #2170

Merged
merged 35 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bdc7e8e
Added object for automatic unlock on drop.
coderBlitz Oct 10, 2023
1740131
Added appropriate changelog file.
coderBlitz Oct 10, 2023
aa8369d
Fixed doc error on `Flock`.
coderBlitz Oct 10, 2023
a566bb2
Requested changes to make `fcntl::flock` private and OwnedFd instead …
coderBlitz Oct 14, 2023
7912e88
Indent fix.
coderBlitz Oct 14, 2023
d234054
Removed doc links to private item, updated imports.
coderBlitz Oct 14, 2023
e0c53bb
More import fixes.
coderBlitz Oct 14, 2023
ad58d40
Format changes.
coderBlitz Oct 14, 2023
647059d
Remove unused import for redox.
coderBlitz Oct 14, 2023
7cf6765
Added `Flockable` trait per discussions, added tests for `Flock`.
coderBlitz Nov 19, 2023
99e9f6c
Simplified flock error conditionals.
coderBlitz Nov 19, 2023
32443fa
Added missing cfg flags for `Flockable`.
coderBlitz Nov 19, 2023
81ca0e5
Added missing cfg flags for impl of `Flockable` on `File`.
coderBlitz Nov 19, 2023
5704efe
Update src/fcntl.rs
coderBlitz Nov 20, 2023
f256883
Merge remote-tracking branch 'upstream/master'
coderBlitz Nov 20, 2023
2103ec7
Updated `Flockable` comment per suggestion.
coderBlitz Nov 22, 2023
5609d07
Finalized `FlockArg` as enum and removed TODO accordingly, removed fl…
coderBlitz Nov 22, 2023
2eaca17
Implemented `Flockable` for `OwnedFd` as well.
coderBlitz Nov 22, 2023
4919bba
Fixed linting error.
coderBlitz Nov 22, 2023
91c9590
Updated changelog accordingly.
coderBlitz Nov 22, 2023
d61c038
Corrected errno logic in `Flock::lock`.
coderBlitz Nov 22, 2023
40fda24
Properly dropped inner type for `Flock`.
coderBlitz Nov 22, 2023
b544cd1
Replaced `ManuallyDrop` with `Option` as inner `Flock` type to avoid …
coderBlitz Nov 22, 2023
91273fb
Fixed linting errors.
coderBlitz Nov 22, 2023
836986c
Removed unnecessary cfg condition, updated documentation.
coderBlitz Nov 22, 2023
723c386
Modified Flock behavior for drop() and unlock().
coderBlitz Nov 22, 2023
85b76ee
Reverted changes to original `flock()` and `FlockArg` for deprecation…
coderBlitz Nov 22, 2023
c470752
Refactored `Flock` to wrap T directly and avoid a double-free after `…
coderBlitz Nov 23, 2023
a28425b
Fixed linting errors.
coderBlitz Nov 23, 2023
47e1f5d
More linting fixes.
coderBlitz Nov 23, 2023
936e3ce
Fixed example code for `Flock::unlock()`.
coderBlitz Nov 23, 2023
bcb5880
Made requested changes.
coderBlitz Dec 2, 2023
6dc7bed
Merge branch 'master' into master
coderBlitz Dec 2, 2023
b04334f
Removed duplicate import.
coderBlitz Dec 2, 2023
d3225bb
Format change.
coderBlitz Dec 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog/2170.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added newtype `Flock` to automatically unlock a held flock upon drop.
Added `Flockable` trait to represent valid types for `Flock`.
1 change: 1 addition & 0 deletions changelog/2170.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Made `fcntl::flock` private in favor of new Flock wrapper.
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
130 changes: 107 additions & 23 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ use libc::{self, c_int, c_uint, size_t, ssize_t};
))]
use std::ffi::CStr;
use std::ffi::OsString;
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
use std::mem::ManuallyDrop;
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
use std::ops::{Deref, DerefMut};
#[cfg(not(target_os = "redox"))]
use std::os::raw;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::RawFd;
// For splice and copy_file_range
#[cfg(all(not(any(target_os = "redox", target_os = "solaris")), unix))]
SteveLauC marked this conversation as resolved.
Show resolved Hide resolved
use std::os::unix::io::{AsRawFd, OwnedFd};
#[cfg(any(
target_os = "netbsd",
apple_targets,
Expand All @@ -27,10 +33,7 @@ use std::path::PathBuf;
target_os = "freebsd",
target_os = "linux"
))]
use std::{
os::unix::io::{AsFd, AsRawFd},
ptr,
};
use std::{os::unix::io::AsFd, ptr};

#[cfg(feature = "fs")]
use crate::{sys::stat::Mode, NixPath, Result};
Expand Down Expand Up @@ -596,39 +599,120 @@ pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
Errno::result(res)
}

// TODO: convert to libc_enum
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum FlockArg {
LockShared,
LockExclusive,
Unlock,
LockSharedNonblock,
LockExclusiveNonblock,
UnlockNonblock,
}

/// Represents valid types for flock.
///
/// # Safety
/// Types implementing this must be `!Clone`.
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
use self::FlockArg::*;
pub unsafe trait Flockable: AsRawFd {}

let res = unsafe {
match arg {
LockShared => libc::flock(fd, libc::LOCK_SH),
LockExclusive => libc::flock(fd, libc::LOCK_EX),
Unlock => libc::flock(fd, libc::LOCK_UN),
LockSharedNonblock => {
libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB)
}
LockExclusiveNonblock => {
libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB)
}
UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB),
/// Represents an owned flock, which unlocks on drop.
///
/// See flock(2) for details on locking semantics.
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
// `ManuallyDrop` is necessary to circumvent move out of `Drop` type error.
SteveLauC marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
#[derive(Debug)]
pub struct Flock<T: Flockable>(ManuallyDrop<T>);

#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
impl<T: Flockable> Drop for Flock<T> {
fn drop(&mut self) {
// Result is ignored because flock has no documented failure cases.
_ = unsafe { libc::flock(self.0.as_raw_fd(), libc::LOCK_UN) };
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
impl<T: Flockable> Deref for Flock<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
impl<T: Flockable> DerefMut for Flock<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
impl<T: Flockable> Flock<T> {
/// Obtain a/an flock.
///
/// # Example
/// ```
/// # use std::fs::File;
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
/// # use nix::fcntl::{Flock, FlockArg};
/// fn do_stuff(file: File) -> nix::Result<()> {
/// let lock = match Flock::lock(file, FlockArg::LockExclusive) {
/// Ok(l) => l,
/// Err((_, e)) => return Err(e),
/// };
///
/// // Do stuff
///
/// Ok(())
/// } // File is unlocked once `lock` goes out of scope.
pub fn lock(t: T, args: FlockArg) -> std::result::Result<Self, (T, Errno)> {
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
let flags = match args {
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,
};
match unsafe { libc::flock(t.as_raw_fd(), flags) } {
0 => Ok(Self(ManuallyDrop::new(t))),
e => Err((t, Errno::from_i32(e))),
}
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved
};
}

Errno::result(res).map(drop)
/// Remove the lock and return the object wrapped within.
///
/// # Example
/// ```
/// # use std::fs::File;
/// # use nix::fcntl::{Flock, FlockArg};
/// fn do_stuff(file: File) -> nix::Result<()> {
/// let lock = match Flock::lock(file, FlockArg::LockExclusive) {
/// Ok(l) => l,
/// Err((_,e)) => return Err(e),
/// };
///
/// // Do critical section
///
/// // Unlock
/// let file = lock.unlock();
///
/// // Do anything else
///
/// Ok(())
/// }
pub fn unlock(mut self) -> T {
_ = unsafe { libc::flock(self.0.as_raw_fd(), libc::LOCK_UN) };

// Safety: `self.0` never used again.
unsafe { ManuallyDrop::take(&mut self.0) }
}
}

// Safety: `File` is not [std::clone::Clone].
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
unsafe impl Flockable for std::fs::File {}
coderBlitz marked this conversation as resolved.
Show resolved Hide resolved

// Safety: `OwnedFd` is not [std::clone::Clone].
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
unsafe impl Flockable for OwnedFd {}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
Expand Down
54 changes: 54 additions & 0 deletions test/test_fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,3 +630,57 @@ fn test_f_kinfo() {
assert_ne!(res, -1);
assert_eq!(path, tmp.path());
}

/// Test `Flock` and associated functions.
///
#[cfg(not(any(target_os = "redox", target_os = "solaris")))]
mod test_flock {
use nix::fcntl::*;
use tempfile::NamedTempFile;

/// Verify that `Flock::lock()` correctly obtains a lock, and subsequently unlocks upon drop.
#[test]
fn verify_lock_and_drop() {
// Get 2 `File` handles to same underlying file.
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 = match Flock::lock(file2, FlockArg::LockExclusiveNonblock) {
Ok(_) => panic!("Expected second exclusive lock to fail."),
Err((f, _)) => f,
};

// Drop first lock
std::mem::drop(lock1);

// Attempt to lock second handle again (but successfully)
if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
panic!("Expected locking to be successful.");
}
}

/// Verify that `Flock::unlock()` correctly obtains unlocks.
#[test]
fn verify_unlock() {
// Get 2 `File` handles to same underlying file.
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();

// Unlock and retain file so any erroneous flocks also remain present.
let _file1 = lock1.unlock();

// Attempt to lock second handle.
if Flock::lock(file2, FlockArg::LockExclusiveNonblock).is_err() {
panic!("Expected locking to be successful.");
}
}
}
Loading