1 use std::fs::File;
2 use std::hash::{Hash, Hasher};
3 use std::io;
4 use std::os::windows::io::{AsRawHandle, IntoRawHandle, RawHandle};
5 use std::path::Path;
6 
7 use winapi_util as winutil;
8 
9 // For correctness, it is critical that both file handles remain open while
10 // their attributes are checked for equality. In particular, the file index
11 // numbers on a Windows stat object are not guaranteed to remain stable over
12 // time.
13 //
14 // See the docs and remarks on MSDN:
15 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx
16 //
17 // It gets worse. It appears that the index numbers are not always
18 // guaranteed to be unique. Namely, ReFS uses 128 bit numbers for unique
19 // identifiers. This requires a distinct syscall to get `FILE_ID_INFO`
20 // documented here:
21 // https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx
22 //
23 // It seems straight-forward enough to modify this code to use
24 // `FILE_ID_INFO` when available (minimum Windows Server 2012), but I don't
25 // have access to such Windows machines.
26 //
27 // Two notes.
28 //
29 // 1. Java's NIO uses the approach implemented here and appears to ignore
30 //    `FILE_ID_INFO` altogether. So Java's NIO and this code are
31 //    susceptible to bugs when running on a file system where
32 //    `nFileIndex{Low,High}` are not unique.
33 //
34 // 2. LLVM has a bug where they fetch the id of a file and continue to use
35 //    it even after the handle has been closed, so that uniqueness is no
36 //    longer guaranteed (when `nFileIndex{Low,High}` are unique).
37 //    bug report: http://lists.llvm.org/pipermail/llvm-bugs/2014-December/037218.html
38 //
39 // All said and done, checking whether two files are the same on Windows
40 // seems quite tricky. Moreover, even if the code is technically incorrect,
41 // it seems like the chances of actually observing incorrect behavior are
42 // extremely small. Nevertheless, we mitigate this by checking size too.
43 //
44 // In the case where this code is erroneous, two files will be reported
45 // as equivalent when they are in fact distinct. This will cause the loop
46 // detection code to report a false positive, which will prevent descending
47 // into the offending directory. As far as failure modes goes, this isn't
48 // that bad.
49 
50 #[derive(Debug)]
51 pub struct Handle {
52     kind: HandleKind,
53     key: Option<Key>,
54 }
55 
56 #[derive(Debug)]
57 enum HandleKind {
58     /// Used when opening a file or acquiring ownership of a file.
59     Owned(winutil::Handle),
60     /// Used for stdio.
61     Borrowed(winutil::HandleRef),
62 }
63 
64 #[derive(Debug, Eq, PartialEq, Hash)]
65 struct Key {
66     volume: u64,
67     index: u64,
68 }
69 
70 impl Eq for Handle {}
71 
72 impl PartialEq for Handle {
eq(&self, other: &Handle) -> bool73     fn eq(&self, other: &Handle) -> bool {
74         // Need this branch to satisfy `Eq` since `Handle`s with
75         // `key.is_none()` wouldn't otherwise.
76         if self as *const Handle == other as *const Handle {
77             return true;
78         } else if self.key.is_none() || other.key.is_none() {
79             return false;
80         }
81         self.key == other.key
82     }
83 }
84 
85 impl AsRawHandle for crate::Handle {
as_raw_handle(&self) -> RawHandle86     fn as_raw_handle(&self) -> RawHandle {
87         match self.0.kind {
88             HandleKind::Owned(ref h) => h.as_raw_handle(),
89             HandleKind::Borrowed(ref h) => h.as_raw_handle(),
90         }
91     }
92 }
93 
94 impl IntoRawHandle for crate::Handle {
into_raw_handle(self) -> RawHandle95     fn into_raw_handle(self) -> RawHandle {
96         match self.0.kind {
97             HandleKind::Owned(h) => h.into_raw_handle(),
98             HandleKind::Borrowed(h) => h.as_raw_handle(),
99         }
100     }
101 }
102 
103 impl Hash for Handle {
hash<H: Hasher>(&self, state: &mut H)104     fn hash<H: Hasher>(&self, state: &mut H) {
105         self.key.hash(state);
106     }
107 }
108 
109 impl Handle {
from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle>110     pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> {
111         let h = winutil::Handle::from_path_any(p)?;
112         let info = winutil::file::information(&h)?;
113         Ok(Handle::from_info(HandleKind::Owned(h), info))
114     }
115 
from_file(file: File) -> io::Result<Handle>116     pub fn from_file(file: File) -> io::Result<Handle> {
117         let h = winutil::Handle::from_file(file);
118         let info = winutil::file::information(&h)?;
119         Ok(Handle::from_info(HandleKind::Owned(h), info))
120     }
121 
from_std_handle(h: winutil::HandleRef) -> io::Result<Handle>122     fn from_std_handle(h: winutil::HandleRef) -> io::Result<Handle> {
123         match winutil::file::information(&h) {
124             Ok(info) => Ok(Handle::from_info(HandleKind::Borrowed(h), info)),
125             // In a Windows console, if there is no pipe attached to a STD
126             // handle, then GetFileInformationByHandle will return an error.
127             // We don't really care. The only thing we care about is that
128             // this handle is never equivalent to any other handle, which is
129             // accomplished by setting key to None.
130             Err(_) => Ok(Handle { kind: HandleKind::Borrowed(h), key: None }),
131         }
132     }
133 
from_info( kind: HandleKind, info: winutil::file::Information, ) -> Handle134     fn from_info(
135         kind: HandleKind,
136         info: winutil::file::Information,
137     ) -> Handle {
138         Handle {
139             kind: kind,
140             key: Some(Key {
141                 volume: info.volume_serial_number(),
142                 index: info.file_index(),
143             }),
144         }
145     }
146 
stdin() -> io::Result<Handle>147     pub fn stdin() -> io::Result<Handle> {
148         Handle::from_std_handle(winutil::HandleRef::stdin())
149     }
150 
stdout() -> io::Result<Handle>151     pub fn stdout() -> io::Result<Handle> {
152         Handle::from_std_handle(winutil::HandleRef::stdout())
153     }
154 
stderr() -> io::Result<Handle>155     pub fn stderr() -> io::Result<Handle> {
156         Handle::from_std_handle(winutil::HandleRef::stderr())
157     }
158 
as_file(&self) -> &File159     pub fn as_file(&self) -> &File {
160         match self.kind {
161             HandleKind::Owned(ref h) => h.as_file(),
162             HandleKind::Borrowed(ref h) => h.as_file(),
163         }
164     }
165 
as_file_mut(&mut self) -> &mut File166     pub fn as_file_mut(&mut self) -> &mut File {
167         match self.kind {
168             HandleKind::Owned(ref mut h) => h.as_file_mut(),
169             HandleKind::Borrowed(ref mut h) => h.as_file_mut(),
170         }
171     }
172 }
173