Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/same-file-1.0.6/src/lib.rs
Line
Count
Source (jump to first uncovered line)
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
0
    pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> {
146
0
        imp::Handle::from_path(p).map(Handle)
147
0
    }
Unexecuted instantiation: <same_file::Handle>::from_path::<&std::path::PathBuf>
Unexecuted instantiation: <same_file::Handle>::from_path::<&&std::path::Path>
Unexecuted instantiation: <same_file::Handle>::from_path::<_>
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
0
    pub fn from_file(file: File) -> io::Result<Handle> {
183
0
        imp::Handle::from_file(file).map(Handle)
184
0
    }
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
0
    pub fn stdin() -> io::Result<Handle> {
249
0
        imp::Handle::stdin().map(Handle)
250
0
    }
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
0
    pub fn stdout() -> io::Result<Handle> {
265
0
        imp::Handle::stdout().map(Handle)
266
0
    }
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
0
    pub fn stderr() -> io::Result<Handle> {
281
0
        imp::Handle::stderr().map(Handle)
282
0
    }
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
0
    pub fn as_file(&self) -> &File {
321
0
        self.0.as_file()
322
0
    }
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
0
    pub fn as_file_mut(&mut self) -> &mut File {
331
0
        self.0.as_file_mut()
332
0
    }
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
0
    pub fn dev(&self) -> u64 {
339
0
        self.0.dev()
340
0
    }
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
0
    pub fn ino(&self) -> u64 {
347
0
        self.0.ino()
348
0
    }
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
0
pub fn is_same_file<P, Q>(path1: P, path2: Q) -> io::Result<bool>
371
0
where
372
0
    P: AsRef<Path>,
373
0
    Q: AsRef<Path>,
374
0
{
375
0
    Ok(Handle::from_path(path1)? == Handle::from_path(path2)?)
376
0
}
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
}