Skip to content

Commit a876d83

Browse files
authored
Add fcntl_getlk for fetching information of a lock held by another process (#1310)
* Add `fcntl_getlk` function * Disable fcntl_getlk on more platforms * Fill remaining space of `c::flock` with zeroes * Use correct enum member name for representing `SEEK_SET` * Add tests for `fcntl_getlk` * Ensure consistent behavior of `fcntl_getlk` on macOS * Return EINVAL in `fcntl_getlk` for every target to ensure consistency * Documentation and code improvements for `fcntl_getlk` * Document that `Err(io::Errno::INVAL)` is returned when `FlockType` is unlocked in `fcntl_getlk` * Use `syscall!` instead of `syscall_readonly!` in `fcntl_getlk` linux syscall * Do not return EINVAL by ourselves if `FlockType` of `fcntl_getlk` argument is `Unlocked` * Remove unused import
1 parent 4dc546f commit a876d83

8 files changed

Lines changed: 285 additions & 4 deletions

File tree

src/backend/libc/process/syscalls.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ use crate::ffi::CStr;
2929
#[cfg(feature = "fs")]
3030
use crate::fs::Mode;
3131
use crate::io;
32+
#[cfg(not(any(
33+
target_os = "emscripten",
34+
target_os = "espidf",
35+
target_os = "fuchsia",
36+
target_os = "redox",
37+
target_os = "vita",
38+
target_os = "wasi"
39+
)))]
40+
use crate::process::Flock;
3241
#[cfg(all(feature = "alloc", not(target_os = "wasi")))]
3342
use crate::process::Gid;
3443
#[cfg(not(target_os = "wasi"))]
@@ -646,3 +655,24 @@ pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result<usize> {
646655

647656
unsafe { ret_usize(c::getgroups(len, buf.as_mut_ptr().cast()) as isize) }
648657
}
658+
659+
#[cfg(not(any(
660+
target_os = "emscripten",
661+
target_os = "espidf",
662+
target_os = "fuchsia",
663+
target_os = "redox",
664+
target_os = "vita",
665+
target_os = "wasi"
666+
)))]
667+
#[inline]
668+
pub(crate) fn fcntl_getlk(fd: BorrowedFd<'_>, lock: &Flock) -> io::Result<Option<Flock>> {
669+
let mut curr_lock: c::flock = lock.as_raw();
670+
unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_GETLK, &mut curr_lock))? };
671+
672+
// If no blocking lock is found, `fcntl(GETLK, ..)` sets `l_type` to `F_UNLCK`
673+
if curr_lock.l_type == c::F_UNLCK as _ {
674+
Ok(None)
675+
} else {
676+
Ok(Some(unsafe { Flock::from_raw_unchecked(curr_lock) }))
677+
}
678+
}

src/backend/linux_raw/c.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,21 @@ pub(crate) const CLONE_CHILD_SETTID: c_int = linux_raw_sys::general::CLONE_CHILD
184184
#[cfg(feature = "process")]
185185
pub(crate) use linux_raw_sys::{
186186
general::{
187-
CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED,
188-
O_NONBLOCK as PIDFD_NONBLOCK, P_ALL, P_PGID, P_PID, P_PIDFD,
187+
CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED, F_RDLCK,
188+
F_UNLCK, F_WRLCK, O_NONBLOCK as PIDFD_NONBLOCK, P_ALL, P_PGID, P_PID, P_PIDFD, SEEK_CUR,
189+
SEEK_END, SEEK_SET,
189190
},
190191
ioctl::TIOCSCTTY,
191192
};
192193

194+
#[cfg(feature = "process")]
195+
#[cfg(target_pointer_width = "32")]
196+
pub(crate) use linux_raw_sys::general::{flock64 as flock, F_GETLK64};
197+
198+
#[cfg(feature = "process")]
199+
#[cfg(target_pointer_width = "64")]
200+
pub(crate) use linux_raw_sys::general::{flock, F_GETLK};
201+
193202
#[cfg(feature = "pty")]
194203
pub(crate) use linux_raw_sys::ioctl::TIOCGPTPEER;
195204

