Skip to content

Basic fcntl-style locking. #555

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

Merged
merged 4 commits into from
Mar 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 35 additions & 1 deletion src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ use crate::fs::Advice;
target_os = "redox",
)))]
use crate::fs::FallocateFlags;
#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
#[cfg(not(target_os = "wasi"))]
use crate::fs::FlockOperation;
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
use crate::fs::MemfdFlags;
Expand Down Expand Up @@ -829,6 +829,40 @@ pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Resul
unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) }
}

#[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "redox",
target_os = "wasi"
)))]
#[inline]
pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
use c::{flock, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, SEEK_SET};

let (cmd, l_type) = match operation {
FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
};

unsafe {
let mut lock: flock = core::mem::zeroed();
lock.l_type = l_type as _;

// When `l_len` is zero, this locks all the bytes from
// `l_whence`/`l_start` to the end of the file, even as the
// file grows dynamically.
lock.l_whence = SEEK_SET as _;
lock.l_start = 0;
lock.l_len = 0;

ret(c::fcntl(borrowed_fd(fd), cmd, &lock))
}
}

pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> {
let (whence, offset): (c::c_int, libc_off_t) = match pos {
SeekFrom::Start(pos) => {
Expand Down
5 changes: 3 additions & 2 deletions src/backend/libc/fs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -785,10 +785,11 @@ bitflags! {
}
}

/// `LOCK_*` constants for use with [`flock`]
/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`].
///
/// [`flock`]: crate::fs::flock
#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
/// [`fcntl_lock`]: crate::fs::fcntl_lock
#[cfg(not(target_os = "wasi"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum FlockOperation {
Expand Down
52 changes: 52 additions & 0 deletions src/backend/linux_raw/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,58 @@ pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Resul
}
}

#[inline]
pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
#[cfg(target_pointer_width = "64")]
use linux_raw_sys::general::{flock, F_SETLK, F_SETLKW};
#[cfg(target_pointer_width = "32")]
use linux_raw_sys::general::{flock64 as flock, F_SETLK64 as F_SETLK, F_SETLKW64 as F_SETLKW};
use linux_raw_sys::general::{F_RDLCK, F_UNLCK, F_WRLCK};

let (cmd, l_type) = match operation {
FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
};

unsafe {
let lock = flock {
l_type: l_type as _,

// When `l_len` is zero, this locks all the bytes from
// `l_whence`/`l_start` to the end of the file, even as the
// file grows dynamically.
l_whence: SEEK_SET as _,
l_start: 0,
l_len: 0,

..core::mem::zeroed()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like we could rely on core::mem::zeroed() also handling the zero-init for the other members?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting l_len to zero is a special case in the documented behavior of fcntl, so I like keeping it explicit. I've now added more comments about this though.

};

#[cfg(target_pointer_width = "32")]
{
ret(syscall_readonly!(
__NR_fcntl64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about if cfg!(target_pointer_width = "32") { __NR_fcntl64 } else { __NR_fcntl } here and dedup the blocks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately syscall_readonly! is a macro that does special things with the first parameter and needs it to be a __NR_* literal.

fd,
c_uint(cmd),
by_ref(&lock)
))
}
#[cfg(target_pointer_width = "64")]
{
ret(syscall_readonly!(
__NR_fcntl,
fd,
c_uint(cmd),
by_ref(&lock)
))
}
}
}

#[inline]
pub(crate) fn rename(oldname: &CStr, newname: &CStr) -> io::Result<()> {
#[cfg(target_arch = "riscv64")]
Expand Down
3 changes: 2 additions & 1 deletion src/backend/linux_raw/fs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,9 +518,10 @@ bitflags! {
}
}

/// `LOCK_*` constants for use with [`flock`]
/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`].
///
/// [`flock`]: crate::fs::flock
/// [`fcntl_lock`]: crate::fs::fcntl_lock
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FlockOperation {
Expand Down
36 changes: 36 additions & 0 deletions src/fs/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
//! a type-safe API, rustix makes them all separate functions so that they
//! can have dedicated static type signatures.

#[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "redox",
target_os = "wasi"
)))]
use crate::fs::FlockOperation;
use crate::{backend, io};
use backend::fd::AsFd;
use backend::fs::types::OFlags;
Expand Down Expand Up @@ -85,3 +92,32 @@ pub use backend::fs::types::SealFlags;
pub fn fcntl_add_seals<Fd: AsFd>(fd: Fd, seals: SealFlags) -> io::Result<()> {
backend::fs::syscalls::fcntl_add_seals(fd.as_fd(), seals)
}

