Skip to content

Commit 9d2db17

Browse files
authored
Basic fcntl-style locking. (#555)
Add an `fcntl_lock` function implementing fcntl-style process-associated locking. Currently this only supports locking the entire file.
1 parent c8f1c8d commit 9d2db17

File tree

10 files changed

+171
-7
lines changed

10 files changed

+171
-7
lines changed

src/backend/libc/fs/syscalls.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ use crate::fs::Advice;
6868
target_os = "redox",
6969
)))]
7070
use crate::fs::FallocateFlags;
71-
#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
71+
#[cfg(not(target_os = "wasi"))]
7272
use crate::fs::FlockOperation;
7373
#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
7474
use crate::fs::MemfdFlags;
@@ -829,6 +829,40 @@ pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Resul
829829
unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) }
830830
}
831831

832+
#[cfg(not(any(
833+
target_os = "emscripten",
834+
target_os = "fuchsia",
835+
target_os = "redox",
836+
target_os = "wasi"
837+
)))]
838+
#[inline]
839+
pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
840+
use c::{flock, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, SEEK_SET};
841+
842+
let (cmd, l_type) = match operation {
843+
FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
844+
FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
845+
FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
846+
FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
847+
FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
848+
FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
849+
};
850+
851+
unsafe {
852+
let mut lock: flock = core::mem::zeroed();
853+
lock.l_type = l_type as _;
854+
855+
// When `l_len` is zero, this locks all the bytes from
856+
// `l_whence`/`l_start` to the end of the file, even as the
857+
// file grows dynamically.
858+
lock.l_whence = SEEK_SET as _;
859+
lock.l_start = 0;
860+
lock.l_len = 0;
861+
862+
ret(c::fcntl(borrowed_fd(fd), cmd, &lock))
863+
}
864+
}
865+
832866
pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> {
833867
let (whence, offset): (c::c_int, libc_off_t) = match pos {
834868
SeekFrom::Start(pos) => {

src/backend/libc/fs/types.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -785,10 +785,11 @@ bitflags! {
785785
}
786786
}
787787

788-
/// `LOCK_*` constants for use with [`flock`]
788+
/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`].
789789
///
790790
/// [`flock`]: crate::fs::flock
791-
#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
791+
/// [`fcntl_lock`]: crate::fs::fcntl_lock
792+
#[cfg(not(target_os = "wasi"))]
792793
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
793794
#[repr(i32)]
794795
pub enum FlockOperation {

src/backend/linux_raw/fs/syscalls.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,58 @@ pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Resul
10221022
}
10231023
}
10241024

1025+
#[inline]
1026+
pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
1027+
#[cfg(target_pointer_width = "64")]
1028+
use linux_raw_sys::general::{flock, F_SETLK, F_SETLKW};
1029+
#[cfg(target_pointer_width = "32")]
1030+
use linux_raw_sys::general::{flock64 as flock, F_SETLK64 as F_SETLK, F_SETLKW64 as F_SETLKW};
1031+
use linux_raw_sys::general::{F_RDLCK, F_UNLCK, F_WRLCK};
1032+
1033+
let (cmd, l_type) = match operation {
1034+
FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
1035+
FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
1036+
FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
1037+
FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
1038+
FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
1039+
FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
1040+
};
1041+
1042+
unsafe {
1043+
let lock = flock {
1044+
l_type: l_type as _,
1045+
1046+
// When `l_len` is zero, this locks all the bytes from
1047+
// `l_whence`/`l_start` to the end of the file, even as the
1048+
// file grows dynamically.
1049+
l_whence: SEEK_SET as _,
1050+
l_start: 0,
1051+
l_len: 0,
1052+
1053+
..core::mem::zeroed()
1054+
};
1055+
1056+
#[cfg(target_pointer_width = "32")]
1057+
{
1058+
ret(syscall_readonly!(
1059+
__NR_fcntl64,
1060+
fd,
1061+
c_uint(cmd),
1062+
by_ref(&lock)
1063+
))
1064+
}
1065+
#[cfg(target_pointer_width = "64")]
1066+
{
1067+
ret(syscall_readonly!(
1068+
__NR_fcntl,
1069+
fd,
1070+
c_uint(cmd),
1071+
by_ref(&lock)
1072+
))
1073+
}
1074+
}
1075+
}
1076+
10251077
#[inline]
10261078
pub(crate) fn rename(oldname: &CStr, newname: &CStr) -> io::Result<()> {
10271079
#[cfg(target_arch = "riscv64")]

src/backend/linux_raw/fs/types.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,10 @@ bitflags! {
518518
}
519519
}
520520

521-
/// `LOCK_*` constants for use with [`flock`]
521+
/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`].
522522
///
523523
/// [`flock`]: crate::fs::flock
524+
/// [`fcntl_lock`]: crate::fs::fcntl_lock
524525
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
525526
#[repr(u32)]
526527
pub enum FlockOperation {

src/fs/fcntl.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
//! a type-safe API, rustix makes them all separate functions so that they
44
//! can have dedicated static type signatures.
55
6+
#[cfg(not(any(
7+
target_os = "emscripten",
8+
target_os = "fuchsia",
9+
target_os = "redox",
10+
target_os = "wasi"
11+
)))]
12+
use crate::fs::FlockOperation;
613
use crate::{backend, io};
714
use backend::fd::AsFd;
815
use backend::fs::types::OFlags;
@@ -85,3 +92,32 @@ pub use backend::fs::types::SealFlags;
8592
pub fn fcntl_add_seals<Fd: AsFd>(fd: Fd, seals: SealFlags) -> io::Result<()> {
8693
backend::fs::syscalls::fcntl_add_seals(fd.as_fd(), seals)
8794
}
95+
96+
/// `fcntl(fd, F_SETLK)`—Acquire or release an `fcntl`-style lock.
97+
///
98+
/// This function doesn't currently have an offset or len; it currently always
99+
/// sets the `l_len` field to 0, which is a special case that means the entire
100+
/// file should be locked.
101+
///
102+
/// Unlike `flock`-style locks, `fcntl`-style locks are process-associated,
103+
/// meaning that they don't guard against being acquired by two threads in
104+
/// the same process.
105+
///
106+
/// # References
107+
/// - [POSIX]
108+
/// - [Linux]
109+
///
110+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html
111+
/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
112+
#[cfg(not(any(
113+
target_os = "emscripten",
114+
target_os = "fuchsia",
115+
target_os = "redox",
116+
target_os = "wasi"
117+
)))]
118+
#[inline]
119+
#[doc(alias = "F_SETLK")]
120+
#[doc(alias = "F_SETLKW")]
121+
pub fn fcntl_lock<Fd: AsFd>(fd: Fd, operation: FlockOperation) -> io::Result<()> {
122+
backend::fs::syscalls::fcntl_lock(fd.as_fd(), operation)
123+
}
File renamed without changes.

