1use std::fs::{File, OpenOptions};
9use std::io;
10use std::path::Path;
11
12const LOCK_FILE: &str = ".aede.lock";
13
14pub struct StoreLock {
19 _file: File,
20}
21
22impl StoreLock {
23 pub fn acquire(data_dir: &Path) -> io::Result<Self> {
25 let file = open_lock(data_dir)?;
26 file.lock()?;
27 Ok(Self { _file: file })
28 }
29
30 pub fn try_acquire(data_dir: &Path) -> io::Result<Self> {
32 let file = open_lock(data_dir)?;
33 file.try_lock()?;
34 Ok(Self { _file: file })
35 }
36}
37
38impl Drop for StoreLock {
39 fn drop(&mut self) {
40 let _ = self._file.unlock();
44 }
45}
46
47fn open_lock(data_dir: &Path) -> io::Result<File> {
48 std::fs::create_dir_all(data_dir)?;
49 let path = data_dir.join(LOCK_FILE);
50 let inspected = match std::fs::symlink_metadata(&path) {
51 Ok(metadata) => {
52 check_regular_lock(&metadata)?;
53 Some(metadata)
54 }
55 Err(error) if error.kind() == io::ErrorKind::NotFound => None,
56 Err(error) => return Err(error),
57 };
58 let mut options = OpenOptions::new();
59 options.read(true).write(true).create(true).truncate(false);
60 #[cfg(unix)]
61 {
62 use std::os::unix::fs::OpenOptionsExt;
63 options.mode(0o600);
64 }
65 let file = options.open(&path)?;
66 let opened = file.metadata()?;
67 check_regular_lock(&opened)?;
68 let published = std::fs::symlink_metadata(&path)?;
69 check_regular_lock(&published)?;
70 #[cfg(unix)]
71 {
72 use std::os::unix::fs::MetadataExt;
73 let same = |metadata: &std::fs::Metadata| {
74 metadata.dev() == opened.dev() && metadata.ino() == opened.ino()
75 };
76 if !same(&published) || inspected.as_ref().is_some_and(|metadata| !same(metadata)) {
77 return Err(io::Error::other("writer lock changed while being opened"));
78 }
79 }
80 #[cfg(not(unix))]
81 let _ = inspected;
82 Ok(file)
83}
84
85fn check_regular_lock(metadata: &std::fs::Metadata) -> io::Result<()> {
86 if !metadata.file_type().is_file() {
87 return Err(io::Error::new(
88 io::ErrorKind::InvalidInput,
89 "writer lock must be an ordinary non-symlink file",
90 ));
91 }
92 Ok(())
93}
94
95#[cfg(test)]
96#[path = "store_lock_tests.rs"]
97mod tests;
98
99#[cfg(test)]
100#[path = "persistence_test_support.rs"]
101pub(crate) mod test_support;