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#![doc(html_root_url = "https://docs.rs/same-file/1.0.0")]
69#![deny(missing_docs)]
70
71#[cfg(windows)]
72extern crate winapi_util;
73
74use std::fs::File;
75use std::io;
76use std::path::Path;
77
78#[cfg(any(target_os = "redox", unix))]
79use unix as imp;
80#[cfg(windows)]
81use win as imp;
82
83#[cfg(any(target_os = "redox", unix))]
84mod unix;
85#[cfg(windows)]
86mod win;
87
88/// A handle to a file that can be tested for equality with other handles.
89///
90/// If two files are the same, then any two handles of those files will compare
91/// equal. If two files are not the same, then any two handles of those files
92/// will compare not-equal.
93///
94/// A handle consumes an open file resource as long as it exists.
95///
96/// Equality is determined by comparing inode numbers on Unix and a combination
97/// of identifier, volume serial, and file size on Windows. Note that it's
98/// possible for comparing two handles to produce a false positive on some
99/// platforms. Namely, two handles can compare equal even if the two handles
100/// *don't* point to the same file. Check the [source] for specific
101/// implementation details.
102///
103/// [source]: https://github.com/BurntSushi/same-file/tree/master/src
104#[derive(Debug, Eq, PartialEq)]
105pub struct Handle(imp::Handle);
106
107impl Handle {
108    /// Construct a handle from a path.
109    ///
110    /// Note that the underlying [`File`] is opened in read-only mode on all
111    /// platforms.
112    ///
113    /// [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html
114    ///
115    /// # Errors
116    /// This method will return an [`io::Error`] if the path cannot
117    /// be opened, or the file's metadata cannot be obtained.
118    /// The most common reasons for this are: the path does not
119    /// exist, or there were not enough permissions.
120    ///
121    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
122    ///
123    /// # Examples
124    /// Check that two paths are not the same file:
125    ///
126    /// ```rust,no_run
127    /// # use std::error::Error;
128    /// use same_file::Handle;
129    ///
130    /// # fn try_main() -> Result<(), Box<Error>> {
131    /// let source = Handle::from_path("./source")?;
132    /// let target = Handle::from_path("./target")?;
133    /// assert_ne!(source, target, "The files are the same.");
134    /// # Ok(())
135    /// # }
136    /// #
137    /// # fn main() {
138    /// #     try_main().unwrap();
139    /// # }
140    /// ```
141    pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> {
142        imp::Handle::from_path(p).map(Handle)
143    }
144
145    /// Construct a handle from a file.
146    ///
147    /// # Errors
148    /// This method will return an [`io::Error`] if the metadata for
149    /// the given [`File`] cannot be obtained.
150    ///
151    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
152    /// [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html
153    ///
154    /// # Examples
155    /// Check that two files are not in fact the same file:
156    ///
157    /// ```rust,no_run
158    /// # use std::error::Error;
159    /// # use std::fs::File;
160    /// use same_file::Handle;
161    ///
162    /// # fn try_main() -> Result<(), Box<Error>> {
163    /// let source = File::open("./source")?;
164    /// let target = File::open("./target")?;
165    ///
166    /// assert_ne!(
167    ///     Handle::from_file(source)?,
168    ///     Handle::from_file(target)?,
169    ///     "The files are the same."
170    /// );
171    /// #     Ok(())
172    /// # }
173    /// #
174    /// # fn main() {
175    /// #     try_main().unwrap();
176    /// # }
177    /// ```
178    pub fn from_file(file: File) -> io::Result<Handle> {
179        imp::Handle::from_file(file).map(Handle)
180    }
181
182    /// Construct a handle from stdin.
183    ///
184    /// # Errors
185    /// This method will return an [`io::Error`] if stdin cannot
186    /// be opened due to any I/O-related reason.
187    ///
188    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
189    ///
190    /// # Examples
191    ///
192    /// ```rust
193    /// # use std::error::Error;
194    /// use same_file::Handle;
195    ///
196    /// # fn try_main() -> Result<(), Box<Error>> {
197    /// let stdin = Handle::stdin()?;
198    /// let stdout = Handle::stdout()?;
199    /// let stderr = Handle::stderr()?;
200    ///
201    /// if stdin == stdout {
202    ///     println!("stdin == stdout");
203    /// }
204    /// if stdin == stderr {
205    ///     println!("stdin == stderr");
206    /// }
207    /// if stdout == stderr {
208    ///     println!("stdout == stderr");
209    /// }
210    /// #
211    /// #     Ok(())
212    /// # }
213    /// #
214    /// # fn main() {
215    /// #     try_main().unwrap();
216    /// # }
217    /// ```
218    ///
219    /// The output differs depending on the platform.
220    ///
221    /// On Linux:
222    ///
223    /// ```text
224    /// $ ./example
225    /// stdin == stdout
226    /// stdin == stderr
227    /// stdout == stderr
228    /// $ ./example > result
229    /// $ cat result
230    /// stdin == stderr
231    /// $ ./example > result 2>&1
232    /// $ cat result
233    /// stdout == stderr
234    /// ```
235    ///
236    /// Windows:
237    ///
238    /// ```text
239    /// > example
240    /// > example > result 2>&1
241    /// > type result
242    /// stdout == stderr
243    /// ```
244    pub fn stdin() -> io::Result<Handle> {
245        imp::Handle::stdin().map(Handle)
246    }
247
248    /// Construct a handle from stdout.
249    ///
250    /// # Errors
251    /// This method will return an [`io::Error`] if stdout cannot
252    /// be opened due to any I/O-related reason.
253    ///
254    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
255    ///
256    /// # Examples
257    /// See the example for [`stdin()`].
258    ///
259    /// [`stdin()`]: #method.stdin
260    pub fn stdout() -> io::Result<Handle> {
261        imp::Handle::stdout().map(Handle)
262    }
263
264    /// Construct a handle from stderr.
265    ///
266    /// # Errors
267    /// This method will return an [`io::Error`] if stderr cannot
268    /// be opened due to any I/O-related reason.
269    ///
270    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
271    ///
272    /// # Examples
273    /// See the example for [`stdin()`].
274    ///
275    /// [`stdin()`]: #method.stdin
276    pub fn stderr() -> io::Result<Handle> {
277        imp::Handle::stderr().map(Handle)
278    }
279
280    /// Return a reference to the underlying file.
281    ///
282    /// # Examples
283    /// Ensure that the target file is not the same as the source one,
284    /// and copy the data to it:
285    ///
286    /// ```rust,no_run
287    /// # use std::error::Error;
288    /// use std::io::prelude::*;
289    /// use std::io::Write;
290    /// use std::fs::File;
291    /// use same_file::Handle;
292    ///
293    /// # fn try_main() -> Result<(), Box<Error>> {
294    /// let source = File::open("source")?;
295    /// let target = File::create("target")?;
296    ///
297    /// let source_handle = Handle::from_file(source)?;
298    /// let mut target_handle = Handle::from_file(target)?;
299    /// assert_ne!(source_handle, target_handle, "The files are the same.");
300    ///
301    /// let mut source = source_handle.as_file();
302    /// let target = target_handle.as_file_mut();
303    ///
304    /// let mut buffer = Vec::new();
305    /// // data copy is simplified for the purposes of the example
306    /// source.read_to_end(&mut buffer)?;
307    /// target.write_all(&buffer)?;
308    /// #
309    /// #    Ok(())
310    /// # }
311    /// #
312    /// # fn main() {
313    /// #    try_main().unwrap();
314    /// # }
315    /// ```
316    pub fn as_file(&self) -> &File {
317        self.0.as_file()
318    }
319
320    /// Return a mutable reference to the underlying file.
321    ///
322    /// # Examples
323    /// See the example for [`as_file()`].
324    ///
325    /// [`as_file()`]: #method.as_file
326    pub fn as_file_mut(&mut self) -> &mut File {
327        self.0.as_file_mut()
328    }
329
330    /// Return the underlying device number of this handle.
331    ///
332    /// Note that this only works on unix platforms.
333    #[cfg(any(target_os = "redox", unix))]
334    pub fn dev(&self) -> u64 {
335        self.0.dev()
336    }
337
338    /// Return the underlying inode number of this handle.
339    ///
340    /// Note that this only works on unix platforms.
341    #[cfg(any(target_os = "redox", unix))]
342    pub fn ino(&self) -> u64 {
343        self.0.ino()
344    }
345}
346
347/// Returns true if the two file paths may correspond to the same file.
348///
349/// Note that it's possible for this to produce a false positive on some
350/// platforms. Namely, this can return true even if the two file paths *don't*
351/// resolve to the same file.
352/// # Errors
353/// This function will return an [`io::Error`] if any of the two paths cannot
354/// be opened. The most common reasons for this are: the path does not exist,
355/// or there were not enough permissions.
356///
357/// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
358///
359/// # Example
360///
361/// ```rust,no_run
362/// use same_file::is_same_file;
363///
364/// assert!(is_same_file("./foo", "././foo").unwrap_or(false));
365/// ```
366pub fn is_same_file<P, Q>(
367    path1: P,
368    path2: Q,
369) -> io::Result<bool> where P: AsRef<Path>, Q: AsRef<Path> {
370    Ok(Handle::from_path(path1)? == Handle::from_path(path2)?)
371}
372
373#[cfg(test)]
374mod tests {
375    extern crate rand;
376
377    use std::env;
378    use std::fs::{self, File};
379    use std::io;
380    use std::path::{Path, PathBuf};
381
382    use self::rand::Rng;
383
384    use super::is_same_file;
385
386    struct TempDir(PathBuf);
387
388    impl TempDir {
389        fn path<'a>(&'a self) -> &'a Path {
390            &self.0
391        }
392    }
393
394    impl Drop for TempDir {
395        fn drop(&mut self) {
396            fs::remove_dir_all(&self.0).unwrap();
397        }
398    }
399
400    fn tmpdir() -> TempDir {
401        let p = env::temp_dir();
402        let mut r = self::rand::thread_rng();
403        let ret = p.join(&format!("rust-{}", r.next_u32()));
404        fs::create_dir(&ret).unwrap();
405        TempDir(ret)
406    }
407
408    #[cfg(unix)]
409    pub fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>(
410        src: P,
411        dst: Q,
412    ) -> io::Result<()> {
413        use std::os::unix::fs::symlink;
414        symlink(src, dst)
415    }
416
417    #[cfg(unix)]
418    pub fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>(
419        src: P,
420        dst: Q,
421    ) -> io::Result<()> {
422        soft_link_dir(src, dst)
423    }
424
425    #[cfg(windows)]
426    pub fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>(
427        src: P,
428        dst: Q,
429    ) -> io::Result<()> {
430        use std::os::windows::fs::symlink_dir;
431        symlink_dir(src, dst)
432    }
433
434    #[cfg(windows)]
435    pub fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>(
436        src: P,
437        dst: Q,
438    ) -> io::Result<()> {
439        use std::os::windows::fs::symlink_file;
440        symlink_file(src, dst)
441    }
442
443    // These tests are rather uninteresting. The really interesting tests
444    // would stress the edge cases. On Unix, this might be comparing two files
445    // on different mount points with the same inode number. On Windows, this
446    // might be comparing two files whose file indices are the same on file
447    // systems where such things aren't guaranteed to be unique.
448    //
449    // Alas, I don't know how to create those environmental conditions. ---AG
450
451    #[test]
452    fn same_file_trivial() {
453        let tdir = tmpdir();
454        let dir = tdir.path();
455
456        File::create(dir.join("a")).unwrap();
457        assert!(is_same_file(dir.join("a"), dir.join("a")).unwrap());
458    }
459
460    #[test]
461    fn same_dir_trivial() {
462        let tdir = tmpdir();
463        let dir = tdir.path();
464
465        fs::create_dir(dir.join("a")).unwrap();
466        assert!(is_same_file(dir.join("a"), dir.join("a")).unwrap());
467    }
468
469    #[test]
470    fn not_same_file_trivial() {
471        let tdir = tmpdir();
472        let dir = tdir.path();
473
474        File::create(dir.join("a")).unwrap();
475        File::create(dir.join("b")).unwrap();
476        assert!(!is_same_file(dir.join("a"), dir.join("b")).unwrap());
477    }
478
479    #[test]
480    fn not_same_dir_trivial() {
481        let tdir = tmpdir();
482        let dir = tdir.path();
483
484        fs::create_dir(dir.join("a")).unwrap();
485        fs::create_dir(dir.join("b")).unwrap();
486        assert!(!is_same_file(dir.join("a"), dir.join("b")).unwrap());
487    }
488
489    #[test]
490    fn same_file_hard() {
491        let tdir = tmpdir();
492        let dir = tdir.path();
493
494        File::create(dir.join("a")).unwrap();
495        fs::hard_link(dir.join("a"), dir.join("alink")).unwrap();
496        assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap());
497    }
498
499    #[test]
500    fn same_file_soft() {
501        let tdir = tmpdir();
502        let dir = tdir.path();
503
504        File::create(dir.join("a")).unwrap();
505        soft_link_file(dir.join("a"), dir.join("alink")).unwrap();
506        assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap());
507    }
508
509    #[test]
510    fn same_dir_soft() {
511        let tdir = tmpdir();
512        let dir = tdir.path();
513
514        fs::create_dir(dir.join("a")).unwrap();
515        soft_link_dir(dir.join("a"), dir.join("alink")).unwrap();
516        assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap());
517    }
518
519    #[test]
520    fn test_send() {
521        fn assert_send<T: Send>() {}
522        assert_send::<super::Handle>();
523    }
524
525    #[test]
526    fn test_sync() {
527        fn assert_sync<T: Sync>() {}
528        assert_sync::<super::Handle>();
529    }
530}