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 | pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> { |
146 | imp::Handle::from_path(p).map(Handle) |
147 | } |
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 | pub fn from_file(file: File) -> io::Result<Handle> { |
183 | imp::Handle::from_file(file).map(Handle) |
184 | } |
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 | pub fn stdin() -> io::Result<Handle> { |
249 | imp::Handle::stdin().map(Handle) |
250 | } |
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 | pub fn stdout() -> io::Result<Handle> { |
265 | imp::Handle::stdout().map(Handle) |
266 | } |
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 | pub fn stderr() -> io::Result<Handle> { |
281 | imp::Handle::stderr().map(Handle) |
282 | } |
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 | pub fn as_file(&self) -> &File { |
321 | self.0.as_file() |
322 | } |
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 | pub fn as_file_mut(&mut self) -> &mut File { |
331 | self.0.as_file_mut() |
332 | } |
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 | pub fn dev(&self) -> u64 { |
339 | self.0.dev() |
340 | } |
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 | pub fn ino(&self) -> u64 { |
347 | self.0.ino() |
348 | } |
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 | pub fn is_same_file<P, Q>(path1: P, path2: Q) -> io::Result<bool> |
371 | where |
372 | P: AsRef<Path>, |
373 | Q: AsRef<Path>, |
374 | { |
375 | Ok(Handle::from_path(path1)? == Handle::from_path(path2)?) |
376 | } |
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 | } |
573 | |