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::fs_std::FileExt;
17
18 fn lock_exclusive(file: &File) -> io::Result<()> {
19 FileExt::lock_exclusive(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: io::Result<bool>) -> Result<(), TryLockError> {
27 match result {
28 Ok(true) => Ok(()),
29 Ok(false) => Err(TryLockError::WouldBlock),
30 Err(e) if e.raw_os_error() == fs4::lock_contended_error().raw_os_error() => {
31 Err(TryLockError::WouldBlock)
32 }
33 Err(e) => Err(TryLockError::Error(e)),
34 }
35 }
36
37 fn try_lock_exclusive(file: &File) -> Result<(), TryLockError> {
38 map_try_lock_result(FileExt::try_lock_exclusive(file))
39 }
40
41 fn try_lock_shared(file: &File) -> Result<(), TryLockError> {
42 map_try_lock_result(FileExt::try_lock_shared(file))
43 }
44
45 fn unlock(file: &File) -> io::Result<()> {
46 FileExt::unlock(file)
47 }
48 } else {
49 fn lock_exclusive(file: &File) -> io::Result<()> {
50 file.lock()
51 }
52
53 fn lock_shared(file: &File) -> io::Result<()> {
54 file.lock_shared()
55 }
56
57 fn try_lock_exclusive(file: &File) -> Result<(), TryLockError> {
58 file.try_lock()
59 }
60
61 fn try_lock_shared(file: &File) -> Result<(), TryLockError> {
62 file.try_lock_shared()
63 }
64
65 fn unlock(file: &File) -> io::Result<()> {
66 file.unlock()
67 }
68 }
69}
70
71#[derive(Debug)]
73pub struct FileLock(File, #[cfg(feature = "tracing")] Option<Box<Path>>);
74
75impl FileLock {
76 #[cfg(not(feature = "tracing"))]
77 fn new(file: File) -> Self {
78 Self(file)
79 }
80
81 #[cfg(feature = "tracing")]
82 fn new(file: File) -> Self {
83 Self(file, None)
84 }
85
86 pub fn new_exclusive(file: File) -> io::Result<Self> {
90 lock_exclusive(&file)?;
91
92 Ok(Self::new(file))
93 }
94
95 pub fn new_try_exclusive(file: File) -> Result<Self, (File, Option<io::Error>)> {
103 match try_lock_exclusive(&file) {
104 Ok(()) => Ok(Self::new(file)),
105 Err(TryLockError::WouldBlock) => Err((file, None)),
106 Err(TryLockError::Error(e)) => Err((file, Some(e))),
107 }
108 }
109
110 pub fn new_shared(file: File) -> io::Result<Self> {
114 lock_shared(&file)?;
115
116 Ok(Self::new(file))
117 }
118
119 pub fn new_try_shared(file: File) -> Result<Self, (File, Option<io::Error>)> {
127 match try_lock_shared(&file) {
128 Ok(()) => Ok(Self::new(file)),
129 Err(TryLockError::WouldBlock) => Err((file, None)),
130 Err(TryLockError::Error(e)) => Err((file, Some(e))),
131 }
132 }
133
134 pub fn set_file_path(mut self, path: impl Into<Box<Path>>) -> Self {
136 #[cfg(feature = "tracing")]
137 {
138 self.1 = Some(path.into());
139 }
140 self
141 }
142}
143
144impl Drop for FileLock {
145 fn drop(&mut self) {
146 let _res = unlock(&self.0);
147
148 #[cfg(feature = "tracing")]
149 if let Err(err) = _res {
150 use std::fmt;
151
152 struct OptionalPath<'a>(Option<&'a Path>);
153 impl fmt::Display for OptionalPath<'_> {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 if let Some(path) = self.0 {
156 fmt::Display::fmt(&path.display(), f)
157 } else {
158 Ok(())
159 }
160 }
161 }
162
163 tracing::warn!(
164 "Failed to unlock file{}: {err}",
165 OptionalPath(self.1.as_deref()),
166 );
167 }
168 }
169}
170
171impl ops::Deref for FileLock {
172 type Target = File;
173
174 fn deref(&self) -> &Self::Target {
175 &self.0
176 }
177}
178impl ops::DerefMut for FileLock {
179 fn deref_mut(&mut self) -> &mut Self::Target {
180 &mut self.0
181 }
182}
183
184impl io::Write for FileLock {
185 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
186 self.0.write(buf)
187 }
188 fn flush(&mut self) -> io::Result<()> {
189 self.0.flush()
190 }
191
192 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
193 self.0.write_vectored(bufs)
194 }
195}
196
197impl io::Read for FileLock {
198 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
199 self.0.read(buf)
200 }
201
202 fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
203 self.0.read_vectored(bufs)
204 }
205}
206
207impl io::Seek for FileLock {
208 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
209 self.0.seek(pos)
210 }
211
212 fn rewind(&mut self) -> io::Result<()> {
213 self.0.rewind()
214 }
215 fn stream_position(&mut self) -> io::Result<u64> {
216 self.0.stream_position()
217 }
218}