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 alot 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     return eConnectionStatusSuccess;
151 }
152 
153 lldb::ConnectionStatus
154 ConnectionGenericFile::Disconnect(Error *error_ptr)
155 {
156     Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION));
157     if (log)
158         log->Printf("%p ConnectionGenericFile::Disconnect ()", static_cast<void *>(this));
159 
160     if (!IsConnected())
161         return eConnectionStatusSuccess;
162 
163     // Reset the handle so that after we unblock any pending reads, subsequent calls to Read() will
164     // see a disconnected state.
165     HANDLE old_file = m_file;
166     m_file = INVALID_HANDLE_VALUE;
167 
168     // Set the disconnect event so that any blocking reads unblock, then cancel any pending IO operations.
169     ::CancelIoEx(old_file, &m_overlapped);
170 
171     // Close the file handle if we owned it, but don't close the event handles.  We could always
172     // reconnect with the same Connection instance.
173     if (m_owns_file)
174         ::CloseHandle(old_file);
175 
176     ::ZeroMemory(&m_file_position, sizeof(m_file_position));
177     m_owns_file = false;
178     return eConnectionStatusSuccess;
179 }
180 
181 size_t
182 ConnectionGenericFile::Read(void *dst, size_t dst_len, uint32_t timeout_usec, lldb::ConnectionStatus &status, Error *error_ptr)
183 {
184     ReturnInfo return_info;
185 
186     if (error_ptr)
187         error_ptr->Clear();
188 
189     if (!IsConnected())
190     {
191         return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE);
192         goto finish;
193     }
194 
195     m_overlapped.hEvent = m_event_handles[kBytesAvailableEvent];
196 
197     BOOL result = ::ReadFile(m_file, dst, dst_len, NULL, &m_overlapped);
198     if (result || ::GetLastError() == ERROR_IO_PENDING)
199     {
200         if (!result)
201         {
202             // The expected return path.  The operation is pending.  Wait for the operation to complete
203             // or be interrupted.
204             TimeValue time_value;
205             time_value.OffsetWithMicroSeconds(timeout_usec);
206             DWORD milliseconds = time_value.milliseconds();
207             result = ::WaitForMultipleObjects(llvm::array_lengthof(m_event_handles), m_event_handles, FALSE, milliseconds);
208             // All of the events are manual reset events, so make sure we reset them to non-signalled.
209             switch (result)
210             {
211                 case WAIT_OBJECT_0 + kBytesAvailableEvent:
212                     break;
213                 case WAIT_OBJECT_0 + kInterruptEvent:
214                     return_info.Set(0, eConnectionStatusInterrupted, 0);
215                     goto finish;
216                 case WAIT_TIMEOUT:
217                     return_info.Set(0, eConnectionStatusTimedOut, 0);
218                     goto finish;
219                 case WAIT_FAILED:
220                     return_info.Set(0, eConnectionStatusError, ::GetLastError());
221                     goto finish;
222             }
223         }
224         // The data is ready.  Figure out how much was read and return;
225         DWORD bytes_read = 0;
226         if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_read, FALSE))
227         {
228             DWORD result_error = ::GetLastError();
229             // ERROR_OPERATION_ABORTED occurs when someone calls Disconnect() during a blocking read.
230             // This triggers a call to CancelIoEx, which causes the operation to complete and the
231             // result to be ERROR_OPERATION_ABORTED.
232             if (result_error == ERROR_HANDLE_EOF || result_error == ERROR_OPERATION_ABORTED)
233                 return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0);
234             else
235                 return_info.Set(bytes_read, eConnectionStatusError, result_error);
236         }
237         else if (bytes_read == 0)
238             return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0);
239         else
240             return_info.Set(bytes_read, eConnectionStatusSuccess, 0);
241 
242         goto finish;
243     }
244 
245     // An unknown error occured.  Fail out.
246     return_info.Set(0, eConnectionStatusError, ::GetLastError());
247     goto finish;
248 
249 finish:
250     status = return_info.GetStatus();
251     if (error_ptr)
252         *error_ptr = return_info.GetError();
253 
254     // kBytesAvailableEvent is a manual reset event.  Make sure it gets reset here so that any
255     // subsequent operations don't immediately see bytes available.
256     ResetEvent(m_event_handles[kBytesAvailableEvent]);
257 
258     IncrementFilePointer(return_info.GetBytes());
259     Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION));
260     if (log)
261     {
262         log->Printf("%" PRIxPTR " ConnectionGenericFile::Read()  handle = %" PRIxPTR ", dst = %" PRIxPTR ", dst_len = %" PRIu64
263                     ") => %" PRIu64 ", error = %s",
264                     this, m_file, dst, static_cast<uint64_t>(dst_len), static_cast<uint64_t>(return_info.GetBytes()),
265                     return_info.GetError().AsCString());
266     }
267 
268     return return_info.GetBytes();
269 }
270 
271 size_t
272 ConnectionGenericFile::Write(const void *src, size_t src_len, lldb::ConnectionStatus &status, Error *error_ptr)
273 {
274     ReturnInfo return_info;
275 
276     if (error_ptr)
277         error_ptr->Clear();
278 
279     if (!IsConnected())
280     {
281         return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE);
282         goto finish;
283     }
284 
285     m_overlapped.hEvent = NULL;
286 
287     // Writes are not interruptible like reads are, so just block until it's done.
288     BOOL result = ::WriteFile(m_file, src, src_len, NULL, &m_overlapped);
289     if (!result && ::GetLastError() != ERROR_IO_PENDING)
290     {
291         return_info.Set(0, eConnectionStatusError, ::GetLastError());
292         goto finish;
293     }
294 
295     DWORD bytes_written = 0;
296     if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_written, TRUE))
297     {
298         return_info.Set(bytes_written, eConnectionStatusError, ::GetLastError());
299         goto finish;
300     }
301 
302     return_info.Set(bytes_written, eConnectionStatusSuccess, 0);
303     goto finish;
304 
305 finish:
306     status = return_info.GetStatus();
307     if (error_ptr)
308         *error_ptr = return_info.GetError();
309 
310     IncrementFilePointer(return_info.GetBytes());
311     Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION));
312     if (log)
313     {
314         log->Printf("%" PRIxPTR " ConnectionGenericFile::Write()  handle = %" PRIxPTR ", src = %" PRIxPTR ", src_len = %" PRIu64
315                     ") => %" PRIu64 ", error = %s",
316                     this, m_file, src, static_cast<uint64_t>(src_len), static_cast<uint64_t>(return_info.GetBytes()),
317                     return_info.GetError().AsCString());
318     }
319     return return_info.GetBytes();
320 }
321 
322 bool
323 ConnectionGenericFile::InterruptRead()
324 {
325     return ::SetEvent(m_event_handles[kInterruptEvent]);
326 }
327 
328 void
329 ConnectionGenericFile::IncrementFilePointer(DWORD amount)
330 {
331     LARGE_INTEGER old_pos;
332     old_pos.HighPart = m_overlapped.OffsetHigh;
333     old_pos.LowPart = m_overlapped.Offset;
334     old_pos.QuadPart += amount;
335     m_overlapped.Offset = old_pos.LowPart;
336     m_overlapped.OffsetHigh = old_pos.HighPart;
337 }