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 1133 : pub fn write_content(mut self, contents: String) -> anyhow::Result<LockFileGuard> {
45 1133 : self.file
46 1133 : .set_len(0)
47 1133 : .context("Failed to truncate lockfile")?;
48 1133 : self.file
49 1133 : .write_all(contents.as_bytes())
50 1133 : .with_context(|| format!("Failed to write '{contents}' contents into lockfile"))?;
51 1133 : crashsafe::fsync_file_and_parent(&self.path).context("fsync lockfile")?;
52 1133 : Ok(LockFileGuard(self.file))
53 1133 : }
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 1133 : pub fn create_exclusive(lock_file_path: &Utf8Path) -> anyhow::Result<UnwrittenLockFile> {
64 1133 : let lock_file = fs::OpenOptions::new()
65 1133 : .create(true) // O_CREAT
66 1133 : .write(true)
67 1133 : .open(lock_file_path)
68 1133 : .context("open lock file")?;
69 :
70 1133 : let res = fcntl::flock(
71 1133 : lock_file.as_raw_fd(),
72 1133 : fcntl::FlockArg::LockExclusiveNonblock,
73 1133 : );
74 0 : match res {
75 1133 : Ok(()) => Ok(UnwrittenLockFile {
76 1133 : path: lock_file_path.to_owned(),
77 1133 : file: lock_file,
78 1133 : }),
79 0 : Err(EAGAIN) => anyhow::bail!("file is already locked"),
80 0 : Err(e) => Err(e).context("flock error"),
81 : }
82 1133 : }
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 3405 : pub fn read_and_hold_lock_file(path: &Utf8Path) -> anyhow::Result<LockFileRead> {
105 3405 : let res = fs::OpenOptions::new().read(true).open(path);
106 3405 : let mut lock_file = match res {
107 3405 : Ok(f) => f,
108 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 3405 : let res = fcntl::flock(
114 3405 : lock_file.as_raw_fd(),
115 3405 : fcntl::FlockArg::LockExclusiveNonblock,
116 3405 : );
117 3405 : // We need the content regardless of lock success / failure.
118 3405 : // But, read it after flock so that, if it succeeded, the content is consistent.
119 3405 : let mut content = String::new();
120 3405 : lock_file
121 3405 : .read_to_string(&mut content)
122 3405 : .context("read lock file")?;
123 3358 : match res {
124 47 : Ok(()) => Ok(LockFileRead::NotHeldByAnyProcess(
125 47 : LockFileGuard(lock_file),
126 47 : content,
127 47 : )),
128 3358 : Err(EAGAIN) => Ok(LockFileRead::LockedByOtherProcess {
129 3358 : not_locked_file: lock_file,
130 3358 : content,
131 3358 : }),
132 0 : Err(e) => Err(e).context("flock error"),
133 : }
134 3405 : }
|