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