file_guard/os/
unix.rs

1//! Provides low-level support operations for file locking on UNIX platforms.
2use libc::{fcntl, off_t, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, SEEK_SET};
3
4use std::fs::File;
5use std::io::{self, Error, ErrorKind};
6use std::ops::Deref;
7use std::os::raw::c_short;
8use std::os::unix::io::AsRawFd;
9
10use crate::{FileGuard, Lock};
11
12/// Acquires and releases a file lock.
13///
14/// # Safety
15///
16/// When used to unlock, this does not guarantee that an exclusive lock is
17/// already held.
18pub unsafe fn raw_file_lock(
19    f: &File,
20    lock: Option<Lock>,
21    off: usize,
22    len: usize,
23    wait: bool,
24) -> io::Result<()> {
25    if len == 0 {
26        return Err(ErrorKind::InvalidInput.into());
27    }
28
29    let op = match wait {
30        true => F_SETLKW,
31        false => F_SETLK,
32    };
33
34    let lock = libc::flock {
35        l_start: off as off_t,
36        l_len: len as off_t,
37        l_pid: 0,
38        l_type: match lock {
39            Some(Lock::Shared) => F_RDLCK as c_short,
40            Some(Lock::Exclusive) => F_WRLCK as c_short,
41            None => F_UNLCK as c_short,
42        },
43        l_whence: SEEK_SET as c_short,
44        #[cfg(any(target_os = "freebsd", target_os = "solaris", target_os = "illumos"))]
45        l_sysid: 0,
46        #[cfg(any(target_os = "solaris", target_os = "illumos"))]
47        l_pad: [0; 4],
48    };
49
50    loop {
51        let rc = fcntl(f.as_raw_fd(), op, &lock);
52        if rc == -1 {
53            let err = Error::last_os_error();
54            if err.kind() != ErrorKind::Interrupted {
55                break Err(err);
56            }
57        } else {
58            break Ok(());
59        }
60    }
61}
62
63/// Downgrades a file lock from exclusive to shared.
64///
65/// # Safety
66///
67/// This does not guarantee that an exclusive lock is already held.
68pub unsafe fn raw_file_downgrade(f: &File, off: usize, len: usize) -> io::Result<()> {
69    raw_file_lock(f, Some(Lock::Shared), off, len, false)
70}
71
72/// UNIX-specific extensions to [`FileGuard`].
73///
74/// [`FileGuard`]: ../../struct.FileGuard.html
75pub trait FileGuardExt {
76    /// Upgrades a lock from [`Shared`] to [`Exclusive`].
77    ///
78    /// If the currently held lock is already [`Exclusive`], no change is made
79    /// and the method succeeds.
80    ///
81    /// [`Shared`]: ../../enum.Lock.html#variant.Shared
82    /// [`Exclusive`]: ../../enum.Lock.html#variant.Exclusive
83    fn upgrade(&mut self) -> io::Result<()>;
84
85    /// Attempts to upgrade a lock from [`Shared`] to [`Exclusive`].
86    ///
87    /// If the currently held lock is already [`Exclusive`], no change is made
88    /// and the method succeeds. If the upgrade cannot be obtained without
89    /// blocking, an `Error` of kind `ErrorKind::WouldBlock` is returned.
90    ///
91    /// [`Shared`]: ../../enum.Lock.html#variant.Shared
92    /// [`Exclusive`]: ../../enum.Lock.html#variant.Exclusive
93    fn try_upgrade(&mut self) -> io::Result<()>;
94}
95
96impl<T> FileGuardExt for FileGuard<T>
97where
98    T: Deref<Target = File>,
99{
100    fn upgrade(&mut self) -> io::Result<()> {
101        if self.is_shared() {
102            unsafe {
103                raw_file_lock(
104                    &self.file,
105                    Some(Lock::Exclusive),
106                    self.offset,
107                    self.len,
108                    true,
109                )?;
110            }
111            self.lock = Lock::Exclusive;
112        }
113        Ok(())
114    }
115
116    fn try_upgrade(&mut self) -> io::Result<()> {
117        if self.is_shared() {
118            unsafe {
119                raw_file_lock(
120                    &self.file,
121                    Some(Lock::Exclusive),
122                    self.offset,
123                    self.len,
124                    false,
125                )?;
126            }
127            self.lock = Lock::Exclusive;
128        }
129        Ok(())
130    }
131}