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}
573