1use std::{
6 fs::{File, TryLockError},
7 io::{self, IoSlice, IoSliceMut, SeekFrom},
8 ops,
9 path::Path,
10};
11
12use cfg_if::cfg_if;
13
14cfg_if! {
15 if #[cfg(any(target_os = "android", target_os = "illumos"))] {
16 use fs4::FileExt;
17
18 fn lock_exclusive(file: &File) -> io::Result<()> {
19 FileExt::lock(file)
20 }
21
22 fn lock_shared(file: &File) -> io::Result<()> {
23 FileExt::lock_shared(file)
24 }
25
26 fn map_try_lock_result(result: Result<(), fs4::TryLockError>) -> Result<(), TryLockError> {
27 match result {
28 Ok(()) => Ok(()),
29 Err(fs4::TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
30 Err(fs4::TryLockError::Error(e)) => Err(TryLockError::Error(e)),
31 }
32 }
33
34 fn try_lock_exclusive(file: &File) -> Result<(), TryLockError> {
35 map_try_lock_result(FileExt::try_lock(file))
36 }
37
38 fn try_lock_shared(file: &File) -> Result<(), TryLockError> {
39 map_try_lock_result(FileExt::try_lock_shared(file))
40 }
41
42 fn unlock(file: &File) -> io::Result<()> {
43 FileExt::unlock(file)
44 }
45 } else {
46 fn lock_exclusive(file: &File) -> io::Result<()> {
47 file.lock()
48 }
49
50 fn lock_shared(file: &File) -> io::Result<()> {
51 file.lock_shared()
52 }
53
54 fn try_lock_exclusive(file: &File) -> Result<(), TryLockError> {
55 file.try_lock()
56 }
57
58 fn try_lock_shared(file: &File) -> Result<(), TryLockError> {
59 file.try_lock_shared()
60 }
61
62 fn unlock(file: &File) -> io::Result<()> {
63 file.unlock()
64 }
65 }
66}
67
68#[derive(Debug)]
70pub struct FileLock(File, #[cfg(feature = "tracing")] Option<Box<Path>>);
71
72impl FileLock {
73 #[cfg(not(feature = "tracing"))]
74 fn new(file: File) -> Self {
75 Self(file)
76 }
77
78 #[cfg(feature = "tracing")]
79 fn new(file: File) -> Self {
80 Self(file, None)
81 }
82
83 pub fn new_exclusive(file: File) -> io::Result<Self> {
87 lock_exclusive(&file)?;
88
89 Ok(Self::new(file))
90 }
91
92 pub fn new_try_exclusive(file: File) -> Result<Self, (File, Option<io::Error>)> {
100 match try_lock_exclusive(&file) {
101 Ok(()) => Ok(Self::new(file)),
102 Err(TryLockError::WouldBlock) => Err((file, None)),
103 Err(TryLockError::Error(e)) => Err((file, Some(e))),
104 }
105 }
106
107 pub fn new_shared(file: File) -> io::Result<Self> {
111 lock_shared(&file)?;
112
113 Ok(Self::new(file))
114 }
115
116 pub fn new_try_shared(file: File) -> Result<Self, (File, Option<io::Error>)> {
124 match try_lock_shared(&file) {
125 Ok(()) => Ok(Self::new(file)),
126 Err(TryLockError::WouldBlock) => Err((file, None)),
127 Err(TryLockError::Error(e)) => Err((file, Some(e))),
128 }
129 }
130
131 pub fn set_file_path(mut self, path: impl Into<Box<Path>>) -> Self {
133 #[cfg(feature = "tracing")]
134 {
135 self.1 = Some(path.into());
136 }
137 self
138 }
139
140 pub fn get_file_path(&self) -> Option<&Path> {
141 cfg_if! {
142 if #[cfg(feature = "tracing")] {
143 self.1.as_deref()
144 } else {
145 None
146 }
147 }
148 }
149}
150
151impl Drop for FileLock {
152 fn drop(&mut self) {
153 let _res = unlock(&self.0);
154
155 #[cfg(feature = "tracing")]
156 if let Err(err) = _res {
157 use std::fmt;
158
159 struct OptionalPath<'a>(Option<&'a Path>);
160 impl fmt::Display for OptionalPath<'_> {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 if let Some(path) = self.0 {
163 fmt::Display::fmt(&path.display(), f)
164 } else {
165 Ok(())
166 }
167 }
168 }
169
170 tracing::warn!(
171 "Failed to unlock file{}: {err}",
172 OptionalPath(self.1.as_deref()),
173 );
174 }
175 }
176}
177
178impl ops::Deref for FileLock {
179 type Target = File;
180
181 fn deref(&self) -> &Self::Target {
182 &self.0
183 }
184}
185impl ops::DerefMut for FileLock {
186 fn deref_mut(&mut self) -> &mut Self::Target {
187 &mut self.0
188 }
189}
190
191impl io::Write for FileLock {
192 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
193 self.0.write(buf)
194 }
195 fn flush(&mut self) -> io::Result<()> {
196 self.0.flush()
197 }
198
199 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
200 self.0.write_vectored(bufs)
201 }
202}
203
204impl io::Write for &FileLock {
205 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
206 (&self.0).write(buf)
207 }
208 fn flush(&mut self) -> io::Result<()> {
209 (&self.0).flush()
210 }
211
212 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
213 (&self.0).write_vectored(bufs)
214 }
215}
216
217impl io::Read for FileLock {
218 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
219 self.0.read(buf)
220 }
221
222 fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
223 self.0.read_vectored(bufs)
224 }
225
226 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
227 self.0.read_to_end(buf)
228 }
229
230 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
231 self.0.read_to_string(buf)
232 }
233}
234
235impl io::Read for &FileLock {
236 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
237 (&self.0).read(buf)
238 }
239
240 fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
241 (&self.0).read_vectored(bufs)
242 }
243
244 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
245 (&self.0).read_to_end(buf)
246 }
247
248 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
249 (&self.0).read_to_string(buf)
250 }
251}
252
253impl io::Seek for FileLock {
254 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
255 self.0.seek(pos)
256 }
257
258 fn rewind(&mut self) -> io::Result<()> {
259 self.0.rewind()
260 }
261 fn stream_position(&mut self) -> io::Result<u64> {
262 self.0.stream_position()
263 }
264}
265
266impl io::Seek for &FileLock {
267 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
268 (&self.0).seek(pos)
269 }
270
271 fn rewind(&mut self) -> io::Result<()> {
272 (&self.0).rewind()
273 }
274 fn stream_position(&mut self) -> io::Result<u64> {
275 (&self.0).stream_position()
276 }
277}