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 || result_error == ERROR_BROKEN_PIPE)
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     else if (::GetLastError() == ERROR_BROKEN_PIPE)
245     {
246         // The write end of a pipe was closed.  This is equivalent to EOF.
247         return_info.Set(0, eConnectionStatusEndOfFile, 0);
248     }
249     else
250     {
251         // An unknown error occured.  Fail out.
252         return_info.Set(0, eConnectionStatusError, ::GetLastError());
253     }
254     goto finish;
255 
256 finish:
257     status = return_info.GetStatus();
258     if (error_ptr)
259         *error_ptr = return_info.GetError();
260 
261     // kBytesAvailableEvent is a manual reset event.  Make sure it gets reset here so that any
262     // subsequent operations don't immediately see bytes available.
263     ResetEvent(m_event_handles[kBytesAvailableEvent]);
264 
265     IncrementFilePointer(return_info.GetBytes());
266     Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION));
267     if (log)
268     {
269         log->Printf("%" PRIxPTR " ConnectionGenericFile::Read()  handle = %" PRIxPTR ", dst = %" PRIxPTR ", dst_len = %" PRIu64
270                     ") => %" PRIu64 ", error = %s",
271                     this, m_file, dst, static_cast<uint64_t>(dst_len), static_cast<uint64_t>(return_info.GetBytes()),
272                     return_info.GetError().AsCString());
273     }
274 
275     return return_info.GetBytes();
276 }
277 
278 size_t
279 ConnectionGenericFile::Write(const void *src, size_t src_len, lldb::ConnectionStatus &status, Error *error_ptr)
280 {
281     ReturnInfo return_info;
282 
283     if (error_ptr)
284         error_ptr->Clear();
285 
286     if (!IsConnected())
287     {
288         return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE);
289         goto finish;
290     }
291 
292     m_overlapped.hEvent = NULL;
293 
294     // Writes are not interruptible like reads are, so just block until it's done.
295     BOOL result = ::WriteFile(m_file, src, src_len, NULL, &m_overlapped);
296     if (!result && ::GetLastError() != ERROR_IO_PENDING)
297     {
298         return_info.Set(0, eConnectionStatusError, ::GetLastError());
299         goto finish;
300     }
301 
302     DWORD bytes_written = 0;
303     if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_written, TRUE))
304     {
305         return_info.Set(bytes_written, eConnectionStatusError, ::GetLastError());
306         goto finish;
307     }
308 
309     return_info.Set(bytes_written, eConnectionStatusSuccess, 0);
310     goto finish;
311 
312 finish:
313     status = return_info.GetStatus();
314     if (error_ptr)
315         *error_ptr = return_info.GetError();
316 
317     IncrementFilePointer(return_info.GetBytes());
318     Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION));
319     if (log)
320     {
321         log->Printf("%" PRIxPTR " ConnectionGenericFile::Write()  handle = %" PRIxPTR ", src = %" PRIxPTR ", src_len = %" PRIu64
322                     ") => %" PRIu64 ", error = %s",
323                     this, m_file, src, static_cast<uint64_t>(src_len), static_cast<uint64_t>(return_info.GetBytes()),
324                     return_info.GetError().AsCString());
325     }
326     return return_info.GetBytes();
327 }
328 
329 bool
330 ConnectionGenericFile::InterruptRead()
331 {
332     return ::SetEvent(m_event_handles[kInterruptEvent]);
333 }
334 
335 void
336 ConnectionGenericFile::IncrementFilePointer(DWORD amount)
337 {
338     LARGE_INTEGER old_pos;
339     old_pos.HighPart = m_overlapped.OffsetHigh;
340     old_pos.LowPart = m_overlapped.Offset;
341     old_pos.QuadPart += amount;
342     m_overlapped.Offset = old_pos.LowPart;
343     m_overlapped.OffsetHigh = old_pos.HighPart;
344 }