citadel-tools/libcitadel/src/system/lock.rs
Bruce Leidl 61d5e10034 RealmFS refactored. Much Simpler.
The concept of an 'unsealed' RealmFS no longer exists so support for this has been
removed. The result is much less complex and easier to understand and maintain.
2020-08-03 19:18:49 -04:00

118 lines
3.2 KiB
Rust

use std::fs::{self,File,OpenOptions};
use std::io::{Error,ErrorKind};
use std::os::unix::io::AsRawFd;
use std::path::{Path,PathBuf};
use crate::Result;
///
/// Create a lockfile and acquire an exclusive lock with flock(2)
///
/// The lock can either be acquired by blocking until available or
/// by failing immediately if the lock is already held.
///
/// The lock is released and the lockfile is removed when `FileLock`
/// instance is dropped.
///
pub struct FileLock {
file: File,
path: PathBuf,
}
impl FileLock {
pub fn nonblocking_acquire<P: AsRef<Path>>(path: P) -> Result<Option<Self>> {
let file = Self::open_lockfile(path.as_ref())?;
let flock = FileLock {
file,
path: path.as_ref().into(),
};
if flock.lock(false)? {
Ok(Some(flock))
} else {
Ok(None)
}
}
pub fn acquire<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref().to_path_buf();
let file = Self::open_lockfile(&path)?;
let flock = FileLock { file, path };
flock.lock(true)?;
Ok(flock)
}
fn open_lockfile(path: &Path) -> Result<File> {
if let Some(parent) = path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
// Make a few attempts just in case we try to open lockfile
// at exact moment another process is releasing and deleting
// file.
for _ in 0..3 {
if let Some(file) = Self::try_create_lockfile(path)? {
return Ok(file);
}
if let Some(file) = Self::try_open_lockfile(path)? {
return Ok(file);
}
}
Err(format_err!("unable to open lockfile {}", path.display() ))
}
fn try_create_lockfile(path: &Path) -> Result<Option<File>> {
match OpenOptions::new().write(true).create_new(true).open(path) {
Ok(file) => Ok(Some(file)),
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => Ok(None),
Err(e) => Err(e.into()),
}
}
fn try_open_lockfile(path: &Path) -> Result<Option<File>> {
match File::open(path) {
Ok(file) => Ok(Some(file)),
Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
}
fn unlock(&self) -> Result<()> {
self.flock(libc::LOCK_UN, true)?;
Ok(())
}
fn lock(&self, block: bool) -> Result<bool> {
if block {
self.flock(libc::LOCK_EX, true)
} else {
self.flock(libc::LOCK_EX | libc::LOCK_NB, false)
}
}
fn flock(&self, flag: libc::c_int, block: bool) -> Result<bool> {
if unsafe { libc::flock(self.file.as_raw_fd(), flag) } < 0 {
let errno = Self::last_errno();
if !block && errno == libc::EWOULDBLOCK {
return Ok(false);
}
return Err(Error::from_raw_os_error(errno).into());
}
Ok(true)
}
pub fn last_errno() -> i32 {
unsafe { *libc::__errno_location() }
}
}
impl Drop for FileLock {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
let _ = self.unlock();
}
}