Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:03:57.866Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

mutation_lock.rs

205 lines · 6.8 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.