src/fs/fd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::process::{Gid, Uid};
88
use crate::{backend, io};
99
use backend::fd::{AsFd, BorrowedFd};
1010

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

1414
#[cfg(not(any(

src/fs/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod dir;
2121
mod fadvise;
2222
pub(crate) mod fcntl;
2323
#[cfg(apple)]
24-
mod fcntl_darwin;
24+
mod fcntl_apple;
2525
#[cfg(apple)]
2626
mod fcopyfile;
2727
pub(crate) mod fd;
@@ -66,7 +66,7 @@ pub use dir::{Dir, DirEntry};
6666
pub use fadvise::{fadvise, Advice};
6767
pub use fcntl::*;
6868
#[cfg(apple)]
69-
pub use fcntl_darwin::{fcntl_fullfsync, fcntl_rdadvise};
69+
pub use fcntl_apple::{fcntl_fullfsync, fcntl_rdadvise};
7070
#[cfg(apple)]
7171
pub use fcopyfile::*;
7272
pub use fd::*;

tests/fs/fcntl_lock.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#[test]
2+
fn test_fcntl_lock() {
3+
use rustix::fs::{fcntl_lock, FlockOperation};
4+
5+
let f = tempfile::tempfile().unwrap();
6+
fcntl_lock(&f, FlockOperation::LockExclusive).unwrap();
7+
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
8+
let g = tempfile::tempfile().unwrap();
9+
fcntl_lock(&g, FlockOperation::LockExclusive).unwrap();
10+
fcntl_lock(&g, FlockOperation::Unlock).unwrap();
11+
drop(f);
12+
drop(g);
13+
14+
let f = tempfile::tempfile().unwrap();
15+
fcntl_lock(&f, FlockOperation::LockShared).unwrap();
16+
let g = tempfile::tempfile().unwrap();
17+
fcntl_lock(&g, FlockOperation::LockShared).unwrap();
18+
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
19+
fcntl_lock(&g, FlockOperation::Unlock).unwrap();
20+
drop(f);
21+
drop(g);
22+
23+
let f = tempfile::tempfile().unwrap();
24+
fcntl_lock(&f, FlockOperation::LockShared).unwrap();
25+
fcntl_lock(&f, FlockOperation::LockExclusive).unwrap();
26+
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
27+
let g = tempfile::tempfile().unwrap();
28+
fcntl_lock(&g, FlockOperation::LockShared).unwrap();
29+
fcntl_lock(&g, FlockOperation::LockExclusive).unwrap();
30+
fcntl_lock(&g, FlockOperation::Unlock).unwrap();
31+
drop(f);
32+
drop(g);
33+
}

tests/fs/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
mod cwd;
1010
mod dir;
1111
mod fcntl;
12+
#[cfg(not(any(
13+
target_os = "emscripten",
14+
target_os = "fuchsia",
15+
target_os = "redox",
16+
target_os = "wasi"
17+
)))]
18+
mod fcntl_lock;
1219
mod file;
1320
#[cfg(not(target_os = "wasi"))]
1421
mod flock;

0 commit comments

Comments
 (0)