agent_graph_mcp/
owner_lock.rs1use fs2::FileExt;
8use std::fs::{File, OpenOptions};
9use std::io;
10use std::os::unix::fs::OpenOptionsExt;
11use std::path::{Path, PathBuf};
12
13pub const DATA_DIR_ALREADY_OWNED: &str =
15 "DATA_DIR_ALREADY_OWNED: another process owns this data directory";
16
17#[derive(Debug)]
24pub struct OwnerLock {
25 _file: File,
26 pub path: PathBuf,
27}
28
29impl OwnerLock {
30 pub fn acquire(data_dir: &Path) -> io::Result<Self> {
35 std::fs::create_dir_all(data_dir)?;
37
38 let path = data_dir.join(".owner.lock");
39 let file = OpenOptions::new()
40 .create(true)
41 .read(true)
42 .write(true)
43 .mode(0o600)
44 .open(&path)?;
45
46 match file.try_lock_exclusive() {
48 Ok(()) => Ok(Self { _file: file, path }),
49 Err(_) => Err(io::Error::new(
50 io::ErrorKind::AlreadyExists,
51 DATA_DIR_ALREADY_OWNED,
52 )),
53 }
54 }
55}
56
57impl Drop for OwnerLock {
58 fn drop(&mut self) {
59 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use tempfile::tempdir;
69
70 #[test]
71 fn acquire_and_reacquire_after_drop() {
72 let dir = tempdir().unwrap();
73 let lock1 = OwnerLock::acquire(dir.path());
74 assert!(lock1.is_ok(), "first acquire should succeed");
75
76 drop(lock1);
78
79 let lock2 = OwnerLock::acquire(dir.path());
81 assert!(lock2.is_ok(), "reacquire after drop should succeed");
82 }
83
84 #[test]
85 fn second_acquire_fails() {
86 let dir = tempdir().unwrap();
87 let _lock1 = OwnerLock::acquire(dir.path()).expect("first acquire");
88
89 let lock2 = OwnerLock::acquire(dir.path());
90 assert!(lock2.is_err(), "second concurrent acquire should fail");
91 let err = lock2.unwrap_err();
92 assert!(
93 err.to_string().contains("ALREADY_OWNED"),
94 "error should mention ALREADY_OWNED"
95 );
96 }
97
98 #[test]
99 fn lock_file_has_private_permissions() {
100 let dir = tempdir().unwrap();
101 let _lock = OwnerLock::acquire(dir.path()).expect("acquire");
102
103 let meta = std::fs::metadata(dir.path().join(".owner.lock")).unwrap();
104 use std::os::unix::fs::PermissionsExt;
105 let mode = meta.permissions().mode() & 0o777;
106 assert_eq!(
107 mode, 0o600,
108 "lock file should have 0600 permissions, got 0o{:o}",
109 mode
110 );
111 }
112}