tiverse_mmap/
file_lock.rs1use crate::{MmapError, Result};
4use std::fs::File;
5use std::os::unix::io::AsRawFd;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum LockType {
10 Shared,
12 Exclusive,
14}
15
16pub struct FileLock {
18 file: File,
19 lock_type: LockType,
20}
21
22impl FileLock {
23 #[cfg(unix)]
45 pub fn lock(file: File, lock_type: LockType) -> Result<Self> {
46 let fd = file.as_raw_fd();
47
48 let operation = match lock_type {
49 LockType::Shared => libc::LOCK_SH,
50 LockType::Exclusive => libc::LOCK_EX,
51 };
52
53 let result = unsafe { libc::flock(fd, operation) };
55
56 if result != 0 {
57 let err = std::io::Error::last_os_error();
58 return Err(MmapError::SystemError(format!(
59 "Failed to acquire file lock: {}",
60 err
61 )));
62 }
63
64 Ok(Self { file, lock_type })
65 }
66
67 #[cfg(unix)]
89 pub fn try_lock(file: File, lock_type: LockType) -> Result<Self> {
90 let fd = file.as_raw_fd();
91
92 let operation = match lock_type {
93 LockType::Shared => libc::LOCK_SH | libc::LOCK_NB,
94 LockType::Exclusive => libc::LOCK_EX | libc::LOCK_NB,
95 };
96
97 let result = unsafe { libc::flock(fd, operation) };
99
100 if result != 0 {
101 let err = std::io::Error::last_os_error();
102 return Err(MmapError::SystemError(format!(
103 "Failed to acquire file lock (would block): {}",
104 err
105 )));
106 }
107
108 Ok(Self { file, lock_type })
109 }
110
111 #[cfg(windows)]
113 pub fn lock(file: File, lock_type: LockType) -> Result<Self> {
114 use std::os::windows::io::AsRawHandle;
115 use windows::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
116 use windows::Win32::Storage::FileSystem::{LockFileEx, LOCKFILE_EXCLUSIVE_LOCK};
117
118 let handle = HANDLE(file.as_raw_handle() as isize);
119 if handle == INVALID_HANDLE_VALUE {
120 return Err(MmapError::SystemError("Invalid file handle".to_string()));
121 }
122
123 let flags = match lock_type {
124 LockType::Shared => 0,
125 LockType::Exclusive => LOCKFILE_EXCLUSIVE_LOCK.0,
126 };
127
128 let mut overlapped = unsafe { std::mem::zeroed() };
129
130 let result = unsafe { LockFileEx(handle, flags, 0, u32::MAX, u32::MAX, &mut overlapped) };
131
132 if result.is_err() {
133 return Err(MmapError::SystemError(
134 "Failed to acquire file lock".to_string(),
135 ));
136 }
137
138 Ok(Self { file, lock_type })
139 }
140
141 #[cfg(windows)]
143 pub fn try_lock(file: File, lock_type: LockType) -> Result<Self> {
144 use std::os::windows::io::AsRawHandle;
145 use windows::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
146 use windows::Win32::Storage::FileSystem::{
147 LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
148 };
149
150 let handle = HANDLE(file.as_raw_handle() as isize);
151 if handle == INVALID_HANDLE_VALUE {
152 return Err(MmapError::SystemError("Invalid file handle".to_string()));
153 }
154
155 let flags = match lock_type {
156 LockType::Shared => LOCKFILE_FAIL_IMMEDIATELY.0,
157 LockType::Exclusive => LOCKFILE_EXCLUSIVE_LOCK.0 | LOCKFILE_FAIL_IMMEDIATELY.0,
158 };
159
160 let mut overlapped = unsafe { std::mem::zeroed() };
161
162 let result = unsafe { LockFileEx(handle, flags, 0, u32::MAX, u32::MAX, &mut overlapped) };
163
164 if result.is_err() {
165 return Err(MmapError::SystemError(
166 "Failed to acquire file lock (would block)".to_string(),
167 ));
168 }
169
170 Ok(Self { file, lock_type })
171 }
172
173 pub fn file(&self) -> &File {
175 &self.file
176 }
177
178 pub fn lock_type(&self) -> LockType {
180 self.lock_type
181 }
182
183 pub fn unlock(self) -> File {
185 #[cfg(unix)]
187 {
188 let fd = self.file.as_raw_fd();
189 unsafe {
190 let _ = libc::flock(fd, libc::LOCK_UN);
191 }
192 }
193
194 #[cfg(windows)]
195 {
196 use std::os::windows::io::AsRawHandle;
197 use windows::Win32::Foundation::HANDLE;
198 use windows::Win32::Storage::FileSystem::UnlockFileEx;
199
200 let handle = HANDLE(self.file.as_raw_handle() as isize);
201 let mut overlapped = unsafe { std::mem::zeroed() };
202
203 unsafe {
204 let _ = UnlockFileEx(handle, 0, u32::MAX, u32::MAX, &mut overlapped);
205 }
206 }
207
208 let file = unsafe { std::ptr::read(&self.file) };
210 std::mem::forget(self);
211 file
212 }
213}
214
215impl Drop for FileLock {
216 fn drop(&mut self) {
217 #[cfg(unix)]
218 {
219 let fd = self.file.as_raw_fd();
220 unsafe {
222 let _ = libc::flock(fd, libc::LOCK_UN);
223 }
224 }
225
226 #[cfg(windows)]
227 {
228 use std::os::windows::io::AsRawHandle;
229 use windows::Win32::Foundation::HANDLE;
230 use windows::Win32::Storage::FileSystem::UnlockFileEx;
231
232 let handle = HANDLE(self.file.as_raw_handle() as isize);
233 let mut overlapped = unsafe { std::mem::zeroed() };
234
235 unsafe {
236 let _ = UnlockFileEx(handle, 0, u32::MAX, u32::MAX, &mut overlapped);
237 }
238 }
239 }
240}
241
242unsafe impl Send for FileLock {}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use std::io::Write;
249 use tempfile::NamedTempFile;
250
251 fn create_test_file() -> NamedTempFile {
252 let mut file = NamedTempFile::new().unwrap();
253 file.write_all(b"test data").unwrap();
254 file.flush().unwrap();
255 file
256 }
257
258 #[test]
259 fn test_shared_lock() {
260 let file = create_test_file();
261 let file_handle = std::fs::File::open(file.path()).unwrap();
262
263 let lock = FileLock::lock(file_handle, LockType::Shared).unwrap();
264 assert_eq!(lock.lock_type(), LockType::Shared);
265 }
266
267 #[test]
268 fn test_exclusive_lock() {
269 let file = create_test_file();
270 let file_handle = std::fs::File::open(file.path()).unwrap();
271
272 let lock = FileLock::lock(file_handle, LockType::Exclusive).unwrap();
273 assert_eq!(lock.lock_type(), LockType::Exclusive);
274 }
275
276 #[test]
277 fn test_try_lock_success() {
278 let file = create_test_file();
279 let file_handle = std::fs::File::open(file.path()).unwrap();
280
281 let lock = FileLock::try_lock(file_handle, LockType::Shared).unwrap();
282 assert_eq!(lock.lock_type(), LockType::Shared);
283 }
284
285 #[test]
286 fn test_lock_unlock() {
287 let file = create_test_file();
288 let file_handle = std::fs::File::open(file.path()).unwrap();
289
290 let lock = FileLock::lock(file_handle, LockType::Exclusive).unwrap();
291 let _file = lock.unlock();
292 }
294
295 #[test]
296 fn test_multiple_shared_locks() {
297 let file = create_test_file();
298 let path = file.path().to_path_buf();
299
300 let file1 = std::fs::File::open(&path).unwrap();
301 let file2 = std::fs::File::open(&path).unwrap();
302
303 let _lock1 = FileLock::lock(file1, LockType::Shared).unwrap();
304 let _lock2 = FileLock::lock(file2, LockType::Shared).unwrap();
305 }
307}