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