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