/rust/registry/src/index.crates.io-6f17d22bba15001f/same-file-1.0.6/src/lib.rs
Line | Count | Source (jump to first uncovered line) |
1 | | /*! |
2 | | This crate provides a safe and simple **cross platform** way to determine |
3 | | whether two file paths refer to the same file or directory. |
4 | | |
5 | | Most uses of this crate should be limited to the top-level [`is_same_file`] |
6 | | function, which takes two file paths and returns true if they refer to the |
7 | | same file or directory: |
8 | | |
9 | | ```rust,no_run |
10 | | # use std::error::Error; |
11 | | use same_file::is_same_file; |
12 | | |
13 | | # fn try_main() -> Result<(), Box<Error>> { |
14 | | assert!(is_same_file("/bin/sh", "/usr/bin/sh")?); |
15 | | # Ok(()) |
16 | | # } |
17 | | # |
18 | | # fn main() { |
19 | | # try_main().unwrap(); |
20 | | # } |
21 | | ``` |
22 | | |
23 | | Additionally, this crate provides a [`Handle`] type that permits a more efficient |
24 | | equality check depending on your access pattern. For example, if one wanted to |
25 | | check whether any path in a list of paths corresponded to the process' stdout |
26 | | handle, then one could build a handle once for stdout. The equality check for |
27 | | each file in the list then only requires one stat call instead of two. The code |
28 | | might look like this: |
29 | | |
30 | | ```rust,no_run |
31 | | # use std::error::Error; |
32 | | use same_file::Handle; |
33 | | |
34 | | # fn try_main() -> Result<(), Box<Error>> { |
35 | | let candidates = &[ |
36 | | "examples/is_same_file.rs", |
37 | | "examples/is_stderr.rs", |
38 | | "examples/stderr", |
39 | | ]; |
40 | | let stdout_handle = Handle::stdout()?; |
41 | | for candidate in candidates { |
42 | | let handle = Handle::from_path(candidate)?; |
43 | | if stdout_handle == handle { |
44 | | println!("{:?} is stdout!", candidate); |
45 | | } else { |
46 | | println!("{:?} is NOT stdout!", candidate); |
47 | | } |
48 | | } |
49 | | # Ok(()) |
50 | | # } |
51 | | # |
52 | | # fn main() { |
53 | | # try_main().unwrap(); |
54 | | # } |
55 | | ``` |
56 | | |
57 | | See [`examples/is_stderr.rs`] for a runnable example and compare the output of: |
58 | | |
59 | | - `cargo run --example is_stderr 2> examples/stderr` and |
60 | | - `cargo run --example is_stderr`. |
61 | | |
62 | | [`is_same_file`]: fn.is_same_file.html |
63 | | [`Handle`]: struct.Handle.html |
64 | | [`examples/is_stderr.rs`]: https://github.com/BurntSushi/same-file/blob/master/examples/is_same_file.rs |
65 | | |
66 | | */ |
67 | | |
68 | | #![allow(bare_trait_objects, unknown_lints)] |
69 | | #![deny(missing_docs)] |
70 | | |
71 | | #[cfg(test)] |
72 | | doc_comment::doctest!("../README.md"); |
73 | | |
74 | | use std::fs::File; |
75 | | use std::io; |
76 | | use std::path::Path; |
77 | | |
78 | | #[cfg(any(target_os = "redox", unix))] |
79 | | use crate::unix as imp; |
80 | | #[cfg(not(any(target_os = "redox", unix, windows)))] |
81 | | use unknown as imp; |
82 | | #[cfg(windows)] |
83 | | use win as imp; |
84 | | |
85 | | #[cfg(any(target_os = "redox", unix))] |
86 | | mod unix; |
87 | | #[cfg(not(any(target_os = "redox", unix, windows)))] |
88 | | mod unknown; |
89 | | #[cfg(windows)] |
90 | | mod win; |
91 | | |
92 | | /// A handle to a file that can be tested for equality with other handles. |
93 | | /// |
94 | | /// If two files are the same, then any two handles of those files will compare |
95 | | /// equal. If two files are not the same, then any two handles of those files |
96 | | /// will compare not-equal. |
97 | | /// |
98 | | /// A handle consumes an open file resource as long as it exists. |
99 | | /// |
100 | | /// Equality is determined by comparing inode numbers on Unix and a combination |
101 | | /// of identifier, volume serial, and file size on Windows. Note that it's |
102 | | /// possible for comparing two handles to produce a false positive on some |
103 | | /// platforms. Namely, two handles can compare equal even if the two handles |
104 | | /// *don't* point to the same file. Check the [source] for specific |
105 | | /// implementation details. |
106 | | /// |
107 | | /// [source]: https://github.com/BurntSushi/same-file/tree/master/src |
108 | | #[derive(Debug, Eq, PartialEq, Hash)] |
109 | | pub struct Handle(imp::Handle); |
110 | | |
111 | | impl Handle { |
112 | | /// Construct a handle from a path. |
113 | | /// |
114 | | /// Note that the underlying [`File`] is opened in read-only mode on all |
115 | | /// platforms. |
116 | | /// |
117 | | /// [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html |
118 | | /// |
119 | | /// # Errors |
120 | | /// This method will return an [`io::Error`] if the path cannot |
121 | | /// be opened, or the file's metadata cannot be obtained. |
122 | | /// The most common reasons for this are: the path does not |
123 | | /// exist, or there were not enough permissions. |
124 | | /// |
125 | | /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html |
126 | | /// |
127 | | /// # Examples |
128 | | /// Check that two paths are not the same file: |
129 | | /// |
130 | | /// ```rust,no_run |
131 | | /// # use std::error::Error; |
132 | | /// use same_file::Handle; |
133 | | /// |
134 | | /// # fn try_main() -> Result<(), Box<Error>> { |
135 | | /// let source = Handle::from_path("./source")?; |
136 | | /// let target = Handle::from_path("./target")?; |
137 | | /// assert_ne!(source, target, "The files are the same."); |
138 | | /// # Ok(()) |
139 | | /// # } |
140 | | /// # |
141 | | /// # fn main() { |
142 | | /// # try_main().unwrap(); |
143 | | /// # } |
144 | | /// ``` |
145 | 0 | pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> { |
146 | 0 | imp::Handle::from_path(p).map(Handle) |
147 | 0 | } Unexecuted instantiation: <same_file::Handle>::from_path::<&std::path::PathBuf> Unexecuted instantiation: <same_file::Handle>::from_path::<&&std::path::Path> Unexecuted instantiation: <same_file::Handle>::from_path::<_> |
148 | | |
149 | | /// Construct a handle from a file. |
150 | | /// |
151 | | /// # Errors |
152 | | /// This method will return an [`io::Error`] if the metadata for |
153 | | /// the given [`File`] cannot be obtained. |
154 | | /// |
155 | | /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html |
156 | | /// [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html |
157 | | /// |
158 | | /// # Examples |
159 | | /// Check that two files are not in fact the same file: |
160 | | /// |
161 | | /// ```rust,no_run |
162 | | /// # use std::error::Error; |
163 | | /// # use std::fs::File; |
164 | | /// use same_file::Handle; |
165 | | /// |
166 | | /// # fn try_main() -> Result<(), Box<Error>> { |
167 | | /// let source = File::open("./source")?; |
168 | | /// let target = File::open("./target")?; |
169 | | /// |
170 | | /// assert_ne!( |
171 | | /// Handle::from_file(source)?, |
172 | | /// Handle::from_file(target)?, |
173 | | /// "The files are the same." |
174 | | /// ); |
175 | | /// # Ok(()) |
176 | | /// # } |
177 | | /// # |
178 | | /// # fn main() { |
179 | | /// # try_main().unwrap(); |
180 | | /// # } |
181 | | /// ``` |
182 | 0 | pub fn from_file(file: File) -> io::Result<Handle> { |
183 | 0 | imp::Handle::from_file(file).map(Handle) |
184 | 0 | } |
185 | | |
186 | | /// Construct a handle from stdin. |
187 | | /// |
188 | | /// # Errors |
189 | | /// This method will return an [`io::Error`] if stdin cannot |
190 | | /// be opened due to any I/O-related reason. |
191 | | /// |
192 | | /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html |
193 | | /// |
194 | | /// # Examples |
195 | | /// |
196 | | /// ```rust |
197 | | /// # use std::error::Error; |
198 | | /// use same_file::Handle; |
199 | | /// |
200 | | /// # fn try_main() -> Result<(), Box<Error>> { |
201 | | /// let stdin = Handle::stdin()?; |
202 | | /// let stdout = Handle::stdout()?; |
203 | | /// let stderr = Handle::stderr()?; |
204 | | /// |
205 | | /// if stdin == stdout { |
206 | | /// println!("stdin == stdout"); |
207 | | /// } |
208 | | /// if stdin == stderr { |
209 | | /// println!("stdin == stderr"); |
210 | | /// } |
211 | | /// if stdout == stderr { |
212 | | /// println!("stdout == stderr"); |
213 | | /// } |
214 | | /// # |
215 | | /// # Ok(()) |
216 | | /// # } |
217 | | /// # |
218 | | /// # fn main() { |
219 | | /// # try_main().unwrap(); |
220 | | /// # } |
221 | | /// ``` |
222 | | /// |
223 | | /// The output differs depending on the platform. |
224 | | /// |
225 | | /// On Linux: |
226 | | /// |
227 | | /// ```text |
228 | | /// $ ./example |
229 | | /// stdin == stdout |
230 | | /// stdin == stderr |
231 | | /// stdout == stderr |
232 | | /// $ ./example > result |
233 | | /// $ cat result |
234 | | /// stdin == stderr |
235 | | /// $ ./example > result 2>&1 |
236 | | /// $ cat result |
237 | | /// stdout == stderr |
238 | | /// ``` |
239 | | /// |
240 | | /// Windows: |
241 | | /// |
242 | | /// ```text |
243 | | /// > example |
244 | | /// > example > result 2>&1 |
245 | | /// > type result |
246 | | /// stdout == stderr |
247 | | /// ``` |
248 | 0 | pub fn stdin() -> io::Result<Handle> { |
249 | 0 | imp::Handle::stdin().map(Handle) |
250 | 0 | } |
251 | | |
252 | | /// Construct a handle from stdout. |
253 | | /// |
254 | | /// # Errors |
255 | | /// This method will return an [`io::Error`] if stdout cannot |
256 | | /// be opened due to any I/O-related reason. |
257 | | /// |
258 | | /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html |
259 | | /// |
260 | | /// # Examples |
261 | | /// See the example for [`stdin()`]. |
262 | | /// |
263 | | /// [`stdin()`]: #method.stdin |
264 | 0 | pub fn stdout() -> io::Result<Handle> { |
265 | 0 | imp::Handle::stdout().map(Handle) |
266 | 0 | } |
267 | | |
268 | | /// Construct a handle from stderr. |
269 | | /// |
270 | | /// # Errors |
271 | | /// This method will return an [`io::Error`] if stderr cannot |
272 | | /// be opened due to any I/O-related reason. |
273 | | /// |
274 | | /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html |
275 | | /// |
276 | | /// # Examples |
277 | | /// See the example for [`stdin()`]. |
278 | | /// |
279 | | /// [`stdin()`]: #method.stdin |
280 | 0 | pub fn stderr() -> io::Result<Handle> { |
281 | 0 | imp::Handle::stderr().map(Handle) |
282 | 0 | } |
283 | | |
284 | | /// Return a reference to the underlying file. |
285 | | /// |
286 | | /// # Examples |
287 | | /// Ensure that the target file is not the same as the source one, |
288 | | /// and copy the data to it: |
289 | | /// |
290 | | /// ```rust,no_run |
291 | | /// # use std::error::Error; |
292 | | /// use std::io::prelude::*; |
293 | | /// use std::io::Write; |
294 | | /// use std::fs::File; |
295 | | /// use same_file::Handle; |
296 | | /// |
297 | | /// # fn try_main() -> Result<(), Box<Error>> { |
298 | | /// let source = File::open("source")?; |
299 | | /// let target = File::create("target")?; |
300 | | /// |
301 | | /// let source_handle = Handle::from_file(source)?; |
302 | | /// let mut target_handle = Handle::from_file(target)?; |
303 | | /// assert_ne!(source_handle, target_handle, "The files are the same."); |
304 | | /// |
305 | | /// let mut source = source_handle.as_file(); |
306 | | /// let target = target_handle.as_file_mut(); |
307 | | /// |
308 | | /// let mut buffer = Vec::new(); |
309 | | /// // data copy is simplified for the purposes of the example |
310 | | /// source.read_to_end(&mut buffer)?; |
311 | | /// target.write_all(&buffer)?; |
312 | | /// # |
313 | | /// # Ok(()) |
314 | | /// # } |
315 | | /// # |
316 | | /// # fn main() { |
317 | | /// # try_main().unwrap(); |
318 | | /// # } |
319 | | /// ``` |
320 | 0 | pub fn as_file(&self) -> &File { |
321 | 0 | self.0.as_file() |
322 | 0 | } |
323 | | |
324 | | /// Return a mutable reference to the underlying file. |
325 | | /// |
326 | | /// # Examples |
327 | | /// See the example for [`as_file()`]. |
328 | | /// |
329 | | /// [`as_file()`]: #method.as_file |
330 | 0 | pub fn as_file_mut(&mut self) -> &mut File { |
331 | 0 | self.0.as_file_mut() |
332 | 0 | } |
333 | | |
334 | | /// Return the underlying device number of this handle. |
335 | | /// |
336 | | /// Note that this only works on unix platforms. |
337 | | #[cfg(any(target_os = "redox", unix))] |
338 | 0 | pub fn dev(&self) -> u64 { |
339 | 0 | self.0.dev() |
340 | 0 | } |
341 | | |
342 | | /// Return the underlying inode number of this handle. |
343 | | /// |
344 | | /// Note that this only works on unix platforms. |
345 | | #[cfg(any(target_os = "redox", unix))] |
346 | 0 | pub fn ino(&self) -> u64 { |
347 | 0 | self.0.ino() |
348 | 0 | } |
349 | | } |
350 | | |
351 | | /// Returns true if the two file paths may correspond to the same file. |
352 | | /// |
353 | | /// Note that it's possible for this to produce a false positive on some |
354 | | /// platforms. Namely, this can return true even if the two file paths *don't* |
355 | | /// resolve to the same file. |
356 | | /// # Errors |
357 | | /// This function will return an [`io::Error`] if any of the two paths cannot |
358 | | /// be opened. The most common reasons for this are: the path does not exist, |
359 | | /// or there were not enough permissions. |
360 | | /// |
361 | | /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html |
362 | | /// |
363 | | /// # Example |
364 | | /// |
365 | | /// ```rust,no_run |
366 | | /// use same_file::is_same_file; |
367 | | /// |
368 | | /// assert!(is_same_file("./foo", "././foo").unwrap_or(false)); |
369 | | /// ``` |
370 | 0 | pub fn is_same_file<P, Q>(path1: P, path2: Q) -> io::Result<bool> |
371 | 0 | where |
372 | 0 | P: AsRef<Path>, |
373 | 0 | Q: AsRef<Path>, |
374 | 0 | { |
375 | 0 | Ok(Handle::from_path(path1)? == Handle::from_path(path2)?) |
376 | 0 | } |
377 | | |
378 | | #[cfg(test)] |
379 | | mod tests { |
380 | | use std::env; |
381 | | use std::error; |
382 | | use std::fs::{self, File}; |
383 | | use std::io; |
384 | | use std::path::{Path, PathBuf}; |
385 | | use std::result; |
386 | | |
387 | | use super::is_same_file; |
388 | | |
389 | | type Result<T> = result::Result<T, Box<error::Error + Send + Sync>>; |
390 | | |
391 | | /// Create an error from a format!-like syntax. |
392 | | macro_rules! err { |
393 | | ($($tt:tt)*) => { |
394 | | Box::<error::Error + Send + Sync>::from(format!($($tt)*)) |
395 | | } |
396 | | } |
397 | | |
398 | | /// A simple wrapper for creating a temporary directory that is |
399 | | /// automatically deleted when it's dropped. |
400 | | /// |
401 | | /// We use this in lieu of tempfile because tempfile brings in too many |
402 | | /// dependencies. |
403 | | #[derive(Debug)] |
404 | | struct TempDir(PathBuf); |
405 | | |
406 | | impl Drop for TempDir { |
407 | | fn drop(&mut self) { |
408 | | fs::remove_dir_all(&self.0).unwrap(); |
409 | | } |
410 | | } |
411 | | |
412 | | impl TempDir { |
413 | | /// Create a new empty temporary directory under the system's |
414 | | /// configured temporary directory. |
415 | | fn new() -> Result<TempDir> { |
416 | | #![allow(deprecated)] |
417 | | |
418 | | use std::sync::atomic::{ |
419 | | AtomicUsize, Ordering, ATOMIC_USIZE_INIT, |
420 | | }; |
421 | | |
422 | | static TRIES: usize = 100; |
423 | | static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; |
424 | | |
425 | | let tmpdir = env::temp_dir(); |
426 | | for _ in 0..TRIES { |
427 | | let count = COUNTER.fetch_add(1, Ordering::SeqCst); |
428 | | let path = tmpdir.join("rust-walkdir").join(count.to_string()); |
429 | | if path.is_dir() { |
430 | | continue; |
431 | | } |
432 | | fs::create_dir_all(&path).map_err(|e| { |
433 | | err!("failed to create {}: {}", path.display(), e) |
434 | | })?; |
435 | | return Ok(TempDir(path)); |
436 | | } |
437 | | Err(err!("failed to create temp dir after {} tries", TRIES)) |
438 | | } |
439 | | |
440 | | /// Return the underlying path to this temporary directory. |
441 | | fn path(&self) -> &Path { |
442 | | &self.0 |
443 | | } |
444 | | } |
445 | | |
446 | | fn tmpdir() -> TempDir { |
447 | | TempDir::new().unwrap() |
448 | | } |
449 | | |
450 | | #[cfg(unix)] |
451 | | pub fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>( |
452 | | src: P, |
453 | | dst: Q, |
454 | | ) -> io::Result<()> { |
455 | | use std::os::unix::fs::symlink; |
456 | | symlink(src, dst) |
457 | | } |
458 | | |
459 | | #[cfg(unix)] |
460 | | pub fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>( |
461 | | src: P, |
462 | | dst: Q, |
463 | | ) -> io::Result<()> { |
464 | | soft_link_dir(src, dst) |
465 | | } |
466 | | |
467 | | #[cfg(windows)] |
468 | | pub fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>( |
469 | | src: P, |
470 | | dst: Q, |
471 | | ) -> io::Result<()> { |
472 | | use std::os::windows::fs::symlink_dir; |
473 | | symlink_dir(src, dst) |
474 | | } |
475 | | |
476 | | #[cfg(windows)] |
477 | | pub fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>( |
478 | | src: P, |
479 | | dst: Q, |
480 | | ) -> io::Result<()> { |
481 | | use std::os::windows::fs::symlink_file; |
482 | | symlink_file(src, dst) |
483 | | } |
484 | | |
485 | | // These tests are rather uninteresting. The really interesting tests |
486 | | // would stress the edge cases. On Unix, this might be comparing two files |
487 | | // on different mount points with the same inode number. On Windows, this |
488 | | // might be comparing two files whose file indices are the same on file |
489 | | // systems where such things aren't guaranteed to be unique. |
490 | | // |
491 | | // Alas, I don't know how to create those environmental conditions. ---AG |
492 | | |
493 | | #[test] |
494 | | fn same_file_trivial() { |
495 | | let tdir = tmpdir(); |
496 | | let dir = tdir.path(); |
497 | | |
498 | | File::create(dir.join("a")).unwrap(); |
499 | | assert!(is_same_file(dir.join("a"), dir.join("a")).unwrap()); |
500 | | } |
501 | | |
502 | | #[test] |
503 | | fn same_dir_trivial() { |
504 | | let tdir = tmpdir(); |
505 | | let dir = tdir.path(); |
506 | | |
507 | | fs::create_dir(dir.join("a")).unwrap(); |
508 | | assert!(is_same_file(dir.join("a"), dir.join("a")).unwrap()); |
509 | | } |
510 | | |
511 | | #[test] |
512 | | fn not_same_file_trivial() { |
513 | | let tdir = tmpdir(); |
514 | | let dir = tdir.path(); |
515 | | |
516 | | File::create(dir.join("a")).unwrap(); |
517 | | File::create(dir.join("b")).unwrap(); |
518 | | assert!(!is_same_file(dir.join("a"), dir.join("b")).unwrap()); |
519 | | } |
520 | | |
521 | | #[test] |
522 | | fn not_same_dir_trivial() { |
523 | | let tdir = tmpdir(); |
524 | | let dir = tdir.path(); |
525 | | |
526 | | fs::create_dir(dir.join("a")).unwrap(); |
527 | | fs::create_dir(dir.join("b")).unwrap(); |
528 | | assert!(!is_same_file(dir.join("a"), dir.join("b")).unwrap()); |
529 | | } |
530 | | |
531 | | #[test] |
532 | | fn same_file_hard() { |
533 | | let tdir = tmpdir(); |
534 | | let dir = tdir.path(); |
535 | | |
536 | | File::create(dir.join("a")).unwrap(); |
537 | | fs::hard_link(dir.join("a"), dir.join("alink")).unwrap(); |
538 | | assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap()); |
539 | | } |
540 | | |
541 | | #[test] |
542 | | fn same_file_soft() { |
543 | | let tdir = tmpdir(); |
544 | | let dir = tdir.path(); |
545 | | |
546 | | File::create(dir.join("a")).unwrap(); |
547 | | soft_link_file(dir.join("a"), dir.join("alink")).unwrap(); |
548 | | assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap()); |
549 | | } |
550 | | |
551 | | #[test] |
552 | | fn same_dir_soft() { |
553 | | let tdir = tmpdir(); |
554 | | let dir = tdir.path(); |
555 | | |
556 | | fs::create_dir(dir.join("a")).unwrap(); |
557 | | soft_link_dir(dir.join("a"), dir.join("alink")).unwrap(); |
558 | | assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap()); |
559 | | } |
560 | | |
561 | | #[test] |
562 | | fn test_send() { |
563 | | fn assert_send<T: Send>() {} |
564 | | assert_send::<super::Handle>(); |
565 | | } |
566 | | |
567 | | #[test] |
568 | | fn test_sync() { |
569 | | fn assert_sync<T: Sync>() {} |
570 | | assert_sync::<super::Handle>(); |
571 | | } |
572 | | } |