Repository: bytecodealliance/rustix (language: rust)

Issue:
Feature request: Add `fcntl_getlk` support
Hi, may I require supporting `fcntl_getlk`?


Discussion / hints:

Fix patch:
diff --git a/src/backend/libc/process/syscalls.rs b/src/backend/libc/process/syscalls.rs
index 1faaabf56..06bfb08c9 100644
--- a/src/backend/libc/process/syscalls.rs
+++ b/src/backend/libc/process/syscalls.rs
@@ -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"))]
@@ -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) }))
+    }
+}
diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs
index 8f7b98658..fee712e05 100644
--- a/src/backend/linux_raw/c.rs
+++ b/src/backend/linux_raw/c.rs
@@ -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;
 
diff --git a/src/backend/linux_raw/process/syscalls.rs b/src/backend/linux_raw/process/syscalls.rs
index eeb463b58..5ab648cc8 100644
--- a/src/backend/linux_raw/process/syscalls.rs
+++ b/src/backend/linux_raw/process/syscalls.rs
@@ -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;
@@ -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) }))
+    }
+}
diff --git a/src/process/fcntl_getlk.rs b/src/process/fcntl_getlk.rs
new file mode 100644
index 000000000..d91f5a056
--- /dev/null
+++ b/src/process/fcntl_getlk.rs
@@ -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)
+}
diff --git a/src/process/mod.rs b/src/process/mod.rs
index e04bafee9..d5a7d01d4 100644
--- a/src/process/mod.rs
+++ b/src/process/mod.rs
@@ -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")))]
@@ -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")))]
@@ -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")))]
@@ -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")))]
diff --git a/src/process/types.rs b/src/process/types.rs
new file mode 100644
index 000000000..eb89a5ae8
--- /dev/null
+++ b/src/process/types.rs
@@ -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 _,
+}


Test patch:
diff --git a/tests/process/fcntl_getlk.rs b/tests/process/fcntl_getlk.rs
new file mode 100644
index 000000000..cbc1c54e5
--- /dev/null
+++ b/tests/process/fcntl_getlk.rs
@@ -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());
+    }
+}
diff --git a/tests/process/main.rs b/tests/process/main.rs
index 61b1eae3b..9a932cb56 100644
--- a/tests/process/main.rs
+++ b/tests/process/main.rs
@@ -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")]