Skip to repository content205 lines · 6.8 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:03:57.866Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
mutation_lock.rs
1use std::sync::{Mutex, MutexGuard};
2
3use sha2::{Digest, Sha256};
4use thiserror::Error;
5
6use crate::KeyringLocator;
7
8static PROCESS_MUTATION_LOCK: Mutex<()> = Mutex::new(());
9
10pub(crate) struct IdentityMutationGuard {
11 _process_guard: MutexGuard<'static, ()>,
12 _platform_guard: PlatformMutationGuard,
13}
14
15impl IdentityMutationGuard {
16 pub(crate) fn acquire(locator: &KeyringLocator) -> Result<Self, MutationLockError> {
17 let process_guard = PROCESS_MUTATION_LOCK
18 .lock()
19 .map_err(|_| MutationLockError::ProcessLockPoisoned)?;
20 let platform_guard = PlatformMutationGuard::acquire(locator.service())?;
21 Ok(Self {
22 _process_guard: process_guard,
23 _platform_guard: platform_guard,
24 })
25 }
26}
27
28#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
29pub(crate) enum MutationLockError {
30 #[error("the process identity mutation lock is unavailable")]
31 ProcessLockPoisoned,
32 #[error("the operating-system identity mutation lock is unavailable")]
33 PlatformLockUnavailable,
34}
35
36fn service_hash(service: &str) -> String {
37 hex::encode(Sha256::digest(service.as_bytes()))
38}
39
40#[cfg(unix)]
41struct PlatformMutationGuard {
42 _file: std::fs::File,
43}
44
45#[cfg(unix)]
46impl PlatformMutationGuard {
47 fn acquire(service: &str) -> Result<Self, MutationLockError> {
48 use std::{
49 fs::{self, OpenOptions, Permissions},
50 os::unix::{
51 fs::{MetadataExt, OpenOptionsExt, PermissionsExt},
52 io::AsRawFd,
53 },
54 path::PathBuf,
55 };
56
57 let user_id = unsafe { libc::getuid() };
58 let lock_directory = PathBuf::from("/tmp").join(format!("omega-identity-{user_id}"));
59 match fs::create_dir(&lock_directory) {
60 Ok(()) => fs::set_permissions(&lock_directory, Permissions::from_mode(0o700))
61 .map_err(|_| MutationLockError::PlatformLockUnavailable)?,
62 Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
63 Err(_) => return Err(MutationLockError::PlatformLockUnavailable),
64 }
65
66 let directory_metadata = fs::symlink_metadata(&lock_directory)
67 .map_err(|_| MutationLockError::PlatformLockUnavailable)?;
68 if !directory_metadata.is_dir()
69 || directory_metadata.file_type().is_symlink()
70 || directory_metadata.uid() != user_id
71 || directory_metadata.mode() & 0o077 != 0
72 {
73 return Err(MutationLockError::PlatformLockUnavailable);
74 }
75
76 let lock_path = lock_directory.join(format!("{}.lock", service_hash(service)));
77 let file = OpenOptions::new()
78 .create(true)
79 .truncate(false)
80 .read(true)
81 .write(true)
82 .mode(0o600)
83 .custom_flags(libc::O_CLOEXEC | libc::O_NOFOLLOW)
84 .open(lock_path)
85 .map_err(|_| MutationLockError::PlatformLockUnavailable)?;
86
87 let file_metadata = file
88 .metadata()
89 .map_err(|_| MutationLockError::PlatformLockUnavailable)?;
90 if !file_metadata.is_file()
91 || file_metadata.uid() != user_id
92 || file_metadata.nlink() != 1
93 || file_metadata.mode() & 0o077 != 0
94 {
95 return Err(MutationLockError::PlatformLockUnavailable);
96 }
97
98 loop {
99 let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) };
100 if result == 0 {
101 break;
102 }
103 if std::io::Error::last_os_error().kind() != std::io::ErrorKind::Interrupted {
104 return Err(MutationLockError::PlatformLockUnavailable);
105 }
106 }
107
108 Ok(Self { _file: file })
109 }
110}
111
112#[cfg(windows)]
113struct PlatformMutationGuard {
114 mutex_handle: windows_sys::Win32::Foundation::HANDLE,
115}
116
117#[cfg(windows)]
118impl PlatformMutationGuard {
119 fn acquire(service: &str) -> Result<Self, MutationLockError> {
120 use windows_sys::Win32::{
121 Foundation::{WAIT_ABANDONED, WAIT_OBJECT_0},
122 Security::SECURITY_ATTRIBUTES,
123 System::Threading::{CreateMutexW, INFINITE, WaitForSingleObject},
124 };
125
126 let mutex_name = format!("Local\\OmegaIdentity-{}", service_hash(service));
127 let wide_name: Vec<u16> = mutex_name
128 .encode_utf16()
129 .chain(std::iter::once(0))
130 .collect();
131 let mutex_handle = unsafe {
132 CreateMutexW(
133 std::ptr::null::<SECURITY_ATTRIBUTES>(),
134 0,
135 wide_name.as_ptr(),
136 )
137 };
138 if mutex_handle.is_null() {
139 return Err(MutationLockError::PlatformLockUnavailable);
140 }
141
142 let wait_result = unsafe { WaitForSingleObject(mutex_handle, INFINITE) };
143 if wait_result != WAIT_OBJECT_0 && wait_result != WAIT_ABANDONED {
144 unsafe {
145 windows_sys::Win32::Foundation::CloseHandle(mutex_handle);
146 }
147 return Err(MutationLockError::PlatformLockUnavailable);
148 }
149
150 Ok(Self { mutex_handle })
151 }
152}
153
154#[cfg(windows)]
155impl Drop for PlatformMutationGuard {
156 fn drop(&mut self) {
157 unsafe {
158 windows_sys::Win32::System::Threading::ReleaseMutex(self.mutex_handle);
159 windows_sys::Win32::Foundation::CloseHandle(self.mutex_handle);
160 }
161 }
162}
163
164#[cfg(not(any(unix, windows)))]
165struct PlatformMutationGuard;
166
167#[cfg(not(any(unix, windows)))]
168impl PlatformMutationGuard {
169 fn acquire(_service: &str) -> Result<Self, MutationLockError> {
170 Err(MutationLockError::PlatformLockUnavailable)
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use app_identity::AppChannel;
177
178 use super::*;
179
180 #[test]
181 fn channel_lock_names_are_deterministic_distinct_and_secret_free() {
182 let hashes = AppChannel::ALL
183 .map(|channel| service_hash(KeyringLocator::for_channel(channel).service()));
184 assert!(hashes.iter().all(|hash| hash.len() == 64));
185 assert!(hashes.iter().all(|hash| {
186 hash.bytes()
187 .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
188 }));
189 for (channel, hash) in AppChannel::ALL.into_iter().zip(hashes.iter()) {
190 let service = KeyringLocator::for_channel(channel).service().to_string();
191 assert!(!hash.contains(&service));
192 assert_eq!(hash, &service_hash(&service));
193 }
194 }
195
196 #[test]
197 fn mutation_guard_can_be_reacquired_after_drop() {
198 let locator = KeyringLocator::for_channel(AppChannel::Dev);
199 {
200 let _guard = IdentityMutationGuard::acquire(&locator).expect("acquire identity lock");
201 }
202 let _guard = IdentityMutationGuard::acquire(&locator).expect("reacquire identity lock");
203 }
204}
205