same-file-1.0.6/.gitignore010064400017500000144000000001321274622257600135760ustar0000000000000000.*.swp doc tags examples/ss10pusa.csv build target Cargo.lock scratch* bench_large/huge x same-file-1.0.6/COPYING010064400017500000144000000001761322772466600126530ustar0000000000000000This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. same-file-1.0.6/Cargo.toml.orig010066400017500001731000000011301360637764500145050ustar0000000000000000[package] name = "same-file" version = "1.0.6" #:version authors = ["Andrew Gallant "] description = """ A simple crate for determining whether two file paths point to the same file. """ documentation = "https://docs.rs/same-file" homepage = "https://github.com/BurntSushi/same-file" repository = "https://github.com/BurntSushi/same-file" readme = "README.md" keywords = ["same", "file", "equal", "inode"] license = "Unlicense/MIT" exclude = ["/.github"] edition = "2018" [target.'cfg(windows)'.dependencies.winapi-util] version = "0.1.1" [dev-dependencies] doc-comment = "0.3" same-file-1.0.6/Cargo.toml0000644000000021341360637764600110200ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "same-file" version = "1.0.6" authors = ["Andrew Gallant "] exclude = ["/.github"] description = "A simple crate for determining whether two file paths point to the same file.\n" homepage = "https://github.com/BurntSushi/same-file" documentation = "https://docs.rs/same-file" readme = "README.md" keywords = ["same", "file", "equal", "inode"] license = "Unlicense/MIT" repository = "https://github.com/BurntSushi/same-file" [dev-dependencies.doc-comment] version = "0.3" [target."cfg(windows)".dependencies.winapi-util] version = "0.1.1" same-file-1.0.6/LICENSE-MIT010064400017500000144000000020711322772471600132440ustar0000000000000000The MIT License (MIT) Copyright (c) 2017 Andrew Gallant Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. same-file-1.0.6/README.md010066400017500001731000000025111360637756200130770ustar0000000000000000same-file ========= A safe and cross platform crate to determine whether two files or directories are the same. [![Build status](https://github.com/BurntSushi/same-file/workflows/ci/badge.svg)](https://github.com/BurntSushi/same-file/actions) [![](http://meritbadge.herokuapp.com/same-file)](https://crates.io/crates/same-file) Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org). ### Documentation https://docs.rs/same-file ### Usage Add this to your `Cargo.toml`: ```toml [dependencies] same-file = "1" ``` ### Example The simplest use of this crate is to use the `is_same_file` function, which takes two file paths and returns true if and only if they refer to the same file: ```rust,no_run use same_file::is_same_file; fn main() { assert!(is_same_file("/bin/sh", "/usr/bin/sh").unwrap()); } ``` ### Minimum Rust version policy This crate's minimum supported `rustc` version is `1.34.0`. The current policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if `crate 1.0` requires Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust 1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum version of Rust. In general, this crate will be conservative with respect to the minimum supported version of Rust. same-file-1.0.6/UNLICENSE010064400017500000144000000022731322772466600130700ustar0000000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to same-file-1.0.6/examples/is_same_file.rs010066400017500001731000000002771360637756200164320ustar0000000000000000use same_file::is_same_file; use std::io; fn try_main() -> Result<(), io::Error> { assert!(is_same_file("/bin/sh", "/usr/bin/sh")?); Ok(()) } fn main() { try_main().unwrap(); } same-file-1.0.6/examples/is_stderr.rs010066400017500001731000000013431360637756200160040ustar0000000000000000use std::io; use std::process; use same_file::Handle; fn main() { if let Err(err) = run() { println!("{}", err); process::exit(1); } } fn run() -> io::Result<()> { // Run with `cargo run --example is_stderr 2> examples/stderr` to see // interesting output. let candidates = &[ "examples/is_same_file.rs", "examples/is_stderr.rs", "examples/stderr", ]; let stderr_handle = Handle::stderr()?; for candidate in candidates { let handle = Handle::from_path(candidate)?; if stderr_handle == handle { println!("{:?} is stderr!", candidate); } else { println!("{:?} is NOT stderr!", candidate); } } Ok(()) } same-file-1.0.6/rustfmt.toml010066400017500001731000000000541360637756200142210ustar0000000000000000max_width = 79 use_small_heuristics = "max" same-file-1.0.6/src/lib.rs010066400017500001731000000377061360637756200135410ustar0000000000000000/*! This crate provides a safe and simple **cross platform** way to determine whether two file paths refer to the same file or directory. Most uses of this crate should be limited to the top-level [`is_same_file`] function, which takes two file paths and returns true if they refer to the same file or directory: ```rust,no_run # use std::error::Error; use same_file::is_same_file; # fn try_main() -> Result<(), Box> { assert!(is_same_file("/bin/sh", "/usr/bin/sh")?); # Ok(()) # } # # fn main() { # try_main().unwrap(); # } ``` Additionally, this crate provides a [`Handle`] type that permits a more efficient equality check depending on your access pattern. For example, if one wanted to check whether any path in a list of paths corresponded to the process' stdout handle, then one could build a handle once for stdout. The equality check for each file in the list then only requires one stat call instead of two. The code might look like this: ```rust,no_run # use std::error::Error; use same_file::Handle; # fn try_main() -> Result<(), Box> { let candidates = &[ "examples/is_same_file.rs", "examples/is_stderr.rs", "examples/stderr", ]; let stdout_handle = Handle::stdout()?; for candidate in candidates { let handle = Handle::from_path(candidate)?; if stdout_handle == handle { println!("{:?} is stdout!", candidate); } else { println!("{:?} is NOT stdout!", candidate); } } # Ok(()) # } # # fn main() { # try_main().unwrap(); # } ``` See [`examples/is_stderr.rs`] for a runnable example and compare the output of: - `cargo run --example is_stderr 2> examples/stderr` and - `cargo run --example is_stderr`. [`is_same_file`]: fn.is_same_file.html [`Handle`]: struct.Handle.html [`examples/is_stderr.rs`]: https://github.com/BurntSushi/same-file/blob/master/examples/is_same_file.rs */ #![allow(bare_trait_objects, unknown_lints)] #![deny(missing_docs)] #[cfg(test)] doc_comment::doctest!("../README.md"); use std::fs::File; use std::io; use std::path::Path; #[cfg(any(target_os = "redox", unix))] use crate::unix as imp; #[cfg(not(any(target_os = "redox", unix, windows)))] use unknown as imp; #[cfg(windows)] use win as imp; #[cfg(any(target_os = "redox", unix))] mod unix; #[cfg(not(any(target_os = "redox", unix, windows)))] mod unknown; #[cfg(windows)] mod win; /// A handle to a file that can be tested for equality with other handles. /// /// If two files are the same, then any two handles of those files will compare /// equal. If two files are not the same, then any two handles of those files /// will compare not-equal. /// /// A handle consumes an open file resource as long as it exists. /// /// Equality is determined by comparing inode numbers on Unix and a combination /// of identifier, volume serial, and file size on Windows. Note that it's /// possible for comparing two handles to produce a false positive on some /// platforms. Namely, two handles can compare equal even if the two handles /// *don't* point to the same file. Check the [source] for specific /// implementation details. /// /// [source]: https://github.com/BurntSushi/same-file/tree/master/src #[derive(Debug, Eq, PartialEq, Hash)] pub struct Handle(imp::Handle); impl Handle { /// Construct a handle from a path. /// /// Note that the underlying [`File`] is opened in read-only mode on all /// platforms. /// /// [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html /// /// # Errors /// This method will return an [`io::Error`] if the path cannot /// be opened, or the file's metadata cannot be obtained. /// The most common reasons for this are: the path does not /// exist, or there were not enough permissions. /// /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// /// # Examples /// Check that two paths are not the same file: /// /// ```rust,no_run /// # use std::error::Error; /// use same_file::Handle; /// /// # fn try_main() -> Result<(), Box> { /// let source = Handle::from_path("./source")?; /// let target = Handle::from_path("./target")?; /// assert_ne!(source, target, "The files are the same."); /// # Ok(()) /// # } /// # /// # fn main() { /// # try_main().unwrap(); /// # } /// ``` pub fn from_path>(p: P) -> io::Result { imp::Handle::from_path(p).map(Handle) } /// Construct a handle from a file. /// /// # Errors /// This method will return an [`io::Error`] if the metadata for /// the given [`File`] cannot be obtained. /// /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html /// /// # Examples /// Check that two files are not in fact the same file: /// /// ```rust,no_run /// # use std::error::Error; /// # use std::fs::File; /// use same_file::Handle; /// /// # fn try_main() -> Result<(), Box> { /// let source = File::open("./source")?; /// let target = File::open("./target")?; /// /// assert_ne!( /// Handle::from_file(source)?, /// Handle::from_file(target)?, /// "The files are the same." /// ); /// # Ok(()) /// # } /// # /// # fn main() { /// # try_main().unwrap(); /// # } /// ``` pub fn from_file(file: File) -> io::Result { imp::Handle::from_file(file).map(Handle) } /// Construct a handle from stdin. /// /// # Errors /// This method will return an [`io::Error`] if stdin cannot /// be opened due to any I/O-related reason. /// /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// /// # Examples /// /// ```rust /// # use std::error::Error; /// use same_file::Handle; /// /// # fn try_main() -> Result<(), Box> { /// let stdin = Handle::stdin()?; /// let stdout = Handle::stdout()?; /// let stderr = Handle::stderr()?; /// /// if stdin == stdout { /// println!("stdin == stdout"); /// } /// if stdin == stderr { /// println!("stdin == stderr"); /// } /// if stdout == stderr { /// println!("stdout == stderr"); /// } /// # /// # Ok(()) /// # } /// # /// # fn main() { /// # try_main().unwrap(); /// # } /// ``` /// /// The output differs depending on the platform. /// /// On Linux: /// /// ```text /// $ ./example /// stdin == stdout /// stdin == stderr /// stdout == stderr /// $ ./example > result /// $ cat result /// stdin == stderr /// $ ./example > result 2>&1 /// $ cat result /// stdout == stderr /// ``` /// /// Windows: /// /// ```text /// > example /// > example > result 2>&1 /// > type result /// stdout == stderr /// ``` pub fn stdin() -> io::Result { imp::Handle::stdin().map(Handle) } /// Construct a handle from stdout. /// /// # Errors /// This method will return an [`io::Error`] if stdout cannot /// be opened due to any I/O-related reason. /// /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// /// # Examples /// See the example for [`stdin()`]. /// /// [`stdin()`]: #method.stdin pub fn stdout() -> io::Result { imp::Handle::stdout().map(Handle) } /// Construct a handle from stderr. /// /// # Errors /// This method will return an [`io::Error`] if stderr cannot /// be opened due to any I/O-related reason. /// /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// /// # Examples /// See the example for [`stdin()`]. /// /// [`stdin()`]: #method.stdin pub fn stderr() -> io::Result { imp::Handle::stderr().map(Handle) } /// Return a reference to the underlying file. /// /// # Examples /// Ensure that the target file is not the same as the source one, /// and copy the data to it: /// /// ```rust,no_run /// # use std::error::Error; /// use std::io::prelude::*; /// use std::io::Write; /// use std::fs::File; /// use same_file::Handle; /// /// # fn try_main() -> Result<(), Box> { /// let source = File::open("source")?; /// let target = File::create("target")?; /// /// let source_handle = Handle::from_file(source)?; /// let mut target_handle = Handle::from_file(target)?; /// assert_ne!(source_handle, target_handle, "The files are the same."); /// /// let mut source = source_handle.as_file(); /// let target = target_handle.as_file_mut(); /// /// let mut buffer = Vec::new(); /// // data copy is simplified for the purposes of the example /// source.read_to_end(&mut buffer)?; /// target.write_all(&buffer)?; /// # /// # Ok(()) /// # } /// # /// # fn main() { /// # try_main().unwrap(); /// # } /// ``` pub fn as_file(&self) -> &File { self.0.as_file() } /// Return a mutable reference to the underlying file. /// /// # Examples /// See the example for [`as_file()`]. /// /// [`as_file()`]: #method.as_file pub fn as_file_mut(&mut self) -> &mut File { self.0.as_file_mut() } /// Return the underlying device number of this handle. /// /// Note that this only works on unix platforms. #[cfg(any(target_os = "redox", unix))] pub fn dev(&self) -> u64 { self.0.dev() } /// Return the underlying inode number of this handle. /// /// Note that this only works on unix platforms. #[cfg(any(target_os = "redox", unix))] pub fn ino(&self) -> u64 { self.0.ino() } } /// Returns true if the two file paths may correspond to the same file. /// /// Note that it's possible for this to produce a false positive on some /// platforms. Namely, this can return true even if the two file paths *don't* /// resolve to the same file. /// # Errors /// This function will return an [`io::Error`] if any of the two paths cannot /// be opened. The most common reasons for this are: the path does not exist, /// or there were not enough permissions. /// /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html /// /// # Example /// /// ```rust,no_run /// use same_file::is_same_file; /// /// assert!(is_same_file("./foo", "././foo").unwrap_or(false)); /// ``` pub fn is_same_file(path1: P, path2: Q) -> io::Result where P: AsRef, Q: AsRef, { Ok(Handle::from_path(path1)? == Handle::from_path(path2)?) } #[cfg(test)] mod tests { use std::env; use std::error; use std::fs::{self, File}; use std::io; use std::path::{Path, PathBuf}; use std::result; use super::is_same_file; type Result = result::Result>; /// Create an error from a format!-like syntax. macro_rules! err { ($($tt:tt)*) => { Box::::from(format!($($tt)*)) } } /// A simple wrapper for creating a temporary directory that is /// automatically deleted when it's dropped. /// /// We use this in lieu of tempfile because tempfile brings in too many /// dependencies. #[derive(Debug)] struct TempDir(PathBuf); impl Drop for TempDir { fn drop(&mut self) { fs::remove_dir_all(&self.0).unwrap(); } } impl TempDir { /// Create a new empty temporary directory under the system's /// configured temporary directory. fn new() -> Result { #![allow(deprecated)] use std::sync::atomic::{ AtomicUsize, Ordering, ATOMIC_USIZE_INIT, }; static TRIES: usize = 100; static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; let tmpdir = env::temp_dir(); for _ in 0..TRIES { let count = COUNTER.fetch_add(1, Ordering::SeqCst); let path = tmpdir.join("rust-walkdir").join(count.to_string()); if path.is_dir() { continue; } fs::create_dir_all(&path).map_err(|e| { err!("failed to create {}: {}", path.display(), e) })?; return Ok(TempDir(path)); } Err(err!("failed to create temp dir after {} tries", TRIES)) } /// Return the underlying path to this temporary directory. fn path(&self) -> &Path { &self.0 } } fn tmpdir() -> TempDir { TempDir::new().unwrap() } #[cfg(unix)] pub fn soft_link_dir, Q: AsRef>( src: P, dst: Q, ) -> io::Result<()> { use std::os::unix::fs::symlink; symlink(src, dst) } #[cfg(unix)] pub fn soft_link_file, Q: AsRef>( src: P, dst: Q, ) -> io::Result<()> { soft_link_dir(src, dst) } #[cfg(windows)] pub fn soft_link_dir, Q: AsRef>( src: P, dst: Q, ) -> io::Result<()> { use std::os::windows::fs::symlink_dir; symlink_dir(src, dst) } #[cfg(windows)] pub fn soft_link_file, Q: AsRef>( src: P, dst: Q, ) -> io::Result<()> { use std::os::windows::fs::symlink_file; symlink_file(src, dst) } // These tests are rather uninteresting. The really interesting tests // would stress the edge cases. On Unix, this might be comparing two files // on different mount points with the same inode number. On Windows, this // might be comparing two files whose file indices are the same on file // systems where such things aren't guaranteed to be unique. // // Alas, I don't know how to create those environmental conditions. ---AG #[test] fn same_file_trivial() { let tdir = tmpdir(); let dir = tdir.path(); File::create(dir.join("a")).unwrap(); assert!(is_same_file(dir.join("a"), dir.join("a")).unwrap()); } #[test] fn same_dir_trivial() { let tdir = tmpdir(); let dir = tdir.path(); fs::create_dir(dir.join("a")).unwrap(); assert!(is_same_file(dir.join("a"), dir.join("a")).unwrap()); } #[test] fn not_same_file_trivial() { let tdir = tmpdir(); let dir = tdir.path(); File::create(dir.join("a")).unwrap(); File::create(dir.join("b")).unwrap(); assert!(!is_same_file(dir.join("a"), dir.join("b")).unwrap()); } #[test] fn not_same_dir_trivial() { let tdir = tmpdir(); let dir = tdir.path(); fs::create_dir(dir.join("a")).unwrap(); fs::create_dir(dir.join("b")).unwrap(); assert!(!is_same_file(dir.join("a"), dir.join("b")).unwrap()); } #[test] fn same_file_hard() { let tdir = tmpdir(); let dir = tdir.path(); File::create(dir.join("a")).unwrap(); fs::hard_link(dir.join("a"), dir.join("alink")).unwrap(); assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap()); } #[test] fn same_file_soft() { let tdir = tmpdir(); let dir = tdir.path(); File::create(dir.join("a")).unwrap(); soft_link_file(dir.join("a"), dir.join("alink")).unwrap(); assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap()); } #[test] fn same_dir_soft() { let tdir = tmpdir(); let dir = tdir.path(); fs::create_dir(dir.join("a")).unwrap(); soft_link_dir(dir.join("a"), dir.join("alink")).unwrap(); assert!(is_same_file(dir.join("a"), dir.join("alink")).unwrap()); } #[test] fn test_send() { fn assert_send() {} assert_send::(); } #[test] fn test_sync() { fn assert_sync() {} assert_sync::(); } } same-file-1.0.6/src/unix.rs010066400017500001731000000056701360637756200137510ustar0000000000000000use std::fs::{File, OpenOptions}; use std::hash::{Hash, Hasher}; use std::io; use std::os::unix::fs::MetadataExt; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use std::path::Path; #[derive(Debug)] pub struct Handle { file: Option, // If is_std is true, then we don't drop the corresponding File since it // will close the handle. is_std: bool, dev: u64, ino: u64, } impl Drop for Handle { fn drop(&mut self) { if self.is_std { // unwrap() will not panic. Since we were able to open an // std stream successfully, then `file` is guaranteed to be Some() self.file.take().unwrap().into_raw_fd(); } } } impl Eq for Handle {} impl PartialEq for Handle { fn eq(&self, other: &Handle) -> bool { (self.dev, self.ino) == (other.dev, other.ino) } } impl AsRawFd for crate::Handle { fn as_raw_fd(&self) -> RawFd { // unwrap() will not panic. Since we were able to open the // file successfully, then `file` is guaranteed to be Some() self.0.file.as_ref().take().unwrap().as_raw_fd() } } impl IntoRawFd for crate::Handle { fn into_raw_fd(mut self) -> RawFd { // unwrap() will not panic. Since we were able to open the // file successfully, then `file` is guaranteed to be Some() self.0.file.take().unwrap().into_raw_fd() } } impl Hash for Handle { fn hash(&self, state: &mut H) { self.dev.hash(state); self.ino.hash(state); } } impl Handle { pub fn from_path>(p: P) -> io::Result { Handle::from_file(OpenOptions::new().read(true).open(p)?) } pub fn from_file(file: File) -> io::Result { let md = file.metadata()?; Ok(Handle { file: Some(file), is_std: false, dev: md.dev(), ino: md.ino(), }) } pub fn from_std(file: File) -> io::Result { Handle::from_file(file).map(|mut h| { h.is_std = true; h }) } pub fn stdin() -> io::Result { Handle::from_std(unsafe { File::from_raw_fd(0) }) } pub fn stdout() -> io::Result { Handle::from_std(unsafe { File::from_raw_fd(1) }) } pub fn stderr() -> io::Result { Handle::from_std(unsafe { File::from_raw_fd(2) }) } pub fn as_file(&self) -> &File { // unwrap() will not panic. Since we were able to open the // file successfully, then `file` is guaranteed to be Some() self.file.as_ref().take().unwrap() } pub fn as_file_mut(&mut self) -> &mut File { // unwrap() will not panic. Since we were able to open the // file successfully, then `file` is guaranteed to be Some() self.file.as_mut().take().unwrap() } pub fn dev(&self) -> u64 { self.dev } pub fn ino(&self) -> u64 { self.ino } } same-file-1.0.6/src/unknown.rs010066400017500001731000000021541360637611300144460ustar0000000000000000use std::fs::File; use std::io; use std::path::Path; static ERROR_MESSAGE: &str = "same-file is not supported on this platform."; // This implementation is to allow same-file to be compiled on // unsupported platforms in case it was incidentally included // as a transitive, unused dependency #[derive(Debug, Hash)] pub struct Handle; impl Eq for Handle {} impl PartialEq for Handle { fn eq(&self, _other: &Handle) -> bool { unreachable!(ERROR_MESSAGE); } } impl Handle { pub fn from_path>(_p: P) -> io::Result { error() } pub fn from_file(_file: File) -> io::Result { error() } pub fn stdin() -> io::Result { error() } pub fn stdout() -> io::Result { error() } pub fn stderr() -> io::Result { error() } pub fn as_file(&self) -> &File { unreachable!(ERROR_MESSAGE); } pub fn as_file_mut(&self) -> &mut File { unreachable!(ERROR_MESSAGE); } } fn error() -> io::Result { Err(io::Error::new(io::ErrorKind::Other, ERROR_MESSAGE)) } same-file-1.0.6/src/win.rs010066400017500001731000000132251360637756200135560ustar0000000000000000use std::fs::File; use std::hash::{Hash, Hasher}; use std::io; use std::os::windows::io::{AsRawHandle, IntoRawHandle, RawHandle}; use std::path::Path; use winapi_util as winutil; // For correctness, it is critical that both file handles remain open while // their attributes are checked for equality. In particular, the file index // numbers on a Windows stat object are not guaranteed to remain stable over // time. // // See the docs and remarks on MSDN: // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx // // It gets worse. It appears that the index numbers are not always // guaranteed to be unique. Namely, ReFS uses 128 bit numbers for unique // identifiers. This requires a distinct syscall to get `FILE_ID_INFO` // documented here: // https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx // // It seems straight-forward enough to modify this code to use // `FILE_ID_INFO` when available (minimum Windows Server 2012), but I don't // have access to such Windows machines. // // Two notes. // // 1. Java's NIO uses the approach implemented here and appears to ignore // `FILE_ID_INFO` altogether. So Java's NIO and this code are // susceptible to bugs when running on a file system where // `nFileIndex{Low,High}` are not unique. // // 2. LLVM has a bug where they fetch the id of a file and continue to use // it even after the handle has been closed, so that uniqueness is no // longer guaranteed (when `nFileIndex{Low,High}` are unique). // bug report: http://lists.llvm.org/pipermail/llvm-bugs/2014-December/037218.html // // All said and done, checking whether two files are the same on Windows // seems quite tricky. Moreover, even if the code is technically incorrect, // it seems like the chances of actually observing incorrect behavior are // extremely small. Nevertheless, we mitigate this by checking size too. // // In the case where this code is erroneous, two files will be reported // as equivalent when they are in fact distinct. This will cause the loop // detection code to report a false positive, which will prevent descending // into the offending directory. As far as failure modes goes, this isn't // that bad. #[derive(Debug)] pub struct Handle { kind: HandleKind, key: Option, } #[derive(Debug)] enum HandleKind { /// Used when opening a file or acquiring ownership of a file. Owned(winutil::Handle), /// Used for stdio. Borrowed(winutil::HandleRef), } #[derive(Debug, Eq, PartialEq, Hash)] struct Key { volume: u64, index: u64, } impl Eq for Handle {} impl PartialEq for Handle { fn eq(&self, other: &Handle) -> bool { // Need this branch to satisfy `Eq` since `Handle`s with // `key.is_none()` wouldn't otherwise. if self as *const Handle == other as *const Handle { return true; } else if self.key.is_none() || other.key.is_none() { return false; } self.key == other.key } } impl AsRawHandle for crate::Handle { fn as_raw_handle(&self) -> RawHandle { match self.0.kind { HandleKind::Owned(ref h) => h.as_raw_handle(), HandleKind::Borrowed(ref h) => h.as_raw_handle(), } } } impl IntoRawHandle for crate::Handle { fn into_raw_handle(self) -> RawHandle { match self.0.kind { HandleKind::Owned(h) => h.into_raw_handle(), HandleKind::Borrowed(h) => h.as_raw_handle(), } } } impl Hash for Handle { fn hash(&self, state: &mut H) { self.key.hash(state); } } impl Handle { pub fn from_path>(p: P) -> io::Result { let h = winutil::Handle::from_path_any(p)?; let info = winutil::file::information(&h)?; Ok(Handle::from_info(HandleKind::Owned(h), info)) } pub fn from_file(file: File) -> io::Result { let h = winutil::Handle::from_file(file); let info = winutil::file::information(&h)?; Ok(Handle::from_info(HandleKind::Owned(h), info)) } fn from_std_handle(h: winutil::HandleRef) -> io::Result { match winutil::file::information(&h) { Ok(info) => Ok(Handle::from_info(HandleKind::Borrowed(h), info)), // In a Windows console, if there is no pipe attached to a STD // handle, then GetFileInformationByHandle will return an error. // We don't really care. The only thing we care about is that // this handle is never equivalent to any other handle, which is // accomplished by setting key to None. Err(_) => Ok(Handle { kind: HandleKind::Borrowed(h), key: None }), } } fn from_info( kind: HandleKind, info: winutil::file::Information, ) -> Handle { Handle { kind: kind, key: Some(Key { volume: info.volume_serial_number(), index: info.file_index(), }), } } pub fn stdin() -> io::Result { Handle::from_std_handle(winutil::HandleRef::stdin()) } pub fn stdout() -> io::Result { Handle::from_std_handle(winutil::HandleRef::stdout()) } pub fn stderr() -> io::Result { Handle::from_std_handle(winutil::HandleRef::stderr()) } pub fn as_file(&self) -> &File { match self.kind { HandleKind::Owned(ref h) => h.as_file(), HandleKind::Borrowed(ref h) => h.as_file(), } } pub fn as_file_mut(&mut self) -> &mut File { match self.kind { HandleKind::Owned(ref mut h) => h.as_file_mut(), HandleKind::Borrowed(ref mut h) => h.as_file_mut(), } } } same-file-1.0.6/.cargo_vcs_info.json0000644000000001121360637764600130140ustar00{ "git": { "sha1": "5799cd323b8eefd17a089c950dac113f66c89c9e" } } same-file-1.0.6/Cargo.lock0000644000000037631360637764600110060ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "doc-comment" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "same-file" version = "1.0.6" dependencies = [ "doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-util" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" "checksum winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "890b38836c01d72fdb636d15c9cfc52ec7fd783b330abc93cd1686f4308dfccc" "checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" "checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" "checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"