mina_tree/ondisk/
lock.rs

1use std::{fs::File, path::Path};
2
3pub struct LockedFile {
4    file: File,
5}
6
7impl Drop for LockedFile {
8    fn drop(&mut self) {
9        let _ = sys::unlock(&self.file);
10    }
11}
12
13impl std::ops::Deref for LockedFile {
14    type Target = File;
15
16    fn deref(&self) -> &Self::Target {
17        &self.file
18    }
19}
20
21impl std::ops::DerefMut for LockedFile {
22    fn deref_mut(&mut self) -> &mut Self::Target {
23        &mut self.file
24    }
25}
26
27impl std::io::Write for LockedFile {
28    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
29        self.file.write(buf)
30    }
31
32    fn flush(&mut self) -> std::io::Result<()> {
33        self.file.flush()
34    }
35}
36
37impl std::io::Read for LockedFile {
38    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
39        self.file.read(buf)
40    }
41}
42
43impl std::io::Seek for LockedFile {
44    fn seek(&mut self, to: std::io::SeekFrom) -> std::io::Result<u64> {
45        self.file.seek(to)
46    }
47}
48
49impl LockedFile {
50    pub fn try_open_exclusively(
51        filename: &Path,
52        open_options: &std::fs::OpenOptions,
53    ) -> std::io::Result<Self> {
54        let file = open_options.open(filename)?;
55        sys::try_lock_exclusive(&file)?;
56
57        Ok(Self { file })
58    }
59}
60
61#[cfg(unix)]
62mod sys {
63    use std::{fs::File, os::unix::io::AsRawFd};
64
65    fn flock(file: &File, flag: libc::c_int) -> std::io::Result<()> {
66        let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
67
68        if ret < 0 {
69            let error = std::io::Error::last_os_error();
70
71            match error.kind() {
72                std::io::ErrorKind::Unsupported => Ok(()), // Succeed when `flock` is not supported
73                _ => Err(error),
74            }
75        } else {
76            Ok(())
77        }
78    }
79
80    pub(super) fn try_lock_exclusive(file: &File) -> std::io::Result<()> {
81        flock(file, libc::LOCK_EX | libc::LOCK_NB).map_err(|e| {
82            std::io::Error::new(
83                std::io::ErrorKind::WouldBlock,
84                format!("Unable to lock the file: {:?}", e),
85            )
86        })
87    }
88
89    pub(super) fn unlock(file: &File) -> std::io::Result<()> {
90        flock(file, libc::LOCK_UN).map_err(|e| {
91            std::io::Error::new(
92                std::io::ErrorKind::WouldBlock,
93                format!("Unable to unlock the file: {:?}", e),
94            )
95        })
96    }
97}
98
99#[cfg(windows)]
100mod sys {
101    use std::{fs::File, mem, os::windows::io::AsRawHandle};
102
103    use windows_sys::Win32::{
104        Foundation::HANDLE,
105        Storage::FileSystem::{
106            LockFileEx, UnlockFile, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
107        },
108    };
109
110    pub(super) fn try_lock_exclusive(file: &File) -> std::io::Result<()> {
111        let flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY;
112
113        let ret = unsafe {
114            let mut overlapped = mem::zeroed();
115            LockFileEx(
116                file.as_raw_handle() as HANDLE,
117                flags,
118                0,
119                !0,
120                !0,
121                &mut overlapped,
122            )
123        };
124
125        if ret == 0 {
126            let error = std::io::Error::last_os_error();
127
128            match error.kind() {
129                std::io::ErrorKind::Unsupported => Ok(()), // Succeed when `flock` is not supported
130                _ => Err(error),
131            }
132        } else {
133            Ok(())
134        }
135    }
136
137    pub(super) fn unlock(file: &File) -> Result<()> {
138        let ret = unsafe { UnlockFile(file.as_raw_handle() as HANDLE, 0, 0, !0, !0) };
139
140        if ret == 0 {
141            Err(std::io::Error::last_os_error())
142        } else {
143            Ok(())
144        }
145    }
146}
147
148#[cfg(not(any(unix, windows)))]
149mod sys {
150    use std::fs::File;
151
152    pub(super) fn try_lock_exclusive(_file: &File) -> std::io::Result<()> {
153        Ok(())
154    }
155
156    pub(super) fn unlock(_file: &File) -> std::io::Result<()> {
157        Ok(())
158    }
159}