src/backend/linux_raw/process/syscalls.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use crate::ffi::CStr;
1818
use crate::io;
1919
use crate::pid::RawPid;
2020
use crate::process::{
21-
Pid, PidfdFlags, PidfdGetfdFlags, Resource, Rlimit, Uid, WaitId, WaitIdOptions, WaitIdStatus,
22-
WaitOptions, WaitStatus,
21+
Flock, Pid, PidfdFlags, PidfdGetfdFlags, Resource, Rlimit, Uid, WaitId, WaitIdOptions,
22+
WaitIdStatus, WaitOptions, WaitStatus,
2323
};
2424
use crate::signal::Signal;
2525
use core::mem::MaybeUninit;
@@ -519,3 +519,33 @@ pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result<usize> {
519519
))
520520
}
521521
}
522+
523+
#[inline]
524+
pub(crate) fn fcntl_getlk(fd: BorrowedFd<'_>, lock: &Flock) -> io::Result<Option<Flock>> {
525+
let mut curr_lock: c::flock = lock.as_raw();
526+
#[cfg(target_pointer_width = "32")]
527+
unsafe {
528+
ret(syscall!(
529+
__NR_fcntl64,
530+
fd,
531+
c_uint(c::F_GETLK64),
532+
by_ref(&mut curr_lock)
533+
))?
534+
}
535+
#[cfg(target_pointer_width = "64")]
536+
unsafe {
537+
ret(syscall!(
538+
__NR_fcntl,
539+
fd,
540+
c_uint(c::F_GETLK),
541+
by_ref(&mut curr_lock)
542+
))?
543+
}
544+
545+
// If no blocking lock is found, `fcntl(GETLK, ..)` sets `l_type` to `F_UNLCK`
546+
if curr_lock.l_type == c::F_UNLCK as _ {
547+
Ok(None)
548+
} else {
549+
Ok(Some(unsafe { Flock::from_raw_unchecked(curr_lock) }))
550+
}
551+
}

src/process/fcntl_getlk.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::Flock;
2+
use crate::fd::AsFd;
3+
use crate::{backend, io};
4+
5+
/// `fcntl(fd, F_GETLK)`—Get the first lock that blocks the lock description pointed to by the
6+
/// argument `lock`. If no such lock is found, then `None` is returned.
7+
///
8+
/// If `lock.typ` is set to `FlockType::Unlocked`, the returned value/error is not explicitly
9+
/// defined, as per POSIX, and will depend on the underlying platform implementation.
10+
///
11+
/// # References
12+
/// - [POSIX]
13+
/// - [Linux]
14+
///
15+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fcntl.html
16+
/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
17+
#[inline]
18+
#[doc(alias = "F_GETLK")]
19+
pub fn fcntl_getlk<Fd: AsFd>(fd: Fd, lock: &Flock) -> io::Result<Option<Flock>> {
20+
backend::process::syscalls::fcntl_getlk(fd.as_fd(), lock)
21+
}

src/process/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ mod chdir;
55
#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
66
mod chroot;
77
mod exit;
8+
#[cfg(not(any(
9+
target_os = "emscripten",
10+
target_os = "espidf",
11+
target_os = "fuchsia",
12+
target_os = "redox",
13+
target_os = "vita",
14+
target_os = "wasi"
15+
)))]
16+
mod fcntl_getlk;
817
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
918
mod id;
1019
#[cfg(not(any(target_os = "aix", target_os = "espidf", target_os = "vita")))]
@@ -32,6 +41,15 @@ mod procctl;
3241
target_os = "wasi"
3342
)))]
3443
mod rlimit;
44+
#[cfg(not(any(
45+
target_os = "emscripten",
46+
target_os = "espidf",
47+
target_os = "fuchsia",
48+
target_os = "redox",
49+
target_os = "vita",
50+
target_os = "wasi"
51+
)))]
52+
mod types;
3553
#[cfg(not(target_os = "wasi"))] // WASI doesn't have umask.
3654
mod umask;
3755
#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
@@ -42,6 +60,15 @@ pub use chdir::*;
4260
#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
4361
pub use chroot::*;
4462
pub use exit::*;
63+
#[cfg(not(any(
64+
target_os = "emscripten",
65+
target_os = "espidf",
66+
target_os = "fuchsia",
67+
target_os = "redox",
68+
target_os = "vita",
69+
target_os = "wasi"
70+
)))]
71+
pub use fcntl_getlk::*;
4572
#[cfg(not(target_os = "wasi"))]
4673
pub use id::*;
4774
#[cfg(not(any(target_os = "aix", target_os = "espidf", target_os = "vita")))]
@@ -68,6 +95,15 @@ pub use procctl::*;
6895
target_os = "wasi"
6996
)))]
7097
pub use rlimit::*;
98+
#[cfg(not(any(
99+
target_os = "emscripten",
100+
target_os = "espidf",
101+
target_os = "fuchsia",
102+
target_os = "redox",
103+
target_os = "vita",
104+
target_os = "wasi"
105+
)))]
106+
pub use types::*;
71107
#[cfg(not(target_os = "wasi"))]
72108
pub use umask::*;
73109
#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]

