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

Add fcntl_getlk for fetching information of a lock held by another process #1310

Merged
merged 12 commits into from
Feb 11, 2025
Merged
30 changes: 30 additions & 0 deletions src/backend/libc/process/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ use crate::ffi::CStr;
#[cfg(feature = "fs")]
use crate::fs::Mode;
use crate::io;
#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
use crate::process::Flock;
#[cfg(all(feature = "alloc", not(target_os = "wasi")))]
use crate::process::Gid;
#[cfg(not(target_os = "wasi"))]
Expand Down Expand Up @@ -646,3 +655,24 @@ pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result<usize> {

unsafe { ret_usize(c::getgroups(len, buf.as_mut_ptr().cast()) as isize) }
}

#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
#[inline]
pub(crate) fn fcntl_getlk(fd: BorrowedFd<'_>, lock: &Flock) -> io::Result<Option<Flock>> {
let mut curr_lock: c::flock = lock.as_raw();
unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_GETLK, &mut curr_lock))? };

// If no blocking lock is found, `fcntl(GETLK, ..)` sets `l_type` to `F_UNLCK`
if curr_lock.l_type == c::F_UNLCK as _ {
Ok(None)
} else {
Ok(Some(unsafe { Flock::from_raw_unchecked(curr_lock) }))
}
}
13 changes: 11 additions & 2 deletions src/backend/linux_raw/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,21 @@ pub(crate) const CLONE_CHILD_SETTID: c_int = linux_raw_sys::general::CLONE_CHILD
#[cfg(feature = "process")]
pub(crate) use linux_raw_sys::{
general::{
CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED,
O_NONBLOCK as PIDFD_NONBLOCK, P_ALL, P_PGID, P_PID, P_PIDFD,
CLD_CONTINUED, CLD_DUMPED, CLD_EXITED, CLD_KILLED, CLD_STOPPED, CLD_TRAPPED, F_RDLCK,
F_UNLCK, F_WRLCK, O_NONBLOCK as PIDFD_NONBLOCK, P_ALL, P_PGID, P_PID, P_PIDFD, SEEK_CUR,
SEEK_END, SEEK_SET,
},
ioctl::TIOCSCTTY,
};

#[cfg(feature = "process")]
#[cfg(target_pointer_width = "32")]
pub(crate) use linux_raw_sys::general::{flock64 as flock, F_GETLK64};

#[cfg(feature = "process")]
#[cfg(target_pointer_width = "64")]
pub(crate) use linux_raw_sys::general::{flock, F_GETLK};

#[cfg(feature = "pty")]
pub(crate) use linux_raw_sys::ioctl::TIOCGPTPEER;

Expand Down
34 changes: 32 additions & 2 deletions src/backend/linux_raw/process/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::ffi::CStr;
use crate::io;
use crate::pid::RawPid;
use crate::process::{
Pid, PidfdFlags, PidfdGetfdFlags, Resource, Rlimit, Uid, WaitId, WaitIdOptions, WaitIdStatus,
WaitOptions, WaitStatus,
Flock, Pid, PidfdFlags, PidfdGetfdFlags, Resource, Rlimit, Uid, WaitId, WaitIdOptions,
WaitIdStatus, WaitOptions, WaitStatus,
};
use crate::signal::Signal;
use core::mem::MaybeUninit;
Expand Down Expand Up @@ -519,3 +519,33 @@ pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result<usize> {
))
}
}

#[inline]
pub(crate) fn fcntl_getlk(fd: BorrowedFd<'_>, lock: &Flock) -> io::Result<Option<Flock>> {
let mut curr_lock: c::flock = lock.as_raw();
#[cfg(target_pointer_width = "32")]
unsafe {
ret(syscall!(
__NR_fcntl64,
fd,
c_uint(c::F_GETLK64),
by_ref(&mut curr_lock)
))?
}
#[cfg(target_pointer_width = "64")]
unsafe {
ret(syscall!(
__NR_fcntl,
fd,
c_uint(c::F_GETLK),
by_ref(&mut curr_lock)
))?
}

// If no blocking lock is found, `fcntl(GETLK, ..)` sets `l_type` to `F_UNLCK`
if curr_lock.l_type == c::F_UNLCK as _ {
Ok(None)
} else {
Ok(Some(unsafe { Flock::from_raw_unchecked(curr_lock) }))
}
}
21 changes: 21 additions & 0 deletions src/process/fcntl_getlk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::Flock;
use crate::fd::AsFd;
use crate::{backend, io};

/// `fcntl(fd, F_GETLK)`—Get the first lock that blocks the lock description pointed to by the
/// argument `lock`. If no such lock is found, then `None` is returned.
///
/// If `lock.typ` is set to `FlockType::Unlocked`, the returned value/error is not explicitly
/// defined, as per POSIX, and will depend on the underlying platform implementation.
///
/// # References
/// - [POSIX]
/// - [Linux]
///
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fcntl.html
/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html
#[inline]
#[doc(alias = "F_GETLK")]
pub fn fcntl_getlk<Fd: AsFd>(fd: Fd, lock: &Flock) -> io::Result<Option<Flock>> {
backend::process::syscalls::fcntl_getlk(fd.as_fd(), lock)
}
36 changes: 36 additions & 0 deletions src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ mod chdir;
#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
mod chroot;
mod exit;
#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
mod fcntl_getlk;
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
mod id;
#[cfg(not(any(target_os = "aix", target_os = "espidf", target_os = "vita")))]
Expand Down Expand Up @@ -32,6 +41,15 @@ mod procctl;
target_os = "wasi"
)))]
mod rlimit;
#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
mod types;
#[cfg(not(target_os = "wasi"))] // WASI doesn't have umask.
mod umask;
#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
Expand All @@ -42,6 +60,15 @@ pub use chdir::*;
#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
pub use chroot::*;
pub use exit::*;
#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
pub use fcntl_getlk::*;
#[cfg(not(target_os = "wasi"))]
pub use id::*;
#[cfg(not(any(target_os = "aix", target_os = "espidf", target_os = "vita")))]
Expand All @@ -68,6 +95,15 @@ pub use procctl::*;
target_os = "wasi"
)))]
pub use rlimit::*;
#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
pub use types::*;
#[cfg(not(target_os = "wasi"))]
pub use umask::*;
#[cfg(not(any(target_os = "espidf", target_os = "vita", target_os = "wasi")))]
Expand Down
84 changes: 84 additions & 0 deletions src/process/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#![allow(unsafe_code)]

