1use crate::path::{Path, PathBuf};
4
5#[derive(Debug)]
10pub struct Lock {
11 _file: std::fs::File,
12 path: PathBuf,
13}
14
15impl Lock {
16 pub fn try_acquire(path: &Path) -> Result<Self, LockError> {
27 let file = std::fs::OpenOptions::new()
28 .read(true)
29 .write(true)
30 .create(true)
31 .truncate(false)
32 .open(path)
33 .map_err(|source| LockError::io(path, source))?;
34
35 match fs4::FileExt::try_lock(&file) {
36 Ok(()) => Ok(Self {
37 _file: file,
38 path: path.to_path_buf(),
39 }),
40 Err(fs4::TryLockError::WouldBlock) => Err(LockError::busy(path)),
41 Err(fs4::TryLockError::Error(source)) => Err(LockError::io(path, source)),
42 }
43 }
44
45 #[must_use]
47 pub fn path(&self) -> &Path {
48 &self.path
49 }
50}
51
52#[derive(Debug, thiserror::Error)]
54#[error(transparent)]
55pub struct LockError(LockErrorKind);
56
57#[derive(Debug, thiserror::Error)]
58enum LockErrorKind {
59 #[error("another process holds the filesystem lock {path}")]
60 Busy { path: PathBuf },
61 #[error("failed to acquire lock {path}: {source}")]
62 Io {
63 path: PathBuf,
64 #[source]
65 source: std::io::Error,
66 },
67}
68
69impl LockError {
70 fn busy(path: &Path) -> Self {
71 Self(LockErrorKind::Busy {
72 path: path.to_path_buf(),
73 })
74 }
75
76 fn io(path: &Path, source: std::io::Error) -> Self {
77 Self(LockErrorKind::Io {
78 path: path.to_path_buf(),
79 source,
80 })
81 }
82
83 #[must_use]
85 pub const fn is_busy(&self) -> bool {
86 matches!(self.0, LockErrorKind::Busy { .. })
87 }
88
89 #[must_use]
91 pub fn path(&self) -> &Path {
92 match &self.0 {
93 LockErrorKind::Busy { path } | LockErrorKind::Io { path, .. } => path,
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use googletest::prelude::*;
101
102 use super::Lock;
103 use crate::temporary::Directory;
104
105 #[gtest]
106 fn lock_is_exclusive_and_released_with_its_guard() -> Result<()> {
107 let directory = Directory::new().or_fail()?;
108 let path = directory.path().join("operation.lock");
109 let first = Lock::try_acquire(&path).or_fail()?;
110
111 verify_true!(Lock::try_acquire(&path).unwrap_err().is_busy())?;
112
113 drop(first);
114
115 verify_that!(Lock::try_acquire(&path).or_fail()?.path(), eq(&*path))
116 }
117}