chj_unix_util/
file_lock.rs

1use std::{fs::File, os::unix::fs::OpenOptionsExt, path::Path};
2
3use nix::errno::Errno;
4use ouroboros::self_referencing;
5
6use crate::unix::{easy_flock_nonblocking, FlockGuard};
7
8#[self_referencing]
9pub struct FileLock {
10    file: File,
11
12    #[borrows(mut file)]
13    #[covariant]
14    flock_guard: FlockGuard<'this>,
15}
16
17#[derive(thiserror::Error, Debug)]
18pub enum FileLockError {
19    #[error("opening file path")]
20    OpenError(#[from] std::io::Error),
21    #[error("calling flock")]
22    FlockError(#[from] Errno),
23    #[error("lock already taken")]
24    AlreadyLocked,
25}
26
27impl FileLock {
28    pub fn leak(&mut self) {
29        self.with_flock_guard_mut(|g| g.leak());
30    }
31}
32
33/// Try to get an flock based lock on a lock file, give an
34/// `FileLockError::AlreadyLocked` error if can't get it. The file is
35/// created or truncated.
36pub fn file_lock_nonblocking<P: AsRef<Path>>(
37    path: P,
38    exclusive: bool,
39) -> Result<FileLock, FileLockError> {
40    let mut opts = File::options();
41    opts.read(true);
42    opts.write(true);
43    opts.truncate(false);
44    opts.create(true);
45    opts.mode(0o600); // XX how to make portable?
46    let file = opts.open(path.as_ref())?;
47    FileLock::try_new(file, |file| {
48        if let Some(flock_guard) = easy_flock_nonblocking(file, exclusive)? {
49            Ok(flock_guard)
50        } else {
51            Err(FileLockError::AlreadyLocked)
52        }
53    })
54}