From a650f503016e77e5b050495f07f763f06553346b Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 22 Mar 2019 08:04:12 +0100 Subject: [PATCH] src/tools/process_locker.rs: implement inter-process reader-writer locks --- Cargo.toml | 2 +- src/tools.rs | 3 + src/tools/process_locker.rs | 155 ++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/tools/process_locker.rs diff --git a/Cargo.toml b/Cargo.toml index 97c6ab32..5cebf940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ hyper-tls = "0.3" lazy_static = "1.1" regex = "1.0" libc = "0.2" -nix = "0.12" +nix = "0.13" shellwords = "1.0" uuid = { version = "0.7", features = ["v4"] } chrono = "0.4" # Date and time library for Rust diff --git a/src/tools.rs b/src/tools.rs index 7fc1bbeb..5dabb416 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -32,6 +32,9 @@ pub mod tty; pub mod signalfd; pub mod daemon; +mod process_locker; +pub use process_locker::*; + #[macro_use] mod file_logger; pub use file_logger::*; diff --git a/src/tools/process_locker.rs b/src/tools/process_locker.rs new file mode 100644 index 00000000..8a21a9aa --- /dev/null +++ b/src/tools/process_locker.rs @@ -0,0 +1,155 @@ +//! Inter-process reader-writer lock builder. +//! +//! This implemenation uses fcntl record locks with non-blocking +//! F_SETLK command (never blocks). + +use failure::*; + +use std::sync::{Arc, Mutex}; +use std::os::unix::io::AsRawFd; + +// fixme: use F_OFD_ locks when implemented with nix::fcntl + +// Note: flock lock conversion is not atomic, so we need to use fcntl + +/// Inter-process reader-writer lock +pub struct ProcessLocker { + file: std::fs::File, + exclusive: bool, + writers: usize, +} + +/// Lock guard for shared locks +/// +/// Release the lock when it goes out of scope. +pub struct ProcessLockSharedGuard { + locker: Arc>, +} + +impl Drop for ProcessLockSharedGuard { + fn drop(&mut self) { + let mut data = self.locker.lock().unwrap(); + + if data.writers == 0 { panic!("unexpected ProcessLocker state"); } + + if data.writers == 1 && !data.exclusive { + + let op = libc::flock { + l_type: libc::F_UNLCK as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) { + panic!("unable to drop writer lock - {}", err); + } + data.writers = 0; + } + } +} + +/// Lock guard for exclusive locks +/// +/// Release the lock when it goes out of scope. +pub struct ProcessLockExclusiveGuard { + locker: Arc>, +} + +impl Drop for ProcessLockExclusiveGuard { + fn drop(&mut self) { + let mut data = self.locker.lock().unwrap(); + + if !data.exclusive { panic!("unexpected ProcessLocker state"); } + + let ltype = if data.writers != 0 { libc::F_RDLCK } else { libc::F_UNLCK }; + let op = libc::flock { + l_type: ltype as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) { + panic!("unable to drop exclusive lock - {}", err); + } + + data.exclusive = false; + } +} + +impl ProcessLocker { + + /// Create a new instance for the specified file. + /// + /// This simply creates the file if it does not exist. + pub fn new(lockfile: &str) -> Result>, Error> { + + let file = std::fs::OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(lockfile)?; + + Ok(Arc::new(Mutex::new(Self { + file: file, + exclusive: false, + writers: 0, + }))) + } + + fn try_lock(file: &std::fs::File, ltype: i32) -> Result<(), Error> { + + let op = libc::flock { + l_type: ltype as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + + nix::fcntl::fcntl(file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLK(&op))?; + + Ok(()) + } + + /// Try to aquire a shared lock + /// + /// On sucess, this makes sure that no other process can get an exclusive lock for the file. + pub fn try_shared_lock(locker: Arc>) -> Result { + + let mut data = locker.lock().unwrap(); + + if data.writers == 0 && !data.exclusive { + if let Err(err) = Self::try_lock(&data.file, libc::F_RDLCK) { + bail!("unable to get shared lock - {}", err); + } + } + + data.writers += 1; + + Ok(ProcessLockSharedGuard { locker: locker.clone() }) + } + + /// Try to aquire a exclusive lock + /// + /// Make sure the we are the only process which has locks for this file (shared or exclusive). + pub fn try_exclusive_lock(locker: Arc>) -> Result { + + let mut data = locker.lock().unwrap(); + + if data.exclusive { + bail!("already locked exclusively"); + } + + if let Err(err) = Self::try_lock(&data.file, libc::F_WRLCK) { + bail!("unable to get exclusive lock - {}", err); + } + + data.exclusive = true; + + Ok(ProcessLockExclusiveGuard { locker: locker.clone() }) + } +}