1 //===-- ConnectionGenericFileWindows.cpp ------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "lldb/Host/windows/ConnectionGenericFileWindows.h" 11 #include "lldb/Utility/Log.h" 12 #include "lldb/Utility/Status.h" 13 #include "lldb/Utility/Timeout.h" 14 15 #include "llvm/ADT/STLExtras.h" 16 #include "llvm/ADT/StringRef.h" 17 #include "llvm/Support/ConvertUTF.h" 18 19 using namespace lldb; 20 using namespace lldb_private; 21 22 namespace { 23 // This is a simple helper class to package up the information needed to return 24 // from a Read/Write operation function. Since there is a lot of code to be 25 // run before exit regardless of whether the operation succeeded or failed, 26 // combined with many possible return paths, this is the cleanest way to 27 // represent it. 28 class ReturnInfo { 29 public: 30 void Set(size_t bytes, ConnectionStatus status, DWORD error_code) { 31 m_error.SetError(error_code, eErrorTypeWin32); 32 m_bytes = bytes; 33 m_status = status; 34 } 35 36 void Set(size_t bytes, ConnectionStatus status, llvm::StringRef error_msg) { 37 m_error.SetErrorString(error_msg.data()); 38 m_bytes = bytes; 39 m_status = status; 40 } 41 42 size_t GetBytes() const { return m_bytes; } 43 ConnectionStatus GetStatus() const { return m_status; } 44 const Status &GetError() const { return m_error; } 45 46 private: 47 Status m_error; 48 size_t m_bytes; 49 ConnectionStatus m_status; 50 }; 51 } 52 53 ConnectionGenericFile::ConnectionGenericFile() 54 : m_file(INVALID_HANDLE_VALUE), m_owns_file(false) { 55 ::ZeroMemory(&m_overlapped, sizeof(m_overlapped)); 56 ::ZeroMemory(&m_file_position, sizeof(m_file_position)); 57 InitializeEventHandles(); 58 } 59 60 ConnectionGenericFile::ConnectionGenericFile(lldb::file_t file, bool owns_file) 61 : m_file(file), m_owns_file(owns_file) { 62 ::ZeroMemory(&m_overlapped, sizeof(m_overlapped)); 63 ::ZeroMemory(&m_file_position, sizeof(m_file_position)); 64 InitializeEventHandles(); 65 } 66 67 ConnectionGenericFile::~ConnectionGenericFile() { 68 if (m_owns_file && IsConnected()) 69 ::CloseHandle(m_file); 70 71 ::CloseHandle(m_event_handles[kBytesAvailableEvent]); 72 ::CloseHandle(m_event_handles[kInterruptEvent]); 73 } 74 75 void ConnectionGenericFile::InitializeEventHandles() { 76 m_event_handles[kInterruptEvent] = CreateEvent(NULL, FALSE, FALSE, NULL); 77 78 // Note, we should use a manual reset event for the hEvent argument of the 79 // OVERLAPPED. This is because both WaitForMultipleObjects and 80 // GetOverlappedResult (if you set the bWait argument to TRUE) will wait for 81 // the event to be signalled. If we use an auto-reset event, 82 // WaitForMultipleObjects will reset the event, return successfully, and then 83 // GetOverlappedResult will block since the event is no longer signalled. 84 m_event_handles[kBytesAvailableEvent] = 85 ::CreateEvent(NULL, TRUE, FALSE, NULL); 86 } 87 88 bool ConnectionGenericFile::IsConnected() const { 89 return m_file && (m_file != INVALID_HANDLE_VALUE); 90 } 91 92 lldb::ConnectionStatus ConnectionGenericFile::Connect(llvm::StringRef path, 93 Status *error_ptr) { 94 Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); 95 if (log) 96 log->Printf("%p ConnectionGenericFile::Connect (url = '%s')", 97 static_cast<void *>(this), path.str().c_str()); 98 99 if (!path.consume_front("file://")) { 100 if (error_ptr) 101 error_ptr->SetErrorStringWithFormat("unsupported connection URL: '%s'", 102 path.str().c_str()); 103 return eConnectionStatusError; 104 } 105 106 if (IsConnected()) { 107 ConnectionStatus status = Disconnect(error_ptr); 108 if (status != eConnectionStatusSuccess) 109 return status; 110 } 111 112 // Open the file for overlapped access. If it does not exist, create it. We 113 // open it overlapped so that we can issue asynchronous reads and then use 114 // WaitForMultipleObjects to allow the read to be interrupted by an event 115 // object. 116 std::wstring wpath; 117 if (!llvm::ConvertUTF8toWide(path, wpath)) { 118 if (error_ptr) 119 error_ptr->SetError(1, eErrorTypeGeneric); 120 return eConnectionStatusError; 121 } 122 m_file = ::CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 123 FILE_SHARE_READ, NULL, OPEN_ALWAYS, 124 FILE_FLAG_OVERLAPPED, NULL); 125 if (m_file == INVALID_HANDLE_VALUE) { 126 if (error_ptr) 127 error_ptr->SetError(::GetLastError(), eErrorTypeWin32); 128 return eConnectionStatusError; 129 } 130 131 m_owns_file = true; 132 m_uri.assign(path); 133 return eConnectionStatusSuccess; 134 } 135 136 lldb::ConnectionStatus ConnectionGenericFile::Disconnect(Status *error_ptr) { 137 Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); 138 if (log) 139 log->Printf("%p ConnectionGenericFile::Disconnect ()", 140 static_cast<void *>(this)); 141 142 if (!IsConnected()) 143 return eConnectionStatusSuccess; 144 145 // Reset the handle so that after we unblock any pending reads, subsequent 146 // calls to Read() will see a disconnected state. 147 HANDLE old_file = m_file; 148 m_file = INVALID_HANDLE_VALUE; 149 150 // Set the disconnect event so that any blocking reads unblock, then cancel 151 // any pending IO operations. 152 ::CancelIoEx(old_file, &m_overlapped); 153 154 // Close the file handle if we owned it, but don't close the event handles. 155 // We could always reconnect with the same Connection instance. 156 if (m_owns_file) 157 ::CloseHandle(old_file); 158 159 ::ZeroMemory(&m_file_position, sizeof(m_file_position)); 160 m_owns_file = false; 161 m_uri.clear(); 162 return eConnectionStatusSuccess; 163 } 164 165 size_t ConnectionGenericFile::Read(void *dst, size_t dst_len, 166 const Timeout<std::micro> &timeout, 167 lldb::ConnectionStatus &status, 168 Status *error_ptr) { 169 ReturnInfo return_info; 170 BOOL result = 0; 171 DWORD bytes_read = 0; 172 173 if (error_ptr) 174 error_ptr->Clear(); 175 176 if (!IsConnected()) { 177 return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE); 178 goto finish; 179 } 180 181 m_overlapped.hEvent = m_event_handles[kBytesAvailableEvent]; 182 183 result = ::ReadFile(m_file, dst, dst_len, NULL, &m_overlapped); 184 if (result || ::GetLastError() == ERROR_IO_PENDING) { 185 if (!result) { 186 // The expected return path. The operation is pending. Wait for the 187 // operation to complete or be interrupted. 188 DWORD milliseconds = 189 timeout 190 ? std::chrono::duration_cast<std::chrono::milliseconds>(*timeout) 191 .count() 192 : INFINITE; 193 DWORD wait_result = 194 ::WaitForMultipleObjects(llvm::array_lengthof(m_event_handles), 195 m_event_handles, FALSE, milliseconds); 196 // All of the events are manual reset events, so make sure we reset them 197 // to non-signalled. 198 switch (wait_result) { 199 case WAIT_OBJECT_0 + kBytesAvailableEvent: 200 break; 201 case WAIT_OBJECT_0 + kInterruptEvent: 202 return_info.Set(0, eConnectionStatusInterrupted, 0); 203 goto finish; 204 case WAIT_TIMEOUT: 205 return_info.Set(0, eConnectionStatusTimedOut, 0); 206 goto finish; 207 case WAIT_FAILED: 208 return_info.Set(0, eConnectionStatusError, ::GetLastError()); 209 goto finish; 210 } 211 } 212 // The data is ready. Figure out how much was read and return; 213 if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_read, FALSE)) { 214 DWORD result_error = ::GetLastError(); 215 // ERROR_OPERATION_ABORTED occurs when someone calls Disconnect() during 216 // a blocking read. This triggers a call to CancelIoEx, which causes the 217 // operation to complete and the result to be ERROR_OPERATION_ABORTED. 218 if (result_error == ERROR_HANDLE_EOF || 219 result_error == ERROR_OPERATION_ABORTED || 220 result_error == ERROR_BROKEN_PIPE) 221 return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0); 222 else 223 return_info.Set(bytes_read, eConnectionStatusError, result_error); 224 } else if (bytes_read == 0) 225 return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0); 226 else 227 return_info.Set(bytes_read, eConnectionStatusSuccess, 0); 228 229 goto finish; 230 } else if (::GetLastError() == ERROR_BROKEN_PIPE) { 231 // The write end of a pipe was closed. This is equivalent to EOF. 232 return_info.Set(0, eConnectionStatusEndOfFile, 0); 233 } else { 234 // An unknown error occurred. Fail out. 235 return_info.Set(0, eConnectionStatusError, ::GetLastError()); 236 } 237 goto finish; 238 239 finish: 240 status = return_info.GetStatus(); 241 if (error_ptr) 242 *error_ptr = return_info.GetError(); 243 244 // kBytesAvailableEvent is a manual reset event. Make sure it gets reset 245 // here so that any subsequent operations don't immediately see bytes 246 // available. 247 ResetEvent(m_event_handles[kBytesAvailableEvent]); 248 249 IncrementFilePointer(return_info.GetBytes()); 250 Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); 251 if (log) { 252 log->Printf("%p ConnectionGenericFile::Read() handle = %p, dst = %p, " 253 "dst_len = %zu) => %zu, error = %s", 254 this, m_file, dst, dst_len, return_info.GetBytes(), 255 return_info.GetError().AsCString()); 256 } 257 258 return return_info.GetBytes(); 259 } 260 261 size_t ConnectionGenericFile::Write(const void *src, size_t src_len, 262 lldb::ConnectionStatus &status, 263 Status *error_ptr) { 264 ReturnInfo return_info; 265 DWORD bytes_written = 0; 266 BOOL result = 0; 267 268 if (error_ptr) 269 error_ptr->Clear(); 270 271 if (!IsConnected()) { 272 return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE); 273 goto finish; 274 } 275 276 m_overlapped.hEvent = NULL; 277 278 // Writes are not interruptible like reads are, so just block until it's 279 // done. 280 result = ::WriteFile(m_file, src, src_len, NULL, &m_overlapped); 281 if (!result && ::GetLastError() != ERROR_IO_PENDING) { 282 return_info.Set(0, eConnectionStatusError, ::GetLastError()); 283 goto finish; 284 } 285 286 if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_written, TRUE)) { 287 return_info.Set(bytes_written, eConnectionStatusError, ::GetLastError()); 288 goto finish; 289 } 290 291 return_info.Set(bytes_written, eConnectionStatusSuccess, 0); 292 goto finish; 293 294 finish: 295 status = return_info.GetStatus(); 296 if (error_ptr) 297 *error_ptr = return_info.GetError(); 298 299 IncrementFilePointer(return_info.GetBytes()); 300 Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); 301 if (log) { 302 log->Printf("%p ConnectionGenericFile::Write() handle = %p, src = %p, " 303 "src_len = %zu) => %zu, error = %s", 304 this, m_file, src, src_len, return_info.GetBytes(), 305 return_info.GetError().AsCString()); 306 } 307 return return_info.GetBytes(); 308 } 309 310 std::string ConnectionGenericFile::GetURI() { return m_uri; } 311 312 bool ConnectionGenericFile::InterruptRead() { 313 return ::SetEvent(m_event_handles[kInterruptEvent]); 314 } 315 316 void ConnectionGenericFile::IncrementFilePointer(DWORD amount) { 317 LARGE_INTEGER old_pos; 318 old_pos.HighPart = m_overlapped.OffsetHigh; 319 old_pos.LowPart = m_overlapped.Offset; 320 old_pos.QuadPart += amount; 321 m_overlapped.Offset = old_pos.LowPart; 322 m_overlapped.OffsetHigh = old_pos.HighPart; 323 } 324