use crate::backend::c;
use crate::pid::Pid;
use core::mem::transmute;

/// File lock data structure used in [`fcntl_getlk`].
///
/// [`fcntl_getlk`]: crate::fs::fcntl_getlk
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Flock {
/// Starting offset for lock
pub start: u64,
/// Number of bytes to lock
pub length: u64,
/// PID of process blocking our lock. If set to `None`, it refers to the current process
pub pid: Option<Pid>,
/// Type of lock
pub typ: FlockType,
/// Offset type of lock
pub offset_type: FlockOffsetType,
}

impl Flock {
pub(crate) const unsafe fn from_raw_unchecked(raw_fl: c::flock) -> Flock {
Flock {
start: raw_fl.l_start as _,
length: raw_fl.l_len as _,
pid: transmute(raw_fl.l_pid),
typ: transmute(raw_fl.l_type),
offset_type: transmute(raw_fl.l_whence),
}
}

pub(crate) fn as_raw(&self) -> c::flock {
let mut f: c::flock = unsafe { core::mem::zeroed() };
f.l_start = self.start as _;
f.l_len = self.length as _;
f.l_pid = unsafe { transmute(self.pid) };
f.l_type = self.typ as _;
f.l_whence = self.offset_type as _;
f
}
}

impl From<FlockType> for Flock {
fn from(value: FlockType) -> Self {
Flock {
start: 0,
length: 0,
pid: None,
typ: value,
offset_type: FlockOffsetType::Set,
}
}
}

/// `F_*LCK` constants for use with [`fcntl_getlk`].
///
/// [`fcntl_getlk`]: crate::fs::fcntl_getlk
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum FlockType {
/// `F_RDLCK`
ReadLock = c::F_RDLCK as _,
/// `F_WRLCK`
WriteLock = c::F_WRLCK as _,
/// `F_UNLCK`
Unlocked = c::F_UNLCK as _,
}

/// `F_SEEK*` constants for use with [`fcntl_getlk`].
///
/// [`fcntl_getlk`]: crate::fs::fcntl_getlk
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum FlockOffsetType {
/// `F_SEEK_SET`
Set = c::SEEK_SET as _,
/// `F_SEEK_CUR`
Current = c::SEEK_CUR as _,
/// `F_SEEK_END`
End = c::SEEK_END as _,
}
62 changes: 62 additions & 0 deletions tests/process/fcntl_getlk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use rustix::fd::{AsRawFd, BorrowedFd};
use rustix::fs::{fcntl_lock, FlockOperation};
use rustix::process::{fcntl_getlk, getppid, Flock, FlockType};
use std::fs::File;
use std::os::unix::process::CommandExt;
use std::process::Command;

#[cfg(feature = "fs")]
#[test]
fn test_fcntl_getlk() {
let f = tempfile::tempfile().unwrap();

fcntl_lock(&f, FlockOperation::Unlock).unwrap();
unsafe {
child_process(&f, |fd| {
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::ReadLock)).unwrap();
assert_eq!(lock, None);

let lock = fcntl_getlk(&fd, &Flock::from(FlockType::WriteLock)).unwrap();
assert_eq!(lock, None);
})
};

fcntl_lock(&f, FlockOperation::LockShared).unwrap();
unsafe {
child_process(&f, |fd| {
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::ReadLock)).unwrap();
assert_eq!(lock, None);

let lock = fcntl_getlk(&fd, &Flock::from(FlockType::WriteLock)).unwrap();
assert_eq!(lock.and_then(|l| l.pid), getppid());
})
};

fcntl_lock(&f, FlockOperation::LockExclusive).unwrap();
unsafe {
child_process(&f, |fd| {
let lock = fcntl_getlk(&fd, &Flock::from(FlockType::ReadLock)).unwrap();
assert_eq!(lock.and_then(|l| l.pid), getppid());

let lock = fcntl_getlk(&fd, &Flock::from(FlockType::WriteLock)).unwrap();
assert_eq!(lock.and_then(|l| l.pid), getppid());
})
};
}

unsafe fn child_process<F>(file: &File, f: F)
where
F: Fn(BorrowedFd<'static>) -> () + Send + Sync + 'static,
{
let fd = BorrowedFd::borrow_raw(file.as_raw_fd());
let output = Command::new("true")
.pre_exec(move || {
f(fd);
Ok(())
})
.output()
.unwrap();
if !output.status.success() {
panic!("{}", std::str::from_utf8(&output.stderr).unwrap());
}
}
9 changes: 9 additions & 0 deletions tests/process/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
#![cfg(not(windows))]
#![cfg_attr(core_c_str, feature(core_c_str))]

#[cfg(not(any(
target_os = "emscripten",
target_os = "espidf",
target_os = "fuchsia",
target_os = "redox",
target_os = "vita",
target_os = "wasi"
)))]
mod fcntl_getlk;
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
mod id;
#[cfg(target_os = "linux")]
Expand Down