use std::fs::File;
use std::io;
use crate::error::{Error, ErrorContext, Result};
pub(crate) fn acquire_writer_lock(file: &File) -> Result<()> {
match lock_exclusive_nonblocking(file) {
Ok(()) => Ok(()),
Err(error) if is_lock_contention(&error) => Err(Error::writer_locked(
"another writer already holds the exclusive lock on this file",
)
.with_context(ErrorContext::File)),
Err(error) => Err(Error::io(error, None).with_context(ErrorContext::File)),
}
}
#[cfg(unix)]
pub(crate) fn release_writer_lock(file: &File) -> io::Result<()> {
use std::os::fd::AsRawFd;
unsafe extern "C" {
fn flock(file_descriptor: i32, operation: i32) -> i32;
}
const LOCK_UN: i32 = 8;
let result = unsafe { flock(file.as_raw_fd(), LOCK_UN) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(unix)]
fn is_lock_contention(error: &io::Error) -> bool {
error.kind() == io::ErrorKind::WouldBlock
}
#[cfg(windows)]
fn is_lock_contention(error: &io::Error) -> bool {
const LOCK_VIOLATION: i32 = 33;
error.kind() == io::ErrorKind::WouldBlock || error.raw_os_error() == Some(LOCK_VIOLATION)
}
#[cfg(not(any(unix, windows)))]
fn is_lock_contention(_error: &io::Error) -> bool {
false
}
fn lock_exclusive_nonblocking(file: &File) -> io::Result<()> {
#[cfg(unix)]
{
use std::os::fd::AsRawFd;
unsafe extern "C" {
fn flock(file_descriptor: i32, operation: i32) -> i32;
}
const LOCK_EX: i32 = 2;
const LOCK_NB: i32 = 4;
let result = unsafe { flock(file.as_raw_fd(), LOCK_EX | LOCK_NB) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(windows)]
{
use std::ffi::c_void;
use std::mem::zeroed;
use std::os::windows::io::AsRawHandle;
#[repr(C)]
struct Overlapped {
internal: usize,
internal_high: usize,
offset: u32,
offset_high: u32,
event: *mut c_void,
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn LockFileEx(
file: *mut c_void,
flags: u32,
reserved: u32,
bytes_to_lock_low: u32,
bytes_to_lock_high: u32,
overlapped: *mut Overlapped,
) -> i32;
}
const LOCKFILE_EXCLUSIVE_LOCK: u32 = 0x0000_0002;
const LOCKFILE_FAIL_IMMEDIATELY: u32 = 0x0000_0001;
const LOCK_OFFSET: u64 = u64::MAX - 1;
let mut overlapped: Overlapped = unsafe { zeroed() };
overlapped.offset = LOCK_OFFSET as u32;
overlapped.offset_high = (LOCK_OFFSET >> 32) as u32;
let result = unsafe {
LockFileEx(
file.as_raw_handle(),
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
1,
0,
&mut overlapped,
)
};
if result != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(not(any(unix, windows)))]
{
let _ = file;
Err(io::Error::new(
io::ErrorKind::Unsupported,
"exclusive writer locks are unavailable on this target",
))
}
}