[go: up one dir, main page]

fs2/
lib.rs

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