fs4/file_ext/sync_impl.rs
1macro_rules! file_ext {
2 ($file:ty, $file_name:literal) => {
3 use std::io::Result;
4
5 #[doc = concat!("Extension trait for `", $file_name, "` which provides allocation, duplication and locking methods.")]
6 ///
7 /// ## Notes on File Locks
8 ///
9 /// This library provides whole-file locks in both shared (read) and exclusive
10 /// (read-write) varieties.
11 ///
12 /// File locks are a cross-platform hazard since the file lock APIs exposed by
13 /// operating system kernels vary in subtle and not-so-subtle ways.
14 ///
15 /// The API exposed by this library can be safely used across platforms as long
16 /// as the following rules are followed:
17 ///
18 /// * Multiple locks should not be created on an individual `File` instance
19 /// concurrently.
20 /// * Duplicated files should not be locked without great care.
21 /// * Files to be locked should be opened with at least read or write
22 /// permissions.
23 /// * File locks may only be relied upon to be advisory.
24 ///
25 /// See the tests in `lib.rs` for cross-platform lock behavior that may be
26 /// relied upon; see the tests in `unix.rs` and `windows.rs` for examples of
27 /// platform-specific behavior. File locks are implemented with
28 /// [`flock(2)`](http://man7.org/linux/man-pages/man2/flock.2.html) on Unix and
29 /// [`LockFile`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365202(v=vs.85).aspx)
30 /// on Windows.
31 pub trait FileExt {
32 /// Returns the amount of physical space allocated for a file.
33 fn allocated_size(&self) -> Result<u64>;
34
35 /// Ensures that at least `len` bytes of disk space are allocated for the
36 /// file, and the file size is at least `len` bytes. After a successful call
37 /// to `allocate`, subsequent writes to the file within the specified length
38 /// are guaranteed not to fail because of lack of disk space.
39 fn allocate(&self, len: u64) -> Result<()>;
40
41 /// Locks the file for shared usage, blocking if the file is currently
42 /// locked exclusively.
43 fn lock_shared(&self) -> Result<()>;
44
45 /// Locks the file for exclusive usage, blocking if the file is currently
46 /// locked.
47 fn lock_exclusive(&self) -> Result<()>;
48
49 /// Locks the file for shared usage, or returns an error if the file is
50 /// currently locked (see `lock_contended_error`).
51 fn try_lock_shared(&self) -> Result<()>;
52
53 /// Locks the file for exclusive usage, or returns an error if the file is
54 /// currently locked (see `lock_contended_error`).
55 fn try_lock_exclusive(&self) -> Result<()>;
56
57 /// Unlocks the file.
58 fn unlock(&self) -> Result<()>;
59 }
60
61 impl FileExt for $file {
62 fn allocated_size(&self) -> Result<u64> {
63 sys::allocated_size(self)
64 }
65 fn allocate(&self, len: u64) -> Result<()> {
66 sys::allocate(self, len)
67 }
68 fn lock_shared(&self) -> Result<()> {
69 sys::lock_shared(self)
70 }
71 fn lock_exclusive(&self) -> Result<()> {
72 sys::lock_exclusive(self)
73 }
74 fn try_lock_shared(&self) -> Result<()> {
75 sys::try_lock_shared(self)
76 }
77 fn try_lock_exclusive(&self) -> Result<()> {
78 sys::try_lock_exclusive(self)
79 }
80 fn unlock(&self) -> Result<()> {
81 sys::unlock(self)
82 }
83 }
84 }
85}
86
87macro_rules! test_mod {
88 ($($use_stmt:item)*) => {
89 #[cfg(test)]
90 mod test {
91 extern crate tempdir;
92 extern crate test;
93
94 use super::*;
95 use crate::{
96 allocation_granularity, available_space, free_space, lock_contended_error, statvfs,
97 total_space, FsStats,
98 };
99
100 $(
101 $use_stmt
102 )*
103
104 /// Tests shared file lock operations.
105 #[test]
106 fn lock_shared() {
107 let tempdir = tempdir::TempDir::new("fs4").unwrap();
108 let path = tempdir.path().join("fs4");
109 let file1 = fs::OpenOptions::new()
110 .read(true)
111 .write(true)
112 .create(true)
113 .truncate(true)
114 .open(&path)
115 .unwrap();
116 let file2 = fs::OpenOptions::new()
117 .read(true)
118 .write(true)
119 .create(true)
120 .truncate(true)
121 .open(&path)
122 .unwrap();
123 let file3 = fs::OpenOptions::new()
124 .read(true)
125 .write(true)
126 .create(true)
127 .truncate(true)
128 .open(&path)
129 .unwrap();
130
131 // Concurrent shared access is OK, but not shared and exclusive.
132 file1.lock_shared().unwrap();
133 file2.lock_shared().unwrap();
134 assert_eq!(
135 file3.try_lock_exclusive().unwrap_err().kind(),
136 lock_contended_error().kind()
137 );
138 file1.unlock().unwrap();
139 assert_eq!(
140 file3.try_lock_exclusive().unwrap_err().kind(),
141 lock_contended_error().kind()
142 );
143
144 // Once all shared file locks are dropped, an exclusive lock may be created;
145 file2.unlock().unwrap();
146 file3.lock_exclusive().unwrap();
147 }
148
149 /// Tests exclusive file lock operations.
150 #[test]
151 fn lock_exclusive() {
152 let tempdir = tempdir::TempDir::new("fs4").unwrap();
153 let path = tempdir.path().join("fs4");
154 let file1 = fs::OpenOptions::new()
155 .read(true)
156 .write(true)
157 .create(true)
158 .truncate(true)
159 .open(&path)
160 .unwrap();
161 let file2 = fs::OpenOptions::new()
162 .read(true)
163 .write(true)
164 .create(true)
165 .truncate(true)
166 .open(&path)
167 .unwrap();
168
169 // No other access is possible once an exclusive lock is created.
170 file1.lock_exclusive().unwrap();
171 assert_eq!(
172 file2.try_lock_exclusive().unwrap_err().kind(),
173 lock_contended_error().kind()
174 );
175 assert_eq!(
176 file2.try_lock_shared().unwrap_err().kind(),
177 lock_contended_error().kind()
178 );
179
180 // Once the exclusive lock is dropped, the second file is able to create a lock.
181 file1.unlock().unwrap();
182 file2.lock_exclusive().unwrap();
183 }
184
185 /// Tests that a lock is released after the file that owns it is dropped.
186 #[test]
187 fn lock_cleanup() {
188 let tempdir = tempdir::TempDir::new("fs4").unwrap();
189 let path = tempdir.path().join("fs4");
190 let file1 = fs::OpenOptions::new()
191 .read(true)
192 .write(true)
193 .create(true)
194 .truncate(true)
195 .open(&path)
196 .unwrap();
197 let file2 = fs::OpenOptions::new()
198 .read(true)
199 .write(true)
200 .create(true)
201 .truncate(true)
202 .open(&path)
203 .unwrap();
204
205 file1.lock_exclusive().unwrap();
206 assert_eq!(
207 file2.try_lock_shared().unwrap_err().kind(),
208 lock_contended_error().kind()
209 );
210
211 // Drop file1; the lock should be released.
212 drop(file1);
213 file2.lock_shared().unwrap();
214 }
215
216 /// Tests file allocation.
217 #[test]
218 fn allocate() {
219 let tempdir = tempdir::TempDir::new("fs4").unwrap();
220 let path = tempdir.path().join("fs4");
221 let file = fs::OpenOptions::new()
222 .write(true)
223 .create(true)
224 .truncate(true)
225 .open(&path)
226 .unwrap();
227 let blksize = allocation_granularity(&path).unwrap();
228
229 // New files are created with no allocated size.
230 assert_eq!(0, file.allocated_size().unwrap());
231 assert_eq!(0, file.metadata().unwrap().len());
232
233 // Allocate space for the file, checking that the allocated size steps
234 // up by block size, and the file length matches the allocated size.
235
236 file.allocate(2 * blksize - 1).unwrap();
237 assert_eq!(2 * blksize, file.allocated_size().unwrap());
238 assert_eq!(2 * blksize - 1, file.metadata().unwrap().len());
239
240 // Truncate the file, checking that the allocated size steps down by
241 // block size.
242
243 file.set_len(blksize + 1).unwrap();
244 assert_eq!(2 * blksize, file.allocated_size().unwrap());
245 assert_eq!(blksize + 1, file.metadata().unwrap().len());
246 }
247
248 /// Checks filesystem space methods.
249 #[test]
250 fn filesystem_space() {
251 let tempdir = tempdir::TempDir::new("fs4").unwrap();
252 let FsStats {
253 free_space,
254 available_space,
255 total_space,
256 ..
257 } = statvfs(tempdir.path()).unwrap();
258
259 assert!(total_space > free_space);
260 assert!(total_space > available_space);
261 assert!(available_space <= free_space);
262 }
263
264 /// Benchmarks creating and removing a file. This is a baseline benchmark
265 /// for comparing against the truncate and allocate benchmarks.
266 #[bench]
267 fn bench_file_create(b: &mut test::Bencher) {
268 let tempdir = tempdir::TempDir::new("fs4").unwrap();
269 let path = tempdir.path().join("file");
270
271 b.iter(|| {
272 fs::OpenOptions::new()
273 .read(true)
274 .write(true)
275 .create(true)
276 .truncate(true)
277 .open(&path)
278 .unwrap();
279 fs::remove_file(&path).unwrap();
280 });
281 }
282
283 /// Benchmarks creating a file, truncating it to 32MiB, and deleting it.
284 #[bench]
285 fn bench_file_truncate(b: &mut test::Bencher) {
286 let size = 32 * 1024 * 1024;
287 let tempdir = tempdir::TempDir::new("fs4").unwrap();
288 let path = tempdir.path().join("file");
289
290 b.iter(|| {
291 let file = fs::OpenOptions::new()
292 .read(true)
293 .write(true)
294 .create(true)
295 .truncate(true)
296 .open(&path)
297 .unwrap();
298 file.set_len(size).unwrap();
299 fs::remove_file(&path).unwrap();
300 });
301 }
302
303 /// Benchmarks creating a file, allocating 32MiB for it, and deleting it.
304 #[bench]
305 fn bench_file_allocate(b: &mut test::Bencher) {
306 let size = 32 * 1024 * 1024;
307 let tempdir = tempdir::TempDir::new("fs4").unwrap();
308 let path = tempdir.path().join("file");
309
310 b.iter(|| {
311 let file = fs::OpenOptions::new()
312 .read(true)
313 .write(true)
314 .create(true)
315 .truncate(true)
316 .open(&path)
317 .unwrap();
318 file.allocate(size).unwrap();
319 fs::remove_file(&path).unwrap();
320 });
321 }
322
323 /// Benchmarks creating a file, allocating 32MiB for it, and deleting it.
324 #[bench]
325 fn bench_allocated_size(b: &mut test::Bencher) {
326 let size = 32 * 1024 * 1024;
327 let tempdir = tempdir::TempDir::new("fs4").unwrap();
328 let path = tempdir.path().join("file");
329 let file = fs::OpenOptions::new()
330 .read(true)
331 .write(true)
332 .create(true)
333 .truncate(true)
334 .open(path)
335 .unwrap();
336 file.allocate(size).unwrap();
337
338 b.iter(|| {
339 file.allocated_size().unwrap();
340 });
341 }
342
343 /// Benchmarks locking and unlocking a file lock.
344 #[bench]
345 fn bench_lock_unlock(b: &mut test::Bencher) {
346 let tempdir = tempdir::TempDir::new("fs4").unwrap();
347 let path = tempdir.path().join("fs4");
348 let file = fs::OpenOptions::new()
349 .read(true)
350 .write(true)
351 .create(true)
352 .truncate(true)
353 .open(path)
354 .unwrap();
355
356 b.iter(|| {
357 file.lock_exclusive().unwrap();
358 file.unlock().unwrap();
359 });
360 }
361
362 /// Benchmarks the free space method.
363 #[bench]
364 fn bench_free_space(b: &mut test::Bencher) {
365 let tempdir = tempdir::TempDir::new("fs4").unwrap();
366 b.iter(|| {
367 test::black_box(free_space(tempdir.path()).unwrap());
368 });
369 }
370
371 /// Benchmarks the available space method.
372 #[bench]
373 fn bench_available_space(b: &mut test::Bencher) {
374 let tempdir = tempdir::TempDir::new("fs4").unwrap();
375 b.iter(|| {
376 test::black_box(available_space(tempdir.path()).unwrap());
377 });
378 }
379
380 /// Benchmarks the total space method.
381 #[bench]
382 fn bench_total_space(b: &mut test::Bencher) {
383 let tempdir = tempdir::TempDir::new("fs4").unwrap();
384 b.iter(|| {
385 test::black_box(total_space(tempdir.path()).unwrap());
386 });
387 }
388 }
389 };
390}
391
392cfg_sync! {
393 pub(crate) mod std_impl;
394}
395
396cfg_fs2_err! {
397 pub(crate) mod fs_err2_impl;
398}
399
400cfg_fs3_err! {
401 pub(crate) mod fs_err3_impl;
402}