]> git.proxmox.com Git - rustc.git/blob - src/vendor/same-file/src/win.rs
New upstream version 1.19.0+dfsg1
[rustc.git] / src / vendor / same-file / src / win.rs
1 use std::fs::{File, OpenOptions};
2 use std::io;
3 use std::mem;
4 use std::os::windows::fs::OpenOptionsExt;
5 use std::os::windows::io::{
6 AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle,
7 };
8 use std::path::Path;
9
10 use kernel32::{GetFileInformationByHandle, GetStdHandle};
11 use winapi::fileapi::BY_HANDLE_FILE_INFORMATION;
12 use winapi::minwindef::DWORD;
13 use winapi::winbase::{
14 FILE_FLAG_BACKUP_SEMANTICS,
15 STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
16 };
17
18 // For correctness, it is critical that both file handles remain open while
19 // their attributes are checked for equality. In particular, the file index
20 // numbers on a Windows stat object are not guaranteed to remain stable over
21 // time.
22 //
23 // See the docs and remarks on MSDN:
24 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx
25 //
26 // It gets worse. It appears that the index numbers are not always
27 // guaranteed to be unqiue. Namely, ReFS uses 128 bit numbers for unique
28 // identifiers. This requires a distinct syscall to get `FILE_ID_INFO`
29 // documented here:
30 // https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx
31 //
32 // It seems straight-forward enough to modify this code to use
33 // `FILE_ID_INFO` when available (minimum Windows Server 2012), but I don't
34 // have access to such Windows machines.
35 //
36 // Two notes.
37 //
38 // 1. Java's NIO uses the approach implemented here and appears to ignore
39 // `FILE_ID_INFO` altogether. So Java's NIO and this code are
40 // susceptible to bugs when running on a file system where
41 // `nFileIndex{Low,High}` are not unique.
42 //
43 // 2. LLVM has a bug where they fetch the id of a file and continue to use
44 // it even after the handle has been closed, so that uniqueness is no
45 // longer guaranteed (when `nFileIndex{Low,High}` are unique).
46 // bug report: http://lists.llvm.org/pipermail/llvm-bugs/2014-December/037218.html
47 //
48 // All said and done, checking whether two files are the same on Windows
49 // seems quite tricky. Moreover, even if the code is technically incorrect,
50 // it seems like the chances of actually observing incorrect behavior are
51 // extremely small. Nevertheless, we mitigate this by checking size too.
52 //
53 // In the case where this code is erroneous, two files will be reported
54 // as equivalent when they are in fact distinct. This will cause the loop
55 // detection code to report a false positive, which will prevent descending
56 // into the offending directory. As far as failure modes goes, this isn't
57 // that bad.
58
59 #[derive(Debug)]
60 pub struct Handle {
61 file: Option<File>,
62 // If is_std is true, then we don't drop the corresponding File since it
63 // will close the handle.
64 is_std: bool,
65 key: Option<Key>,
66 }
67
68 #[derive(Debug, Eq, PartialEq)]
69 struct Key {
70 volume: DWORD,
71 idx_high: DWORD,
72 idx_low: DWORD,
73 }
74
75 impl Drop for Handle {
76 fn drop(&mut self) {
77 if self.is_std {
78 self.file.take().unwrap().into_raw_handle();
79 }
80 }
81 }
82
83 impl Eq for Handle {}
84
85 impl PartialEq for Handle {
86 fn eq(&self, other: &Handle) -> bool {
87 if self.key.is_none() || other.key.is_none() {
88 return false;
89 }
90 self.key == other.key
91 }
92 }
93
94 impl AsRawHandle for ::Handle {
95 fn as_raw_handle(&self) -> RawHandle {
96 self.0.file.as_ref().take().unwrap().as_raw_handle()
97 }
98 }
99
100 impl IntoRawHandle for ::Handle {
101 fn into_raw_handle(mut self) -> RawHandle {
102 self.0.file.take().unwrap().into_raw_handle()
103 }
104 }
105
106 impl Handle {
107 pub fn from_path<P: AsRef<Path>>(p: P) -> io::Result<Handle> {
108 let file = try!(OpenOptions::new()
109 .read(true)
110 // Necessary in order to support opening directory paths.
111 .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
112 .open(p));
113 Handle::from_file(file)
114 }
115
116 pub fn from_file(file: File) -> io::Result<Handle> {
117 file_info(&file).map(|info| Handle::from_file_info(file, false, info))
118 }
119
120 fn from_std_handle(file: File) -> io::Result<Handle> {
121 match file_info(&file) {
122 Ok(info) => Ok(Handle::from_file_info(file, true, info)),
123 // In a Windows console, if there is no pipe attached to a STD
124 // handle, then GetFileInformationByHandle will return an error.
125 // We don't really care. The only thing we care about is that
126 // this handle is never equivalent to any other handle, which is
127 // accomplished by setting key to None.
128 Err(_) => Ok(Handle { file: Some(file), is_std: true, key: None }),
129 }
130 }
131
132 fn from_file_info(
133 file: File,
134 is_std: bool,
135 info: BY_HANDLE_FILE_INFORMATION,
136 ) -> Handle {
137 Handle {
138 file: Some(file),
139 is_std: is_std,
140 key: Some(Key {
141 volume: info.dwVolumeSerialNumber,
142 idx_high: info.nFileIndexHigh,
143 idx_low: info.nFileIndexLow,
144 }),
145 }
146 }
147
148 pub fn stdin() -> io::Result<Handle> {
149 Handle::from_std_handle(unsafe {
150 File::from_raw_handle(GetStdHandle(STD_INPUT_HANDLE))
151 })
152 }
153
154 pub fn stdout() -> io::Result<Handle> {
155 Handle::from_std_handle(unsafe {
156 File::from_raw_handle(GetStdHandle(STD_OUTPUT_HANDLE))
157 })
158 }
159
160 pub fn stderr() -> io::Result<Handle> {
161 Handle::from_std_handle(unsafe {
162 File::from_raw_handle(GetStdHandle(STD_ERROR_HANDLE))
163 })
164 }
165
166 pub fn as_file(&self) -> &File {
167 self.file.as_ref().take().unwrap()
168 }
169
170 pub fn as_file_mut(&mut self) -> &mut File {
171 self.file.as_mut().take().unwrap()
172 }
173 }
174
175 fn file_info(file: &File) -> io::Result<BY_HANDLE_FILE_INFORMATION> {
176 let (r, info) = unsafe {
177 let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
178 (GetFileInformationByHandle(file.as_raw_handle(), &mut info), info)
179 };
180 if r == 0 {
181 Err(io::Error::last_os_error())
182 } else {
183 Ok(info)
184 }
185 }