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