possum/sys/flock/
ofd.rs

1use std::io::SeekFrom;
2
3use super::*;
4
5fn seek_from_offset(seek_from: SeekFrom) -> i64 {
6    use SeekFrom::*;
7    match seek_from {
8        Start(offset) => offset as i64,
9        End(offset) | Current(offset) => offset,
10    }
11}
12
13fn seek_from_whence(seek_from: SeekFrom) -> libc::c_short {
14    use libc::*;
15    use SeekFrom::*;
16    (match seek_from {
17        Start(_) => SEEK_SET,
18        Current(_) => SEEK_CUR,
19        End(_) => SEEK_END,
20    }) as c_short
21}
22
23cfg_if! {
24    if #[cfg(any(target_os = "macos", target_os = "freebsd"))] {
25        use libc::flock as flock_struct;
26    } else {
27        use libc::flock64 as flock_struct;
28    }
29}
30
31// #[instrument]
32pub(super) fn lock_file_segment(
33    file: &File,
34    arg: FlockArg,
35    len: Option<i64>,
36    whence: SeekFrom,
37) -> nix::Result<bool> {
38    debug!(?file, ?arg, ?len, ?whence, "locking file segment");
39    if let Some(len) = len {
40        // This has special meaning on macOS: To the end of the file. Use None instead.
41        if len == 0 {
42            return Ok(true);
43        }
44    }
45    #[allow(deprecated)]
46    let l_type = match arg {
47        LockShared | LockSharedNonblock => libc::F_RDLCK,
48        LockExclusive | LockExclusiveNonblock => libc::F_WRLCK,
49        Unlock | UnlockNonblock => libc::F_UNLCK,
50        // Silly non-exhaustive enum.
51        _ => unimplemented!(),
52    };
53    let mut flock_arg: flock_struct = unsafe { std::mem::zeroed() };
54    flock_arg.l_start = seek_from_offset(whence);
55    flock_arg.l_len = len.unwrap_or(0);
56    #[allow(clippy::useless_conversion)]
57    let l_type = l_type.try_into().unwrap();
58    flock_arg.l_type = l_type;
59    flock_arg.l_whence = seek_from_whence(whence);
60    use libc::{F_OFD_SETLK as SetLock, F_OFD_SETLKW as SetLockWait};
61    #[allow(deprecated)]
62    let arg = match arg {
63        LockShared | LockExclusive => SetLockWait,
64        LockSharedNonblock | LockExclusiveNonblock | Unlock | UnlockNonblock => SetLock,
65        _ => unimplemented!(),
66    };
67    // EWOULDBLOCK is an inherent impl const so can't be glob imported.
68    use nix::errno::Errno;
69    // nix doesn't handle 64bit fcntl on 32bit Linux yet, and I can't be bothered to send a PR since
70    // my last one got stalled. https://github.com/nix-rust/nix/pull/2032#issuecomment-1931419587.
71    // There doesn't seem to be a fcntl64 available for 32bit systems. Hopefully fcntl there still
72    // takes a fcntl64 arg.
73    match Errno::result(unsafe { libc::fcntl(file.as_raw_fd(), arg, &flock_arg) }) {
74        Ok(_) => Ok(true),
75        Err(errno) if errno == Errno::EWOULDBLOCK || errno == Errno::EAGAIN => Ok(false),
76        Err(err) => {
77            error!(?err, "fcntl");
78            Err(err)
79        }
80    }
81}
82
83impl FileLocking for File {
84    fn trim_exclusive_lock_left(&self, old_left: u64, new_left: u64) -> io::Result<bool> {
85        if flocking() {
86            return Ok(true);
87        }
88        // #[allow(deprecated)]
89        self.lock_segment(UnlockNonblock, Some(new_left - old_left), old_left)
90    }
91
92    fn lock_segment(&self, arg: FlockArg, len: Option<u64>, offset: u64) -> io::Result<bool> {
93        if flocking() {
94            return self.flock(arg);
95        }
96        Ok(lock_file_segment(
97            self,
98            arg,
99            len.map(|some| some.try_into().unwrap()),
100            Start(offset),
101        )?)
102    }
103}