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 { 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> { + 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 { )) } } + +#[inline] +pub(crate) fn fcntl_getlk(fd: BorrowedFd<'_>, lock: &Flock) -> io::Result> { + 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: Fd, lock: &Flock) -> io::Result> { + 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, + /// 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 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 _, +} 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(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")]