diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index 12d458995..7c1176b03 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -68,7 +68,7 @@ use crate::fs::Advice; target_os = "redox", )))] use crate::fs::FallocateFlags; -#[cfg(not(any(target_os = "solaris", target_os = "wasi")))] +#[cfg(not(target_os = "wasi"))] use crate::fs::FlockOperation; #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))] use crate::fs::MemfdFlags; @@ -829,6 +829,40 @@ pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Resul unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) } } +#[cfg(not(any( + target_os = "emscripten", + target_os = "fuchsia", + target_os = "redox", + target_os = "wasi" +)))] +#[inline] +pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> { + use c::{flock, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, SEEK_SET}; + + let (cmd, l_type) = match operation { + FlockOperation::LockShared => (F_SETLKW, F_RDLCK), + FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK), + FlockOperation::Unlock => (F_SETLKW, F_UNLCK), + FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK), + FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK), + FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK), + }; + + unsafe { + let mut lock: flock = core::mem::zeroed(); + lock.l_type = l_type as _; + + // When `l_len` is zero, this locks all the bytes from + // `l_whence`/`l_start` to the end of the file, even as the + // file grows dynamically. + lock.l_whence = SEEK_SET as _; + lock.l_start = 0; + lock.l_len = 0; + + ret(c::fcntl(borrowed_fd(fd), cmd, &lock)) + } +} + pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result { let (whence, offset): (c::c_int, libc_off_t) = match pos { SeekFrom::Start(pos) => { diff --git a/src/backend/libc/fs/types.rs b/src/backend/libc/fs/types.rs index 42fc49a6b..0539713ed 100644 --- a/src/backend/libc/fs/types.rs +++ b/src/backend/libc/fs/types.rs @@ -785,10 +785,11 @@ bitflags! { } } -/// `LOCK_*` constants for use with [`flock`] +/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`]. /// /// [`flock`]: crate::fs::flock -#[cfg(not(any(target_os = "solaris", target_os = "wasi")))] +/// [`fcntl_lock`]: crate::fs::fcntl_lock +#[cfg(not(target_os = "wasi"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(i32)] pub enum FlockOperation { diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs index ab64a8a48..eba0268e0 100644 --- a/src/backend/linux_raw/fs/syscalls.rs +++ b/src/backend/linux_raw/fs/syscalls.rs @@ -1022,6 +1022,58 @@ pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Resul } } +#[inline] +pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> { + #[cfg(target_pointer_width = "64")] + use linux_raw_sys::general::{flock, F_SETLK, F_SETLKW}; + #[cfg(target_pointer_width = "32")] + use linux_raw_sys::general::{flock64 as flock, F_SETLK64 as F_SETLK, F_SETLKW64 as F_SETLKW}; + use linux_raw_sys::general::{F_RDLCK, F_UNLCK, F_WRLCK}; + + let (cmd, l_type) = match operation { + FlockOperation::LockShared => (F_SETLKW, F_RDLCK), + FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK), + FlockOperation::Unlock => (F_SETLKW, F_UNLCK), + FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK), + FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK), + FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK), + }; + + unsafe { + let lock = flock { + l_type: l_type as _, + + // When `l_len` is zero, this locks all the bytes from + // `l_whence`/`l_start` to the end of the file, even as the + // file grows dynamically. + l_whence: SEEK_SET as _, + l_start: 0, + l_len: 0, + + ..core::mem::zeroed() + }; + + #[cfg(target_pointer_width = "32")] + { + ret(syscall_readonly!( + __NR_fcntl64, + fd, + c_uint(cmd), + by_ref(&lock) + )) + } + #[cfg(target_pointer_width = "64")] + { + ret(syscall_readonly!( + __NR_fcntl, + fd, + c_uint(cmd), + by_ref(&lock) + )) + } + } +} + #[inline] pub(crate) fn rename(oldname: &CStr, newname: &CStr) -> io::Result<()> { #[cfg(target_arch = "riscv64")] diff --git a/src/backend/linux_raw/fs/types.rs b/src/backend/linux_raw/fs/types.rs index a3a0104d6..4a221f674 100644 --- a/src/backend/linux_raw/fs/types.rs +++ b/src/backend/linux_raw/fs/types.rs @@ -518,9 +518,10 @@ bitflags! { } } -/// `LOCK_*` constants for use with [`flock`] +/// `LOCK_*` constants for use with [`flock`] and [`fcntl_lock`]. /// /// [`flock`]: crate::fs::flock +/// [`fcntl_lock`]: crate::fs::fcntl_lock #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u32)] pub enum FlockOperation { diff --git a/src/fs/fcntl.rs b/src/fs/fcntl.rs index 80ac858c0..0f557ef7f 100644 --- a/src/fs/fcntl.rs +++ b/src/fs/fcntl.rs @@ -3,6 +3,13 @@ //! a type-safe API, rustix makes them all separate functions so that they //! can have dedicated static type signatures. +#[cfg(not(any( + target_os = "emscripten", + target_os = "fuchsia", + target_os = "redox", + target_os = "wasi" +)))] +use crate::fs::FlockOperation; use crate::{backend, io}; use backend::fd::AsFd; use backend::fs::types::OFlags; @@ -85,3 +92,32 @@ pub use backend::fs::types::SealFlags; pub fn fcntl_add_seals(fd: Fd, seals: SealFlags) -> io::Result<()> { backend::fs::syscalls::fcntl_add_seals(fd.as_fd(), seals) } + +/// `fcntl(fd, F_SETLK)`—Acquire or release an `fcntl`-style lock. +/// +/// This function doesn't currently have an offset or len; it currently always +/// sets the `l_len` field to 0, which is a special case that means the entire +/// file should be locked. +/// +/// Unlike `flock`-style locks, `fcntl`-style locks are process-associated, +/// meaning that they don't guard against being acquired by two threads in +/// the same process. +/// +/// # References +/// - [POSIX] +/// - [Linux] +/// +/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html +/// [Linux]: https://man7.org/linux/man-pages/man2/fcntl.2.html +#[cfg(not(any( + target_os = "emscripten", + target_os = "fuchsia", + target_os = "redox", + target_os = "wasi" +)))] +#[inline] +#[doc(alias = "F_SETLK")] +#[doc(alias = "F_SETLKW")] +pub fn fcntl_lock(fd: Fd, operation: FlockOperation) -> io::Result<()> { + backend::fs::syscalls::fcntl_lock(fd.as_fd(), operation) +} diff --git a/src/fs/fcntl_darwin.rs b/src/fs/fcntl_apple.rs similarity index 100% rename from src/fs/fcntl_darwin.rs rename to src/fs/fcntl_apple.rs diff --git a/src/fs/fd.rs b/src/fs/fd.rs index 2e302f833..303a684e4 100644 --- a/src/fs/fd.rs +++ b/src/fs/fd.rs @@ -8,7 +8,7 @@ use crate::process::{Gid, Uid}; use crate::{backend, io}; use backend::fd::{AsFd, BorrowedFd}; -#[cfg(not(any(target_os = "solaris", target_os = "wasi")))] +#[cfg(not(target_os = "wasi"))] pub use backend::fs::types::FlockOperation; #[cfg(not(any( diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 7654df303..39f3c5726 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -21,7 +21,7 @@ mod dir; mod fadvise; pub(crate) mod fcntl; #[cfg(apple)] -mod fcntl_darwin; +mod fcntl_apple; #[cfg(apple)] mod fcopyfile; pub(crate) mod fd; @@ -66,7 +66,7 @@ pub use dir::{Dir, DirEntry}; pub use fadvise::{fadvise, Advice}; pub use fcntl::*; #[cfg(apple)] -pub use fcntl_darwin::{fcntl_fullfsync, fcntl_rdadvise}; +pub use fcntl_apple::{fcntl_fullfsync, fcntl_rdadvise}; #[cfg(apple)] pub use fcopyfile::*; pub use fd::*; diff --git a/tests/fs/fcntl_lock.rs b/tests/fs/fcntl_lock.rs new file mode 100644 index 000000000..166275627 --- /dev/null +++ b/tests/fs/fcntl_lock.rs @@ -0,0 +1,33 @@ +#[test] +fn test_fcntl_lock() { + use rustix::fs::{fcntl_lock, FlockOperation}; + + let f = tempfile::tempfile().unwrap(); + fcntl_lock(&f, FlockOperation::LockExclusive).unwrap(); + fcntl_lock(&f, FlockOperation::Unlock).unwrap(); + let g = tempfile::tempfile().unwrap(); + fcntl_lock(&g, FlockOperation::LockExclusive).unwrap(); + fcntl_lock(&g, FlockOperation::Unlock).unwrap(); + drop(f); + drop(g); + + let f = tempfile::tempfile().unwrap(); + fcntl_lock(&f, FlockOperation::LockShared).unwrap(); + let g = tempfile::tempfile().unwrap(); + fcntl_lock(&g, FlockOperation::LockShared).unwrap(); + fcntl_lock(&f, FlockOperation::Unlock).unwrap(); + fcntl_lock(&g, FlockOperation::Unlock).unwrap(); + drop(f); + drop(g); + + let f = tempfile::tempfile().unwrap(); + fcntl_lock(&f, FlockOperation::LockShared).unwrap(); + fcntl_lock(&f, FlockOperation::LockExclusive).unwrap(); + fcntl_lock(&f, FlockOperation::Unlock).unwrap(); + let g = tempfile::tempfile().unwrap(); + fcntl_lock(&g, FlockOperation::LockShared).unwrap(); + fcntl_lock(&g, FlockOperation::LockExclusive).unwrap(); + fcntl_lock(&g, FlockOperation::Unlock).unwrap(); + drop(f); + drop(g); +} diff --git a/tests/fs/main.rs b/tests/fs/main.rs index c83c7914a..e7a78aeef 100644 --- a/tests/fs/main.rs +++ b/tests/fs/main.rs @@ -9,6 +9,13 @@ mod cwd; mod dir; mod fcntl; +#[cfg(not(any( + target_os = "emscripten", + target_os = "fuchsia", + target_os = "redox", + target_os = "wasi" +)))] +mod fcntl_lock; mod file; #[cfg(not(target_os = "wasi"))] mod flock;