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)]
8 extern crate winapi;
9
10 #[cfg(unix)]
11 mod unix;
12 #[cfg(unix)]
13 use unix as sys;
14
15 #[cfg(windows)]
16 mod windows;
17 #[cfg(windows)]
18 use windows as sys;
19
20 use std::fs::File;
21 use std::io::{Error, Result};
22 use 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.
50 pub 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.
duplicate(&self) -> Result<File>65 fn duplicate(&self) -> Result<File>;
66
67 /// Returns the amount of physical space allocated for a file.
allocated_size(&self) -> Result<u64>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.
allocate(&self, len: u64) -> Result<()>74 fn allocate(&self, len: u64) -> Result<()>;
75
76 /// Locks the file for shared usage, blocking if the file is currently
77 /// locked exclusively.
lock_shared(&self) -> Result<()>78 fn lock_shared(&self) -> Result<()>;
79
80 /// Locks the file for exclusive usage, blocking if the file is currently
81 /// locked.
lock_exclusive(&self) -> Result<()>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`).
try_lock_shared(&self) -> Result<()>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`).
try_lock_exclusive(&self) -> Result<()>90 fn try_lock_exclusive(&self) -> Result<()>;
91
92 /// Unlocks the file.
unlock(&self) -> Result<()>93 fn unlock(&self) -> Result<()>;
94 }
95
96 impl FileExt for File {
duplicate(&self) -> Result<File>97 fn duplicate(&self) -> Result<File> {
98 sys::duplicate(self)
99 }
allocated_size(&self) -> Result<u64>100 fn allocated_size(&self) -> Result<u64> {
101 sys::allocated_size(self)
102 }
allocate(&self, len: u64) -> Result<()>103 fn allocate(&self, len: u64) -> Result<()> {
104 sys::allocate(self, len)
105 }
lock_shared(&self) -> Result<()>106 fn lock_shared(&self) -> Result<()> {
107 sys::lock_shared(self)
108 }
lock_exclusive(&self) -> Result<()>109 fn lock_exclusive(&self) -> Result<()> {
110 sys::lock_exclusive(self)
111 }
try_lock_shared(&self) -> Result<()>112 fn try_lock_shared(&self) -> Result<()> {
113 sys::try_lock_shared(self)
114 }
try_lock_exclusive(&self) -> Result<()>115 fn try_lock_exclusive(&self) -> Result<()> {
116 sys::try_lock_exclusive(self)
117 }
unlock(&self) -> Result<()>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.
lock_contended_error() -> Error125 pub 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)]
131 pub struct FsStats {
132 free_space: u64,
133 available_space: u64,
134 total_space: u64,
135 allocation_granularity: u64,
136 }
137
138 impl FsStats {
139 /// Returns the number of free bytes in the file system containing the provided
140 /// path.
free_space(&self) -> u64141 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.
available_space(&self) -> u64147 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.
total_space(&self) -> u64153 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.
allocation_granularity(&self) -> u64162 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.
statvfs<P>(path: P) -> Result<FsStats> where P: AsRef<Path>168 pub 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.
free_space<P>(path: P) -> Result<u64> where P: AsRef<Path>174 pub 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.
available_space<P>(path: P) -> Result<u64> where P: AsRef<Path>180 pub 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.
total_space<P>(path: P) -> Result<u64> where P: AsRef<Path>186 pub 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.
allocation_granularity<P>(path: P) -> Result<u64> where P: AsRef<Path>195 pub 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)]
200 mod 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]
duplicate()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]
lock_shared()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]
lock_exclusive()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]
lock_cleanup()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]
allocate()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]
filesystem_space()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]
bench_file_create(b: &mut test::Bencher)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]
bench_file_truncate(b: &mut test::Bencher)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]
bench_file_allocate(b: &mut test::Bencher)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]
bench_allocated_size(b: &mut test::Bencher)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]
bench_duplicate(b: &mut test::Bencher)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]
bench_lock_unlock(b: &mut test::Bencher)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]
bench_free_space(b: &mut test::Bencher)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]
bench_available_space(b: &mut test::Bencher)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]
bench_total_space(b: &mut test::Bencher)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 }
459