src/process/types.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#![allow(unsafe_code)]
2+
3+
use crate::backend::c;
4+
use crate::pid::Pid;
5+
use core::mem::transmute;
6+
7+
/// File lock data structure used in [`fcntl_getlk`].
8+
///
9+
/// [`fcntl_getlk`]: crate::fs::fcntl_getlk
10+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11+
pub struct Flock {
12+
/// Starting offset for lock
13+
pub start: u64,
14+
/// Number of bytes to lock
15+
pub length: u64,
16+
/// PID of process blocking our lock. If set to `None`, it refers to the current process
17+
pub pid: Option<Pid>,
18+
/// Type of lock
19+
pub typ: FlockType,
20+
/// Offset type of lock
21+
pub offset_type: FlockOffsetType,
22+
}
23+
24+
impl Flock {
25+
pub(crate) const unsafe fn from_raw_unchecked(raw_fl: c::flock) -> Flock {
26+
Flock {
27+
start: raw_fl.l_start as _,
28+
length: raw_fl.l_len as _,
29+
pid: transmute(raw_fl.l_pid),
30+
typ: transmute(raw_fl.l_type),
31+
offset_type: transmute(raw_fl.l_whence),
32+
}
33+
}
34+
35+
pub(crate) fn as_raw(&self) -> c::flock {
36+
let mut f: c::flock = unsafe { core::mem::zeroed() };
37+
f.l_start = self.start as _;
38+
f.l_len = self.length as _;
39+
f.l_pid = unsafe { transmute(self.pid) };
40+
f.l_type = self.typ as _;
41+
f.l_whence = self.offset_type as _;
42+
f
43+
}
44+
}
45+
46+
impl From<FlockType> for Flock {
47+
fn from(value: FlockType) -> Self {
48+
Flock {
49+
start: 0,
50+
length: 0,
51+
pid: None,
52+
typ: value,
53+
offset_type: FlockOffsetType::Set,
54+
}
55+
}
56+
}
57+
58+
/// `F_*LCK` constants for use with [`fcntl_getlk`].
59+
///
60+
/// [`fcntl_getlk`]: crate::fs::fcntl_getlk
61+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62+
#[repr(i16)]
63+
pub enum FlockType {
64+
/// `F_RDLCK`
65+
ReadLock = c::F_RDLCK as _,
66+
/// `F_WRLCK`
67+
WriteLock = c::F_WRLCK as _,
68+
/// `F_UNLCK`
69+
Unlocked = c::F_UNLCK as _,
70+
}
71+
72+
/// `F_SEEK*` constants for use with [`fcntl_getlk`].
73+
///
74+
/// [`fcntl_getlk`]: crate::fs::fcntl_getlk
75+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76+
#[repr(i16)]
77+
pub enum FlockOffsetType {
78+
/// `F_SEEK_SET`
79+
Set = c::SEEK_SET as _,
80+
/// `F_SEEK_CUR`
81+
Current = c::SEEK_CUR as _,
82+
/// `F_SEEK_END`
83+
End = c::SEEK_END as _,
84+
}

tests/process/fcntl_getlk.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use rustix::fd::{AsRawFd, BorrowedFd};
2+
use rustix::fs::{fcntl_lock, FlockOperation};
3+
use rustix::process::{fcntl_getlk, getppid, Flock, FlockType};
4+
use std::fs::File;
5+
use std::os::unix::process::CommandExt;
6+
use std::process::Command;
7+
8+
#[cfg(feature = "fs")]
9+
#[test]
10+
fn test_fcntl_getlk() {
11+
let f = tempfile::tempfile().unwrap();
12+
13+
fcntl_lock(&f, FlockOperation::Unlock).unwrap();
14+
unsafe {
15+
child_process(&f, |fd| {
16+
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::ReadLock)).unwrap();
17+
assert_eq!(lock, None);
18+
19+
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::WriteLock)).unwrap();
20+
assert_eq!(lock, None);
21+
})
22+
};
23+
24+
fcntl_lock(&f, FlockOperation::LockShared).unwrap();
25+
unsafe {
26+
child_process(&f, |fd| {
27+
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::ReadLock)).unwrap();
28+
assert_eq!(lock, None);
29+
30+
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::WriteLock)).unwrap();
31+
assert_eq!(lock.and_then(|l| l.pid), getppid());
32+
})
33+
};
34+
35+
fcntl_lock(&f, FlockOperation::LockExclusive).unwrap();
36+
unsafe {
37+
child_process(&f, |fd| {
38+
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::ReadLock)).unwrap();
39+
assert_eq!(lock.and_then(|l| l.pid), getppid());
40+
41+
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::WriteLock)).unwrap();
42+
assert_eq!(lock.and_then(|l| l.pid), getppid());
43+
})
44+
};
45+
}
46+
47+
unsafe fn child_process<F>(file: &File, f: F)
48+
where
49+
F: Fn(BorrowedFd<'static>) -> () + Send + Sync + 'static,
50+
{
51+
let fd = BorrowedFd::borrow_raw(file.as_raw_fd());
52+
let output = Command::new("true")
53+
.pre_exec(move || {
54+
f(fd);
55+
Ok(())
56+
})
57+
.output()
58+
.unwrap();
59+
if !output.status.success() {
60+
panic!("{}", std::str::from_utf8(&output.stderr).unwrap());
61+
}
62+
}

tests/process/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
#![cfg(not(windows))]
55
#![cfg_attr(core_c_str, feature(core_c_str))]
66

7+
#[cfg(not(any(
8+
target_os = "emscripten",
9+
target_os = "espidf",
10+
target_os = "fuchsia",
11+
target_os = "redox",
12+
target_os = "vita",
13+
target_os = "wasi"
14+
)))]
15+
mod fcntl_getlk;
716
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
817
mod id;
918
#[cfg(target_os = "linux")]

0 commit comments

Comments
 (0)