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}