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