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