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}