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