coven_foundation/files/
file_lock.rs1use std::fs::{File, TryLockError};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum LockMode {
14 Shared,
16 Exclusive,
18}
19
20#[derive(Debug)]
23pub struct FileLock {
24 file: File,
25}
26
27impl FileLock {
28 pub fn lock(file: File, mode: LockMode) -> std::io::Result<Self> {
30 os::lock(&file, mode)?;
31 Ok(Self { file })
32 }
33
34 pub fn try_lock(file: File, mode: LockMode) -> Result<Self, TryLockError> {
37 os::try_lock(&file, mode)?;
38 Ok(Self { file })
39 }
40
41 #[cfg(test)]
42 pub(crate) fn inherited_file(&self) -> File {
43 self.file.try_clone().unwrap()
44 }
45}
46
47impl Drop for FileLock {
48 fn drop(&mut self) {
49 os::unlock(&self.file).expect("release a held file lock");
52 }
53}
54
55#[cfg(unix)]
56mod os {
57 use super::LockMode;
58 use rustix::fs::{flock, FlockOperation};
59 use std::fs::{File, TryLockError};
60
61 pub(super) fn lock(file: &File, mode: LockMode) -> std::io::Result<()> {
62 let operation = match mode {
63 LockMode::Shared => FlockOperation::LockShared,
64 LockMode::Exclusive => FlockOperation::LockExclusive,
65 };
66 Ok(flock(file, operation)?)
67 }
68
69 pub(super) fn try_lock(file: &File, mode: LockMode) -> Result<(), TryLockError> {
70 let operation = match mode {
71 LockMode::Shared => FlockOperation::NonBlockingLockShared,
72 LockMode::Exclusive => FlockOperation::NonBlockingLockExclusive,
73 };
74 flock(file, operation).map_err(|errno| {
75 if errno == rustix::io::Errno::WOULDBLOCK {
76 TryLockError::WouldBlock
77 } else {
78 TryLockError::Error(errno.into())
79 }
80 })
81 }
82
83 pub(super) fn unlock(file: &File) -> std::io::Result<()> {
84 Ok(flock(file, FlockOperation::Unlock)?)
85 }
86}
87
88#[cfg(windows)]
89#[expect(
90 clippy::disallowed_methods,
91 reason = "std locks Windows files with LockFileEx; only its Android stub is refused"
92)]
93mod os {
94 use super::LockMode;
95 use std::fs::{File, TryLockError};
96
97 pub(super) fn lock(file: &File, mode: LockMode) -> std::io::Result<()> {
98 match mode {
99 LockMode::Shared => file.lock_shared(),
100 LockMode::Exclusive => file.lock(),
101 }
102 }
103
104 pub(super) fn try_lock(file: &File, mode: LockMode) -> Result<(), TryLockError> {
105 match mode {
106 LockMode::Shared => file.try_lock_shared(),
107 LockMode::Exclusive => file.try_lock(),
108 }
109 }
110
111 pub(super) fn unlock(file: &File) -> std::io::Result<()> {
112 file.unlock()
113 }
114}
115
116#[cfg(test)]
117#[path = "file_lock_tests.rs"]
118mod tests;