same_file/
lib.rs

1/*!
2This crate provides a safe and simple **cross platform** way to determine
3whether two file paths refer to the same file or directory.
4
5Most uses of this crate should be limited to the top-level [`is_same_file`]
6function, which takes two file paths and returns true if they refer to the
7same file or directory:
8
9```rust,no_run
10# use std::error::Error;
11use same_file::is_same_file;
12
13# fn try_main() -> Result<(), Box<Error>> {
14assert!(is_same_file("/bin/sh", "/usr/bin/sh")?);
15#    Ok(())
16# }
17#
18# fn main() {
19#    try_main().unwrap();
20# }
21```
22
23Additionally, this crate provides a [`Handle`] type that permits a more efficient
24equality check depending on your access pattern. For example, if one wanted to
25check whether any path in a list of paths corresponded to the process' stdout
26handle, then one could build a handle once for stdout. The equality check for
27each file in the list then only requires one stat call instead of two. The code
28might look like this:
29
30```rust,no_run
31# use std::error::Error;
32use same_file::Handle;
33
34# fn try_main() -> Result<(), Box<Error>> {
35let candidates = &[
36    "examples/is_same_file.rs",
37    "examples/is_stderr.rs",
38    "examples/stderr",
39];
40let stdout_handle = Handle::stdout()?;
41for 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
57See [`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)]
72doc_comment::doctest!("../README.md");
73
74use std::fs::File;
75use std::io;
76use std::path::Path;
77
78#[cfg(any(target_os = "redox", unix))]
79use crate::unix as imp;
80#[cfg(not(any(target_os = "redox", unix, windows)))]
81use unknown as imp;
82#[cfg(windows)]
83use win as imp;
84
85#[cfg(any(target_os = "redox", unix))]
86mod unix;
87#[cfg(not(any(target_os = "redox", unix, windows)))]
88mod unknown;
89#[cfg(windows)]
90mod 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)]
109pub struct Handle(imp::Handle);
110
111impl 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/// ```
370pub fn is_same_file<P, Q>(path1: P, path2: Q) -> io::Result<bool>
371where
372    P: AsRef<Path>,
373    Q: AsRef<Path>,
374{
375    Ok(Handle::from_path(path1)? == Handle::from_path(path2)?)
376}
377
378#[cfg(test)]
379mod 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}