same_file/win.rs
1use std::fs::File;
2use std::hash::{Hash, Hasher};
3use std::io;
4use std::os::windows::io::{AsRawHandle, IntoRawHandle, RawHandle};
5use std::path::Path;
6
7use 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)]
51pub struct Handle {
52 kind: HandleKind,
53 key: Option<Key>,
54}
55
56#[derive(Debug)]
57enum 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)]
65struct Key {
66 volume: u64,
67 index: u64,
68}
69
70impl Eq for Handle {}
71
72impl PartialEq for Handle {
73 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
85impl AsRawHandle for crate::Handle {
86 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
94impl IntoRawHandle for crate::Handle {
95 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
103impl Hash for Handle {
104 fn hash<H: Hasher>(&self, state: &mut H) {
105 self.key.hash(state);
106 }
107}
108
109impl 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
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
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
134 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
147 pub fn stdin() -> io::Result<Handle> {
148 Handle::from_std_handle(winutil::HandleRef::stdin())
149 }
150
151 pub fn stdout() -> io::Result<Handle> {
152 Handle::from_std_handle(winutil::HandleRef::stdout())
153 }
154
155 pub fn stderr() -> io::Result<Handle> {
156 Handle::from_std_handle(winutil::HandleRef::stderr())
157 }
158
159 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
166 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}