use std::fs::File;
use std::os::unix::io::AsRawFd;
use crate::backend::LockBackend;
use crate::error::Result;
use crate::lock::{LockKind, LockMode};
pub(crate) fn lock(
file: &File,
kind: LockKind,
mode: LockMode,
backend: LockBackend,
) -> Result<()> {
match backend {
LockBackend::Flock => flock_backend::lock(file, kind, mode),
LockBackend::Fcntl => fcntl_backend::lock(file, kind, mode),
}
}
pub(crate) fn unlock(file: &File, backend: LockBackend) -> Result<()> {
match backend {
LockBackend::Flock => flock_backend::unlock(file),
LockBackend::Fcntl => fcntl_backend::unlock(file),
}
}
pub(crate) fn upgrade(file: &File, mode: LockMode, backend: LockBackend) -> Result<()> {
lock(file, LockKind::Exclusive, mode, backend)
}
pub(crate) fn downgrade(file: &File, backend: LockBackend) -> Result<()> {
lock(file, LockKind::Shared, LockMode::Blocking, backend)
}
mod flock_backend {
use super::*;
use libc::{LOCK_EX, LOCK_NB, LOCK_SH, LOCK_UN};
#[inline]
fn op(kind: LockKind, mode: LockMode) -> libc::c_int {
let base = match kind {
LockKind::Shared => LOCK_SH,
LockKind::Exclusive => LOCK_EX,
};
match mode {
LockMode::Blocking => base,
LockMode::NonBlocking => base | LOCK_NB,
}
}
pub fn lock(file: &File, kind: LockKind, mode: LockMode) -> Result<()> {
let fd = file.as_raw_fd();
loop {
let ret = unsafe { libc::flock(fd, op(kind, mode)) };
if ret == 0 {
return Ok(());
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINTR) {
continue;
}
return Err(err.into());
}
}
pub fn unlock(file: &File) -> Result<()> {
let fd = file.as_raw_fd();
loop {
let ret = unsafe { libc::flock(fd, LOCK_UN) };
if ret == 0 {
return Ok(());
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINTR) {
continue;
}
return Err(err.into());
}
}
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_vendor = "apple",
target_os = "illumos"
))]
mod fcntl_backend {
use super::*;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod ofd {
pub const F_OFD_SETLK: libc::c_int = 37;
pub const F_OFD_SETLKW: libc::c_int = 38;
}
#[cfg(target_vendor = "apple")]
mod ofd {
pub const F_OFD_SETLK: libc::c_int = 90;
pub const F_OFD_SETLKW: libc::c_int = 91;
}
#[cfg(target_os = "illumos")]
mod ofd {
pub const F_OFD_SETLK: libc::c_int = 48;
pub const F_OFD_SETLKW: libc::c_int = 49;
}
const OFD_UNSUPPORTED: &str =
"open-file-description locks are not supported by this OS version or filesystem";
#[inline]
fn setlk_cmd(mode: LockMode) -> libc::c_int {
match mode {
LockMode::Blocking => ofd::F_OFD_SETLKW,
LockMode::NonBlocking => ofd::F_OFD_SETLK,
}
}
#[inline]
fn unlock_cmd() -> libc::c_int {
ofd::F_OFD_SETLK
}
#[inline]
unsafe fn make_flock(l_type: libc::c_short) -> libc::flock {
let mut fl: libc::flock = std::mem::zeroed();
fl.l_type = l_type;
fl.l_whence = libc::SEEK_SET as libc::c_short;
fl.l_start = 0;
fl.l_len = 0; fl.l_pid = 0; fl
}
fn fcntl_call(
fd: libc::c_int,
cmd: libc::c_int,
fl: &mut libc::flock,
map_contention: bool,
) -> Result<()> {
loop {
let ret = unsafe { libc::fcntl(fd, cmd, fl as *mut libc::flock) };
if ret != -1 {
return Ok(());
}
let err = std::io::Error::last_os_error();
let raw = err.raw_os_error().unwrap_or(0);
if raw == libc::EINTR {
continue;
}
if map_contention && (raw == libc::EACCES || raw == libc::EAGAIN) {
return Err(crate::Error::WouldBlock);
}
if raw == libc::EINVAL || raw == libc::ENOTSUP || raw == libc::EOPNOTSUPP {
return Err(crate::Error::Unsupported(OFD_UNSUPPORTED));
}
return Err(crate::Error::Io(err));
}
}
pub fn lock(file: &File, kind: LockKind, mode: LockMode) -> Result<()> {
let fd = file.as_raw_fd();
let l_type = match kind {
LockKind::Shared => libc::F_RDLCK as libc::c_short,
LockKind::Exclusive => libc::F_WRLCK as libc::c_short,
};
let mut fl = unsafe { make_flock(l_type) };
fcntl_call(fd, setlk_cmd(mode), &mut fl, true)
}
pub fn unlock(file: &File) -> Result<()> {
let fd = file.as_raw_fd();
let mut fl = unsafe { make_flock(libc::F_UNLCK as libc::c_short) };
fcntl_call(fd, unlock_cmd(), &mut fl, false)
}
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_vendor = "apple",
target_os = "illumos"
)))]
mod fcntl_backend {
use super::*;
const UNSUPPORTED: &str = "open-file-description locks are unavailable on this Unix target";
pub fn lock(_file: &File, _kind: LockKind, _mode: LockMode) -> Result<()> {
Err(crate::Error::Unsupported(UNSUPPORTED))
}
pub fn unlock(_file: &File) -> Result<()> {
Err(crate::Error::Unsupported(UNSUPPORTED))
}
}