LCOV - code coverage report
Current view: top level - libs/utils/src - lock_file.rs (source / functions) Coverage Total Hit
Test: d0feeceb9d5ee9c8e73bee7d4ffcced539793178.info Lines: 0.0 % 60 0
Test Date: 2024-06-26 15:19:01 Functions: 0.0 % 5 0

            Line data    Source code
       1              : //! A module to create and read lock files.
       2              : //!
       3              : //! File locking is done using [`fcntl::flock`] exclusive locks.
       4              : //! The only consumer of this module is currently
       5              : //! [`pid_file`](crate::pid_file). See the module-level comment
       6              : //! there for potential pitfalls with lock files that are used
       7              : //! to store PIDs (pidfiles).
       8              : 
       9              : use std::{
      10              :     fs,
      11              :     io::{Read, Write},
      12              :     ops::Deref,
      13              :     os::unix::prelude::AsRawFd,
      14              : };
      15              : 
      16              : use anyhow::Context;
      17              : use camino::{Utf8Path, Utf8PathBuf};
      18              : use nix::{errno::Errno::EAGAIN, fcntl};
      19              : 
      20              : use crate::crashsafe;
      21              : 
      22              : /// A handle to an open and unlocked, but not-yet-written lock file.
      23              : /// Returned by [`create_exclusive`].
      24              : #[must_use]
      25              : pub struct UnwrittenLockFile {
      26              :     path: Utf8PathBuf,
      27              :     file: fs::File,
      28              : }
      29              : 
      30              : /// Returned by [`UnwrittenLockFile::write_content`].
      31              : #[must_use]
      32              : pub struct LockFileGuard(fs::File);
      33              : 
      34              : impl Deref for LockFileGuard {
      35              :     type Target = fs::File;
      36              : 
      37            0 :     fn deref(&self) -> &Self::Target {
      38            0 :         &self.0
      39            0 :     }
      40              : }
      41              : 
      42              : impl UnwrittenLockFile {
      43              :     /// Replace the content of this lock file with the byte representation of `contents`.
      44            0 :     pub fn write_content(mut self, contents: String) -> anyhow::Result<LockFileGuard> {
      45            0 :         self.file
      46            0 :             .set_len(0)
      47            0 :             .context("Failed to truncate lockfile")?;
      48            0 :         self.file
      49            0 :             .write_all(contents.as_bytes())
      50            0 :             .with_context(|| format!("Failed to write '{contents}' contents into lockfile"))?;
      51            0 :         crashsafe::fsync_file_and_parent(&self.path).context("fsync lockfile")?;
      52            0 :         Ok(LockFileGuard(self.file))
      53            0 :     }
      54              : }
      55              : 
      56              : /// Creates and opens a lock file in the path, grabs an exclusive flock on it, and returns
      57              : /// a handle that allows overwriting the locked file's content.
      58              : ///
      59              : /// The exclusive lock is released when dropping the returned handle.
      60              : ///
      61              : /// It is not an error if the file already exists.
      62              : /// It is an error if the file is already locked.
      63            0 : pub fn create_exclusive(lock_file_path: &Utf8Path) -> anyhow::Result<UnwrittenLockFile> {
      64            0 :     let lock_file = fs::OpenOptions::new()
      65            0 :         .create(true) // O_CREAT
      66            0 :         .truncate(true)
      67            0 :         .write(true)
      68            0 :         .open(lock_file_path)
      69            0 :         .context("open lock file")?;
      70              : 
      71            0 :     let res = fcntl::flock(
      72            0 :         lock_file.as_raw_fd(),
      73            0 :         fcntl::FlockArg::LockExclusiveNonblock,
      74            0 :     );
      75            0 :     match res {
      76            0 :         Ok(()) => Ok(UnwrittenLockFile {
      77            0 :             path: lock_file_path.to_owned(),
      78            0 :             file: lock_file,
      79            0 :         }),
      80            0 :         Err(EAGAIN) => anyhow::bail!("file is already locked"),
      81            0 :         Err(e) => Err(e).context("flock error"),
      82              :     }
      83            0 : }
      84              : 
      85              : /// Returned by [`read_and_hold_lock_file`].
      86              : /// Check out the [`pid_file`](crate::pid_file) module for what the variants mean
      87              : /// and potential caveats if the lock files that are used to store PIDs.
      88              : pub enum LockFileRead {
      89              :     /// No file exists at the given path.
      90              :     NotExist,
      91              :     /// No other process held the lock file, so we grabbed an flock
      92              :     /// on it and read its contents.
      93              :     /// Release the flock by dropping the [`LockFileGuard`].
      94              :     NotHeldByAnyProcess(LockFileGuard, String),
      95              :     /// The file exists but another process was holding an flock on it.
      96              :     LockedByOtherProcess {
      97              :         not_locked_file: fs::File,
      98              :         content: String,
      99              :     },
     100              : }
     101              : 
     102              : /// Open & try to lock the lock file at the given `path`, returning a [handle][`LockFileRead`] to
     103              : /// inspect its content. It is not an `Err(...)` if the file does not exist or is already locked.
     104              : /// Check the [`LockFileRead`] variants for details.
     105            0 : pub fn read_and_hold_lock_file(path: &Utf8Path) -> anyhow::Result<LockFileRead> {
     106            0 :     let res = fs::OpenOptions::new().read(true).open(path);
     107            0 :     let mut lock_file = match res {
     108            0 :         Ok(f) => f,
     109            0 :         Err(e) => match e.kind() {
     110            0 :             std::io::ErrorKind::NotFound => return Ok(LockFileRead::NotExist),
     111            0 :             _ => return Err(e).context("open lock file"),
     112              :         },
     113              :     };
     114            0 :     let res = fcntl::flock(
     115            0 :         lock_file.as_raw_fd(),
     116            0 :         fcntl::FlockArg::LockExclusiveNonblock,
     117            0 :     );
     118            0 :     // We need the content regardless of lock success / failure.
     119            0 :     // But, read it after flock so that, if it succeeded, the content is consistent.
     120            0 :     let mut content = String::new();
     121            0 :     lock_file
     122            0 :         .read_to_string(&mut content)
     123            0 :         .context("read lock file")?;
     124            0 :     match res {
     125            0 :         Ok(()) => Ok(LockFileRead::NotHeldByAnyProcess(
     126            0 :             LockFileGuard(lock_file),
     127            0 :             content,
     128            0 :         )),
     129            0 :         Err(EAGAIN) => Ok(LockFileRead::LockedByOtherProcess {
     130            0 :             not_locked_file: lock_file,
     131            0 :             content,
     132            0 :         }),
     133            0 :         Err(e) => Err(e).context("flock error"),
     134              :     }
     135            0 : }
        

Generated by: LCOV version 2.1-beta