LCOV - differential code coverage report
Current view: top level - libs/utils/src - lock_file.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 82.8 % 58 48 10 48
Current Date: 2023-10-19 02:04:12 Functions: 60.0 % 5 3 2 3
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  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 UBC           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                 :     pub fn write_content(mut self, contents: String) -> anyhow::Result<LockFileGuard> {
      45 CBC        1060 :         self.file
      46            1060 :             .set_len(0)
      47            1060 :             .context("Failed to truncate lockfile")?;
      48            1060 :         self.file
      49            1060 :             .write_all(contents.as_bytes())
      50            1060 :             .with_context(|| format!("Failed to write '{contents}' contents into lockfile"))?;
      51            1060 :         crashsafe::fsync_file_and_parent(&self.path).context("fsync lockfile")?;
      52            1060 :         Ok(LockFileGuard(self.file))
      53            1060 :     }
      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            1060 : pub fn create_exclusive(lock_file_path: &Utf8Path) -> anyhow::Result<UnwrittenLockFile> {
      64            1060 :     let lock_file = fs::OpenOptions::new()
      65            1060 :         .create(true) // O_CREAT
      66            1060 :         .write(true)
      67            1060 :         .open(lock_file_path)
      68            1060 :         .context("open lock file")?;
      69                 : 
      70            1060 :     let res = fcntl::flock(
      71            1060 :         lock_file.as_raw_fd(),
      72            1060 :         fcntl::FlockArg::LockExclusiveNonblock,
      73            1060 :     );
      74 UBC           0 :     match res {
      75 CBC        1060 :         Ok(()) => Ok(UnwrittenLockFile {
      76            1060 :             path: lock_file_path.to_owned(),
      77            1060 :             file: lock_file,
      78            1060 :         }),
      79 UBC           0 :         Err(EAGAIN) => anyhow::bail!("file is already locked"),
      80               0 :         Err(e) => Err(e).context("flock error"),
      81                 :     }
      82 CBC        1060 : }
      83                 : 
      84                 : /// Returned by [`read_and_hold_lock_file`].
      85                 : /// Check out the [`pid_file`](crate::pid_file) module for what the variants mean
      86                 : /// and potential caveats if the lock files that are used to store PIDs.
      87                 : pub enum LockFileRead {
      88                 :     /// No file exists at the given path.
      89                 :     NotExist,
      90                 :     /// No other process held the lock file, so we grabbed an flock
      91                 :     /// on it and read its contents.
      92                 :     /// Release the flock by dropping the [`LockFileGuard`].
      93                 :     NotHeldByAnyProcess(LockFileGuard, String),
      94                 :     /// The file exists but another process was holding an flock on it.
      95                 :     LockedByOtherProcess {
      96                 :         not_locked_file: fs::File,
      97                 :         content: String,
      98                 :     },
      99                 : }
     100                 : 
     101                 : /// Open & try to lock the lock file at the given `path`, returning a [handle][`LockFileRead`] to
     102                 : /// inspect its content. It is not an `Err(...)` if the file does not exist or is already locked.
     103                 : /// Check the [`LockFileRead`] variants for details.
     104            2190 : pub fn read_and_hold_lock_file(path: &Utf8Path) -> anyhow::Result<LockFileRead> {
     105            2190 :     let res = fs::OpenOptions::new().read(true).open(path);
     106            2190 :     let mut lock_file = match res {
     107            2190 :         Ok(f) => f,
     108 UBC           0 :         Err(e) => match e.kind() {
     109               0 :             std::io::ErrorKind::NotFound => return Ok(LockFileRead::NotExist),
     110               0 :             _ => return Err(e).context("open lock file"),
     111                 :         },
     112                 :     };
     113 CBC        2190 :     let res = fcntl::flock(
     114            2190 :         lock_file.as_raw_fd(),
     115            2190 :         fcntl::FlockArg::LockExclusiveNonblock,
     116            2190 :     );
     117            2190 :     // We need the content regardless of lock success / failure.
     118            2190 :     // But, read it after flock so that, if it succeeded, the content is consistent.
     119            2190 :     let mut content = String::new();
     120            2190 :     lock_file
     121            2190 :         .read_to_string(&mut content)
     122            2190 :         .context("read lock file")?;
     123            2146 :     match res {
     124              44 :         Ok(()) => Ok(LockFileRead::NotHeldByAnyProcess(
     125              44 :             LockFileGuard(lock_file),
     126              44 :             content,
     127              44 :         )),
     128            2146 :         Err(EAGAIN) => Ok(LockFileRead::LockedByOtherProcess {
     129            2146 :             not_locked_file: lock_file,
     130            2146 :             content,
     131            2146 :         }),
     132 UBC           0 :         Err(e) => Err(e).context("flock error"),
     133                 :     }
     134 CBC        2190 : }
        

Generated by: LCOV version 2.1-beta