1use std::fs;
9use std::fs::File;
10use std::io;
11use std::path::Path;
12use std::path::PathBuf;
13
14use fs2::FileExt;
15use memmap2::MmapMut;
16use memmap2::MmapOptions;
17
18use crate::change_detect::SharedChangeDetector;
19use crate::errors::IoResultExt;
20use crate::utils;
21
22pub struct ScopedFileLock<'a> {
24 file: &'a mut File,
25}
26
27impl<'a> ScopedFileLock<'a> {
28 pub fn new(file: &'a mut File, exclusive: bool) -> io::Result<Self> {
29 if exclusive {
30 file.lock_exclusive()?;
31 } else {
32 file.lock_shared()?;
33 }
34 Ok(ScopedFileLock { file })
35 }
36}
37
38impl<'a> AsRef<File> for ScopedFileLock<'a> {
39 fn as_ref(&self) -> &File {
40 self.file
41 }
42}
43
44impl<'a> AsMut<File> for ScopedFileLock<'a> {
45 fn as_mut(&mut self) -> &mut File {
46 self.file
47 }
48}
49
50impl<'a> Drop for ScopedFileLock<'a> {
51 fn drop(&mut self) {
52 self.file.unlock().expect("unlock");
53 }
54}
55
56pub struct ScopedDirLock {
58 file: File,
59 path: PathBuf,
60}
61
62pub struct DirLockOptions {
64 pub exclusive: bool,
65 pub non_blocking: bool,
66 pub file_name: &'static str,
67}
68
69pub(crate) static READER_LOCK_OPTS: DirLockOptions = DirLockOptions {
78 exclusive: false,
79 non_blocking: false,
80 file_name: "rlock",
87};
88
89impl ScopedDirLock {
90 pub fn new(path: &Path) -> crate::Result<Self> {
92 const DEFAULT_OPTIONS: DirLockOptions = DirLockOptions {
93 exclusive: true,
94 non_blocking: false,
95 file_name: "",
96 };
97 Self::new_with_options(path, &DEFAULT_OPTIONS)
98 }
99
100 pub fn new_with_options(dir: &Path, opts: &DirLockOptions) -> crate::Result<Self> {
110 let (path, file) = if opts.file_name.is_empty() {
111 let file = utils::open_dir(dir).context(dir, "cannot open for locking")?;
112 (dir.to_path_buf(), file)
113 } else {
114 let path = dir.join(opts.file_name);
115
116 #[allow(unused_mut)]
126 let mut file = match fs::OpenOptions::new().read(true).write(true).open(&path) {
127 Ok(f) => f,
128 Err(e) if e.kind() == io::ErrorKind::NotFound => {
129 utils::mkdir_p(dir)?;
131 fs::OpenOptions::new()
132 .read(true)
133 .write(true)
134 .create(true)
135 .open(&path)
136 .context(&path, "cannot create for locking")?
137 }
138 Err(e) => {
139 return Err(e).context(&path, "cannot open for locking");
140 }
141 };
142
143 #[cfg(unix)]
145 {
146 use std::fs::Permissions;
147 use std::os::unix::fs::PermissionsExt;
148 if let Ok(metadata) = file.metadata() {
149 let mode = metadata.permissions().mode();
150 let desired_mode = 0o666;
151 if (mode & desired_mode) != desired_mode {
152 let _ = file.set_permissions(Permissions::from_mode(mode | desired_mode));
153 }
154 }
155 }
156
157 (path, file)
158 };
159
160 lock_file(&file, opts.exclusive, opts.non_blocking).context(&path, || {
162 format!(
163 "cannot lock (exclusive: {}, non_blocking: {})",
164 opts.exclusive, opts.non_blocking,
165 )
166 })?;
167
168 let result = Self { file, path };
169 Ok(result)
170 }
171
172 pub fn path(&self) -> &Path {
174 &self.path
175 }
176
177 pub(crate) fn shared_mmap_mut(&self, len: usize) -> crate::Result<MmapMut> {
186 let metadata = self
187 .file
188 .metadata()
189 .context(&self.path, "cannot read metadata")?;
190 if len as u64 > metadata.len() {
191 self.file
192 .set_len(len as u64)
193 .context(&self.path, "cannot resize for mmap buffer")?;
194 }
195 unsafe { MmapOptions::new().len(len).map_mut(&self.file) }
196 .context(&self.path, "cannot mmap read-write")
197 }
198
199 pub(crate) fn shared_change_detector(&self) -> crate::Result<SharedChangeDetector> {
201 let mmap = self.shared_mmap_mut(std::mem::size_of::<u64>())?;
202 Ok(SharedChangeDetector::new(mmap))
203 }
204}
205
206impl Drop for ScopedDirLock {
207 fn drop(&mut self) {
208 let _ = unlock_file(&self.file);
209 }
210}
211
212fn lock_file(file: &File, exclusive: bool, non_blocking: bool) -> io::Result<()> {
213 #[cfg(windows)]
214 unsafe {
215 use std::os::windows::io::AsRawHandle;
216
217 use winapi::shared::minwindef::DWORD;
218 use winapi::um::fileapi::LockFileEx;
219 use winapi::um::minwinbase::LOCKFILE_EXCLUSIVE_LOCK;
220 use winapi::um::minwinbase::LOCKFILE_FAIL_IMMEDIATELY;
221 use winapi::um::minwinbase::OVERLAPPED;
222
223 let mut flags: DWORD = 0;
224 if exclusive {
225 flags |= LOCKFILE_EXCLUSIVE_LOCK;
226 }
227 if non_blocking {
228 flags |= LOCKFILE_FAIL_IMMEDIATELY;
229 }
230
231 let mut overlapped: OVERLAPPED = std::mem::zeroed();
233 overlapped.u.s_mut().Offset = u32::MAX - 1;
234 overlapped.u.s_mut().OffsetHigh = u32::MAX;
235
236 let ret = LockFileEx(file.as_raw_handle(), flags, 0, 1, 0, &mut overlapped);
238 if ret == 0 {
239 return Err(io::Error::last_os_error());
240 }
241 }
242 #[cfg(not(windows))]
243 match (exclusive, non_blocking) {
244 (true, false) => file.lock_exclusive()?,
245 (true, true) => file.try_lock_exclusive()?,
246 (false, false) => file.lock_shared()?,
247 (false, true) => file.try_lock_shared()?,
248 }
249 Ok(())
250}
251
252fn unlock_file(file: &File) -> io::Result<()> {
253 #[cfg(windows)]
254 unsafe {
255 use std::os::windows::io::AsRawHandle;
256
257 use winapi::um::fileapi::UnlockFile;
258
259 let ret = UnlockFile(file.as_raw_handle(), u32::MAX - 1, u32::MAX, 1, 0);
261 if ret == 0 {
262 return Err(io::Error::last_os_error());
263 }
264 }
265 #[cfg(not(windows))]
266 {
267 file.unlock()?;
268 }
269 Ok(())
270}
271
272#[cfg(test)]
273mod tests {
274 use std::fs::OpenOptions;
275 use std::io::Read;
276 use std::io::Seek;
277 use std::io::SeekFrom;
278 use std::io::Write;
279 use std::thread;
280
281 use tempfile::tempdir;
282
283 use super::*;
284
285 #[test]
286 fn test_file_lock() {
287 let dir = tempdir().unwrap();
288 let _file = OpenOptions::new()
289 .write(true)
290 .create(true)
291 .open(dir.path().join("f"))
292 .unwrap();
293
294 const N: usize = 40;
295
296 let threads: Vec<_> = (0..N)
298 .map(|i| {
299 let i = i;
300 let path = dir.path().join("f");
301 thread::spawn(move || {
302 let write = i % 2 == 0;
303 let mut file = OpenOptions::new()
304 .write(write)
305 .read(true)
306 .open(path)
307 .unwrap();
308 let mut lock = ScopedFileLock::new(&mut file, write).unwrap();
309 let len = lock.as_mut().seek(SeekFrom::End(0)).unwrap();
310 let ptr1 = lock.as_mut() as *const File;
311 let ptr2 = lock.as_ref() as *const File;
312 assert_eq!(ptr1, ptr2);
313 assert_eq!(len % 227, 0);
314 if write {
315 for j in 0..227 {
316 lock.as_mut().write_all(&[j]).expect("write");
317 lock.as_mut().flush().expect("flush");
318 }
319 }
320 })
321 })
322 .collect();
323
324 for thread in threads {
326 thread.join().expect("joined");
327 }
328
329 let mut file = OpenOptions::new()
331 .read(true)
332 .open(dir.path().join("f"))
333 .unwrap();
334 let mut buf = [0u8; 227];
335 let expected: Vec<u8> = (0..227).collect();
336 for _ in 0..(N / 2) {
337 file.read_exact(&mut buf).expect("read");
338 assert_eq!(&buf[..], &expected[..]);
339 }
340 }
341
342 #[test]
343 fn test_dir_lock() {
344 let dir = tempdir().unwrap();
345 let _file = OpenOptions::new()
346 .write(true)
347 .create(true)
348 .open(dir.path().join("f"))
349 .unwrap();
350
351 const N: usize = 40;
352
353 let threads: Vec<_> = (0..N)
355 .map(|i| {
356 let i = i;
357 let path = dir.path().join("f");
358 let dir_path = dir.path().to_path_buf();
359 thread::spawn(move || {
360 let write = i % 2 == 0;
361 let mut _lock = ScopedDirLock::new(&dir_path).unwrap();
362 let mut file = OpenOptions::new()
363 .write(write)
364 .read(true)
365 .open(path)
366 .unwrap();
367 let len = file.seek(SeekFrom::End(0)).unwrap();
368 assert_eq!(len % 227, 0);
369 if write {
370 for j in 0..227 {
371 file.write_all(&[j]).expect("write");
372 file.flush().expect("flush");
373 }
374 }
375 })
376 })
377 .collect();
378
379 for thread in threads {
381 thread.join().expect("joined");
382 }
383
384 let mut file = OpenOptions::new()
386 .read(true)
387 .open(dir.path().join("f"))
388 .unwrap();
389 let mut buf = [0u8; 227];
390 let expected: Vec<u8> = (0..227).collect();
391 for _ in 0..(N / 2) {
392 file.read_exact(&mut buf).expect("read");
393 assert_eq!(&buf[..], &expected[..]);
394 }
395 }
396
397 #[test]
398 fn test_dir_lock_with_options() {
399 let dir = tempdir().unwrap();
400 let path = dir.path();
401 let opts = DirLockOptions {
402 file_name: "foo",
403 exclusive: false,
404 non_blocking: false,
405 };
406
407 let l1 = ScopedDirLock::new_with_options(path, &opts).unwrap();
409 let l2 = ScopedDirLock::new_with_options(path, &opts).unwrap();
410
411 let opts = DirLockOptions {
412 non_blocking: true,
413 ..opts
414 };
415 let l3 = ScopedDirLock::new_with_options(path, &opts).unwrap();
416
417 let opts = DirLockOptions {
419 exclusive: true,
420 ..opts
421 };
422 assert!(ScopedDirLock::new_with_options(path, &opts).is_err());
423
424 drop((l1, l2, l3));
426 let l4 = ScopedDirLock::new_with_options(path, &opts).unwrap();
427
428 assert!(ScopedDirLock::new_with_options(path, &opts).is_err());
430
431 let opts = DirLockOptions {
433 file_name: "bar",
434 ..opts
435 };
436 assert!(ScopedDirLock::new_with_options(path, &opts).is_ok());
437
438 drop(l4);
439 }
440
441 #[test]
442 fn test_dir_lock_shared_buffer() {
443 let dir = tempdir().unwrap();
444 let path = dir.path();
445 let opts = DirLockOptions {
446 file_name: "foo",
447 exclusive: false,
448 non_blocking: false,
449 };
450
451 let mut v1 = &[1u8, 2, 3, 4, 5, 6, 7, 8][..];
452 let mut v2 = vec![0; v1.len()];
453
454 let l1 = ScopedDirLock::new_with_options(path, &opts).unwrap();
455 let mut buf1 = l1.shared_mmap_mut(v1.len()).unwrap();
456 buf1.as_mut().write_all(&v1).unwrap();
457
458 let l2 = ScopedDirLock::new_with_options(path, &opts).unwrap();
459 let buf2 = l2.shared_mmap_mut(v1.len()).unwrap();
460 buf2.as_ref().read_exact(&mut v2).unwrap();
461 assert_eq!(v1, v2);
462
463 drop((l1, l2));
465 v1 = &[99u8, 98, 97, 96, 95, 94, 93, 92][..];
466 buf1.as_mut().write_all(&v1).unwrap();
467 buf2.as_ref().read_exact(&mut v2).unwrap();
468 assert_eq!(v1, v2);
469
470 drop((buf1, buf2));
472 let l3 = ScopedDirLock::new_with_options(path, &opts).unwrap();
473 let buf3 = l3.shared_mmap_mut(v1.len()).unwrap();
474 buf3.as_ref().read_exact(&mut v2).unwrap();
475 assert_eq!(v1, v2);
476
477 let d1 = l3.shared_change_detector().unwrap();
479 let d2 = l3.shared_change_detector().unwrap();
480 let d3 = d2.clone();
481
482 assert!(!d1.is_changed());
483 assert!(!d2.is_changed());
484 assert!(!d3.is_changed());
485
486 d1.set(1);
487
488 assert!(!d1.is_changed());
489 assert!(d2.is_changed());
490 assert!(d3.is_changed());
491
492 d2.set(1);
493 assert!(!d2.is_changed());
494 assert!(d3.is_changed());
495
496 d3.set(2);
497 assert!(d1.is_changed());
498 assert!(d2.is_changed());
499 assert!(!d3.is_changed());
500
501 d2.set(3);
502 assert!(d1.is_changed());
503 assert!(!d2.is_changed());
504 assert!(d3.is_changed());
505 }
506}