/// `fcntl(fd, F_SETLK)`—Acquire or release an `fcntl`-style lock.
///
/// This function doesn't currently have an offset or len; it currently always
/// sets the `l_len` field to 0, which is a special case that means the entire
/// file should be locked.
///
/// Unlike `flock`-style locks, `fcntl`-style locks are process-associated,
/// meaning that they don't guard against being acquired by two threads in
/// the same process.
///
/// # References
/// - [POSIX]
/// - [Linux]
///
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
#[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "redox",
target_os = "wasi"
)))]
#[inline]
#[doc(alias = "F_SETLK")]
#[doc(alias = "F_SETLKW")]
pub fn fcntl_lock<Fd: AsFd>(fd: Fd, operation: FlockOperation) -> io::Result<()> {
backend::fs::syscalls::fcntl_lock(fd.as_fd(), operation)
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/fs/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::process::{Gid, Uid};
use crate::{backend, io};
use backend::fd::{AsFd, BorrowedFd};

#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
#[cfg(not(target_os = "wasi"))]
pub use backend::fs::types::FlockOperation;

#[cfg(not(any(
Expand Down
4 changes: 2 additions & 2 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod dir;
mod fadvise;
pub(crate) mod fcntl;
#[cfg(apple)]
mod fcntl_darwin;
mod fcntl_apple;
#[cfg(apple)]
mod fcopyfile;
pub(crate) mod fd;
Expand Down Expand Up @@ -66,7 +66,7 @@ pub use dir::{Dir, DirEntry};
pub use fadvise::{fadvise, Advice};
pub use fcntl::*;
#[cfg(apple)]
pub use fcntl_darwin::{fcntl_fullfsync, fcntl_rdadvise};
pub use fcntl_apple::{fcntl_fullfsync, fcntl_rdadvise};
#[cfg(apple)]
pub use fcopyfile::*;
pub use fd::*;
Expand Down
33 changes: 33 additions & 0 deletions tests/fs/fcntl_lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#[test]
fn test_fcntl_lock() {
use rustix::fs::{fcntl_lock, FlockOperation};

let f = tempfile::tempfile().unwrap();
fcntl_lock(&f, FlockOperation::LockExclusive).unwrap();
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
let g = tempfile::tempfile().unwrap();
fcntl_lock(&g, FlockOperation::LockExclusive).unwrap();
fcntl_lock(&g, FlockOperation::Unlock).unwrap();
drop(f);
drop(g);

let f = tempfile::tempfile().unwrap();
fcntl_lock(&f, FlockOperation::LockShared).unwrap();
let g = tempfile::tempfile().unwrap();
fcntl_lock(&g, FlockOperation::LockShared).unwrap();
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
fcntl_lock(&g, FlockOperation::Unlock).unwrap();
drop(f);
drop(g);

let f = tempfile::tempfile().unwrap();
fcntl_lock(&f, FlockOperation::LockShared).unwrap();
fcntl_lock(&f, FlockOperation::LockExclusive).unwrap();
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
let g = tempfile::tempfile().unwrap();
fcntl_lock(&g, FlockOperation::LockShared).unwrap();
fcntl_lock(&g, FlockOperation::LockExclusive).unwrap();
fcntl_lock(&g, FlockOperation::Unlock).unwrap();
drop(f);
drop(g);
}
7 changes: 7 additions & 0 deletions tests/fs/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
mod cwd;
mod dir;
mod fcntl;
#[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "redox",
target_os = "wasi"
)))]
mod fcntl_lock;
mod file;
#[cfg(not(target_os = "wasi"))]
mod flock;
Expand Down