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.
50 ///
51 /// Returns `Ok(true)` if the lock was acquired, `Ok(false)` if the file is currently
52 /// locked.
53 fn try_lock_shared(&self) -> Result<bool>;
54
55 /// Locks the file for exclusive usage.
56 ///
57 /// Returns `Ok(true)` if the lock was acquired, `Ok(false)` if the file is currently
58 /// locked.
59 fn try_lock_exclusive(&self) -> Result<bool>;
60
61 /// Unlocks the file.
62 fn unlock(&self) -> Result<()>;
63 }
64
65 impl FileExt for $file {
66 fn allocated_size(&self) -> Result<u64> {
67 sys::allocated_size(self)
68 }
69 fn allocate(&self, len: u64) -> Result<()> {
70 sys::allocate(self, len)
71 }
72 fn lock_shared(&self) -> Result<()> {
73 sys::lock_shared(self)
74 }
75 fn lock_exclusive(&self) -> Result<()> {
76 sys::lock_exclusive(self)
77 }
78 fn try_lock_shared(&self) -> Result<bool> {
79 sys::try_lock_shared(self)
80 }
81 fn try_lock_exclusive(&self) -> Result<bool> {
82 sys::try_lock_exclusive(self)
83 }
84 fn unlock(&self) -> Result<()> {
85 sys::unlock(self)
86 }
87 }
88 }
89}
90
91macro_rules! test_mod {
92 ($($use_stmt:item)*) => {
93 #[cfg(test)]
94 mod test {
95 extern crate tempfile;
96 extern crate test;
97
98 use super::*;
99 use crate::{
100 allocation_granularity, available_space, free_space, statvfs,
101 total_space, FsStats,
102 };
103
104 $(
105 $use_stmt
106 )*
107
108 /// Tests shared file lock operations.
109 #[test]
110 fn lock_shared() {
111 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
112 let path = tempdir.path().join("fs4");
113 let file1 = fs::OpenOptions::new()
114 .read(true)
115 .write(true)
116 .create(true)
117 .truncate(true)
118 .open(&path)
119 .unwrap();
120 let file2 = fs::OpenOptions::new()
121 .read(true)
122 .write(true)
123 .create(true)
124 .truncate(true)
125 .open(&path)
126 .unwrap();
127 let file3 = fs::OpenOptions::new()
128 .read(true)
129 .write(true)
130 .create(true)
131 .truncate(true)
132 .open(&path)
133 .unwrap();
134
135 // Concurrent shared access is OK, but not shared and exclusive.
136 file1.lock_shared().unwrap();
137 file2.lock_shared().unwrap();
138 assert_eq!(
139 file3.try_lock_exclusive().unwrap(),
140 false,
141 );
142 file1.unlock().unwrap();
143 assert_eq!(
144 file3.try_lock_exclusive().unwrap(),
145 false,
146 );
147
148 // Once all shared file locks are dropped, an exclusive lock may be created;
149 file2.unlock().unwrap();
150 file3.lock_exclusive().unwrap();
151 }
152
153 /// Tests exclusive file lock operations.
154 #[test]
155 fn lock_exclusive() {
156 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
157 let path = tempdir.path().join("fs4");
158 let file1 = fs::OpenOptions::new()
159 .read(true)
160 .write(true)
161 .create(true)
162 .truncate(true)
163 .open(&path)
164 .unwrap();
165 let file2 = fs::OpenOptions::new()
166 .read(true)
167 .write(true)
168 .create(true)
169 .truncate(true)
170 .open(&path)
171 .unwrap();
172
173 // No other access is possible once an exclusive lock is created.
174 file1.lock_exclusive().unwrap();
175 assert_eq!(
176 file2.try_lock_exclusive().unwrap(),
177 false,
178 );
179 assert_eq!(
180 file2.try_lock_shared().unwrap(),
181 false,
182 );
183
184 // Once the exclusive lock is dropped, the second file is able to create a lock.
185 file1.unlock().unwrap();
186 file2.lock_exclusive().unwrap();
187 }
188
189 /// Tests that a lock is released after the file that owns it is dropped.
190 #[test]
191 fn lock_cleanup() {
192 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
193 let path = tempdir.path().join("fs4");
194 let file1 = fs::OpenOptions::new()
195 .read(true)
196 .write(true)
197 .create(true)
198 .truncate(true)
199 .open(&path)
200 .unwrap();
201 let file2 = fs::OpenOptions::new()
202 .read(true)
203 .write(true)
204 .create(true)
205 .truncate(true)
206 .open(&path)
207 .unwrap();
208
209 file1.lock_exclusive().unwrap();
210 assert_eq!(
211 file2.try_lock_shared().unwrap(),
212 false,
213 );
214
215 // Drop file1; the lock should be released.
216 drop(file1);
217 file2.lock_shared().unwrap();
218 }
219
220 /// Tests file allocation.
221 #[test]
222 fn allocate() {
223 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
224 let path = tempdir.path().join("fs4");
225 let file = fs::OpenOptions::new()
226 .write(true)
227 .create(true)
228 .truncate(true)
229 .open(&path)
230 .unwrap();
231 let blksize = allocation_granularity(&path).unwrap();
232
233 // New files are created with no allocated size.
234 assert_eq!(0, file.allocated_size().unwrap());
235 assert_eq!(0, file.metadata().unwrap().len());
236
237 // Allocate space for the file, checking that the allocated size steps
238 // up by block size, and the file length matches the allocated size.
239
240 file.allocate(2 * blksize - 1).unwrap();
241 assert_eq!(2 * blksize, file.allocated_size().unwrap());
242 assert_eq!(2 * blksize - 1, file.metadata().unwrap().len());
243
244 // Truncate the file, checking that the allocated size steps down by
245 // block size.
246
247 file.set_len(blksize + 1).unwrap();
248 assert_eq!(2 * blksize, file.allocated_size().unwrap());
249 assert_eq!(blksize + 1, file.metadata().unwrap().len());
250 }
251
252 /// Checks filesystem space methods.
253 #[test]
254 fn filesystem_space() {
255 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
256 let FsStats {
257 free_space,
258 available_space,
259 total_space,
260 ..
261 } = statvfs(tempdir.path()).unwrap();
262
263 assert!(total_space > free_space);
264 assert!(total_space > available_space);
265 assert!(available_space <= free_space);
266 }
267
268 /// Benchmarks creating and removing a file. This is a baseline benchmark
269 /// for comparing against the truncate and allocate benchmarks.
270 #[bench]
271 fn bench_file_create(b: &mut test::Bencher) {
272 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
273 let path = tempdir.path().join("file");
274
275 b.iter(|| {
276 fs::OpenOptions::new()
277 .read(true)
278 .write(true)
279 .create(true)
280 .truncate(true)
281 .open(&path)
282 .unwrap();
283 fs::remove_file(&path).unwrap();
284 });
285 }
286
287 /// Benchmarks creating a file, truncating it to 32MiB, and deleting it.
288 #[bench]
289 fn bench_file_truncate(b: &mut test::Bencher) {
290 let size = 32 * 1024 * 1024;
291 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
292 let path = tempdir.path().join("file");
293
294 b.iter(|| {
295 let file = fs::OpenOptions::new()
296 .read(true)
297 .write(true)
298 .create(true)
299 .truncate(true)
300 .open(&path)
301 .unwrap();
302 file.set_len(size).unwrap();
303 fs::remove_file(&path).unwrap();
304 });
305 }
306
307 /// Benchmarks creating a file, allocating 32MiB for it, and deleting it.
308 #[bench]
309 fn bench_file_allocate(b: &mut test::Bencher) {
310 let size = 32 * 1024 * 1024;
311 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
312 let path = tempdir.path().join("file");
313
314 b.iter(|| {
315 let file = fs::OpenOptions::new()
316 .read(true)
317 .write(true)
318 .create(true)
319 .truncate(true)
320 .open(&path)
321 .unwrap();
322 file.allocate(size).unwrap();
323 fs::remove_file(&path).unwrap();
324 });
325 }
326
327 /// Benchmarks creating a file, allocating 32MiB for it, and deleting it.
328 #[bench]
329 fn bench_allocated_size(b: &mut test::Bencher) {
330 let size = 32 * 1024 * 1024;
331 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
332 let path = tempdir.path().join("file");
333 let file = fs::OpenOptions::new()
334 .read(true)
335 .write(true)
336 .create(true)
337 .truncate(true)
338 .open(path)
339 .unwrap();
340 file.allocate(size).unwrap();
341
342 b.iter(|| {
343 file.allocated_size().unwrap();
344 });
345 }
346
347 /// Benchmarks locking and unlocking a file lock.
348 #[bench]
349 fn bench_lock_unlock(b: &mut test::Bencher) {
350 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
351 let path = tempdir.path().join("fs4");
352 let file = fs::OpenOptions::new()
353 .read(true)
354 .write(true)
355 .create(true)
356 .truncate(true)
357 .open(path)
358 .unwrap();
359
360 b.iter(|| {
361 file.lock_exclusive().unwrap();
362 file.unlock().unwrap();
363 });
364 }
365
366 /// Benchmarks the free space method.
367 #[bench]
368 fn bench_free_space(b: &mut test::Bencher) {
369 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
370 b.iter(|| {
371 test::black_box(free_space(tempdir.path()).unwrap());
372 });
373 }
374
375 /// Benchmarks the available space method.
376 #[bench]
377 fn bench_available_space(b: &mut test::Bencher) {
378 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
379 b.iter(|| {
380 test::black_box(available_space(tempdir.path()).unwrap());
381 });
382 }
383
384 /// Benchmarks the total space method.
385 #[bench]
386 fn bench_total_space(b: &mut test::Bencher) {
387 let tempdir = tempfile::TempDir::with_prefix("fs4").unwrap();
388 b.iter(|| {
389 test::black_box(total_space(tempdir.path()).unwrap());
390 });
391 }
392 }
393 };
394}
395
396cfg_sync! {
397 pub(crate) mod std_impl;
398}
399
400cfg_fs2_err! {
401 pub(crate) mod fs_err2_impl;
402}
403
404cfg_fs3_err! {
405 pub(crate) mod fs_err3_impl;
406}