secunit_core/evidence/
lock.rs1use std::fs::{File, OpenOptions};
6use std::io;
7use std::path::Path;
8
9use fs2::FileExt;
10
11pub struct RootLock {
12 file: File,
13}
14
15impl RootLock {
16 pub fn acquire(root: &Path) -> io::Result<Self> {
19 let path = root.join(".secunit.lock");
20 let file = OpenOptions::new()
21 .create(true)
22 .read(true)
23 .write(true)
24 .truncate(false)
25 .open(&path)?;
26 file.lock_exclusive()?;
27 Ok(Self { file })
28 }
29
30 pub fn try_acquire(root: &Path) -> io::Result<Option<Self>> {
33 let path = root.join(".secunit.lock");
34 let file = OpenOptions::new()
35 .create(true)
36 .read(true)
37 .write(true)
38 .truncate(false)
39 .open(&path)?;
40 match file.try_lock_exclusive() {
41 Ok(()) => Ok(Some(Self { file })),
42 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
43 Err(e) => Err(e),
44 }
45 }
46}
47
48impl Drop for RootLock {
49 fn drop(&mut self) {
50 let _ = self.file.unlock();
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn try_acquire_returns_none_when_held() {
60 let dir = tempfile::tempdir().unwrap();
61 let _held = RootLock::acquire(dir.path()).unwrap();
62 let again = RootLock::try_acquire(dir.path()).unwrap();
63 assert!(again.is_none());
64 }
65
66 #[test]
67 fn lock_is_released_on_drop() {
68 let dir = tempfile::tempdir().unwrap();
69 {
70 let _held = RootLock::acquire(dir.path()).unwrap();
71 }
72 let again = RootLock::try_acquire(dir.path()).unwrap();
73 assert!(again.is_some());
74 }
75}