Initial import of same-file-1.0.6.

Bug: 155309706
Change-Id: Iaa5474ae70e5c8533693d858e824d2c070410571
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ed7ccf5
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,572 @@
+/*!
+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<Error>> {
+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<Error>> {
+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<Error>> {
+    /// 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: AsRef<Path>>(p: P) -> io::Result<Handle> {
+        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<Error>> {
+    /// 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<Handle> {
+        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<Error>> {
+    /// 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<Handle> {
+        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<Handle> {
+        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<Handle> {
+        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<Error>> {
+    /// 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<P, Q>(path1: P, path2: Q) -> io::Result<bool>
+where
+    P: AsRef<Path>,
+    Q: AsRef<Path>,
+{
+    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<T> = result::Result<T, Box<error::Error + Send + Sync>>;
+
+    /// Create an error from a format!-like syntax.
+    macro_rules! err {
+        ($($tt:tt)*) => {
+            Box::<error::Error + Send + Sync>::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<TempDir> {
+            #![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<P: AsRef<Path>, Q: AsRef<Path>>(
+        src: P,
+        dst: Q,
+    ) -> io::Result<()> {
+        use std::os::unix::fs::symlink;
+        symlink(src, dst)
+    }
+
+    #[cfg(unix)]
+    pub fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>(
+        src: P,
+        dst: Q,
+    ) -> io::Result<()> {
+        soft_link_dir(src, dst)
+    }
+
+    #[cfg(windows)]
+    pub fn soft_link_dir<P: AsRef<Path>, Q: AsRef<Path>>(
+        src: P,
+        dst: Q,
+    ) -> io::Result<()> {
+        use std::os::windows::fs::symlink_dir;
+        symlink_dir(src, dst)
+    }
+
+    #[cfg(windows)]
+    pub fn soft_link_file<P: AsRef<Path>, Q: AsRef<Path>>(
+        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<T: Send>() {}
+        assert_send::<super::Handle>();
+    }
+
+    #[test]
+    fn test_sync() {
+        fn assert_sync<T: Sync>() {}
+        assert_sync::<super::Handle>();
+    }
+}
diff --git a/src/unix.rs b/src/unix.rs
new file mode 100644
index 0000000..fb3d19f
--- /dev/null
+++ b/src/unix.rs
@@ -0,0 +1,112 @@
+use 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<File>,
+    // 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<H: Hasher>(&self, state: &mut H) {
+        self.dev.hash(state);
+        self.ino.hash(state);
+    }
+}
+
+impl Handle {
+    pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> {
+        Handle::from_file(OpenOptions::new().read(true).open(p)?)
+    }
+
+    pub fn from_file(file: File) -> io::Result<Handle> {
+        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> {
+        Handle::from_file(file).map(|mut h| {
+            h.is_std = true;
+            h
+        })
+    }
+
+    pub fn stdin() -> io::Result<Handle> {
+        Handle::from_std(unsafe { File::from_raw_fd(0) })
+    }
+
+    pub fn stdout() -> io::Result<Handle> {
+        Handle::from_std(unsafe { File::from_raw_fd(1) })
+    }
+
+    pub fn stderr() -> io::Result<Handle> {
+        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
+    }
+}
diff --git a/src/unknown.rs b/src/unknown.rs
new file mode 100644
index 0000000..6bfbdea
--- /dev/null
+++ b/src/unknown.rs
@@ -0,0 +1,52 @@
+use 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: AsRef<Path>>(_p: P) -> io::Result<Handle> {
+        error()
+    }
+
+    pub fn from_file(_file: File) -> io::Result<Handle> {
+        error()
+    }
+
+    pub fn stdin() -> io::Result<Handle> {
+        error()
+    }
+
+    pub fn stdout() -> io::Result<Handle> {
+        error()
+    }
+
+    pub fn stderr() -> io::Result<Handle> {
+        error()
+    }
+
+    pub fn as_file(&self) -> &File {
+        unreachable!(ERROR_MESSAGE);
+    }
+
+    pub fn as_file_mut(&self) -> &mut File {
+        unreachable!(ERROR_MESSAGE);
+    }
+}
+
+fn error<T>() -> io::Result<T> {
+    Err(io::Error::new(io::ErrorKind::Other, ERROR_MESSAGE))
+}
diff --git a/src/win.rs b/src/win.rs
new file mode 100644
index 0000000..6924739
--- /dev/null
+++ b/src/win.rs
@@ -0,0 +1,172 @@
+use 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<Key>,
+}
+
+#[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<H: Hasher>(&self, state: &mut H) {
+        self.key.hash(state);
+    }
+}
+
+impl Handle {
+    pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> {
+        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<Handle> {
+        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<Handle> {
+        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> {
+        Handle::from_std_handle(winutil::HandleRef::stdin())
+    }
+
+    pub fn stdout() -> io::Result<Handle> {
+        Handle::from_std_handle(winutil::HandleRef::stdout())
+    }
+
+    pub fn stderr() -> io::Result<Handle> {
+        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(),
+        }
+    }
+}