From c18459e66e8e71a8765bb9b8e2b3d2ba61855a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Thu, 16 Aug 2018 18:04:07 +0200 Subject: [PATCH] Fix extcap initialization deadlock On Windows the code calling extcap worked as follows: 1. Create stdout and stderr pipes with default buffer size 2. Execute extcap redirecting output to the pipes 3. Wait for extcap process to exit 4. Read the data from stdout pipe This resulted in deadlock when the extcap wrote more data than the pipe could buffer. This was especially seen with USBPcap as it is quite normal to have plenty of USB devices connected. Fix the issue by contantly reading the stdout data and storing it in GString. To prevent similar deadlock on the stderr, the stderr data is being constantly monitored as well (and discarded). Change-Id: I0f93e6d79617cef0e828aef2b96fad2757227923 Bug: 14657 Reviewed-on: https://code.wireshark.org/review/29159 Petri-Dish: Pascal Quantin Tested-by: Petri Dish Buildbot Reviewed-by: Roland Knall --- wsutil/ws_pipe.c | 78 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/wsutil/ws_pipe.c b/wsutil/ws_pipe.c index bd712bcb85..3deb3a0dbc 100644 --- a/wsutil/ws_pipe.c +++ b/wsutil/ws_pipe.c @@ -120,17 +120,83 @@ gboolean ws_pipe_spawn_sync(gchar *dirname, gchar *command, gint argc, gchar **a if (win32_create_process(NULL, winargs->str, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo)) { - gchar* buffer; + gchar* buffer = (gchar*)g_malloc(BUFFER_SIZE); + HANDLE handles[] = {processInfo.hProcess, child_stdout_rd, child_stderr_rd}; + DWORD dw; + DWORD bytes_read; + DWORD bytes_avail; + GString *output_string = g_string_new(NULL); - WaitForSingleObject(processInfo.hProcess, INFINITE); - buffer = (gchar*)g_malloc(BUFFER_SIZE); - status = ws_read_string_from_pipe(child_stdout_rd, buffer, BUFFER_SIZE); - if (status) + for (;;) { - local_output = g_strdup_printf("%s", buffer); + dw = WaitForMultipleObjects(G_N_ELEMENTS(handles), handles, FALSE, INFINITE); + int idx = dw - WAIT_OBJECT_0; + if ((idx >= 0) && (idx < G_N_ELEMENTS(handles))) + { + if (handles[idx] == processInfo.hProcess) + { + /* Process finished. Nothing left to do here. */ + break; + } + else if (handles[idx] == child_stdout_rd) + { + if (PeekNamedPipe(child_stdout_rd, NULL, 0, NULL, &bytes_avail, NULL)) + { + if (bytes_avail > 0) + { + bytes_avail = min(bytes_avail, BUFFER_SIZE); + if (ReadFile(child_stdout_rd, &buffer[0], bytes_avail, &bytes_read, NULL)) + { + g_string_append_len(output_string, buffer, bytes_read); + } + } + } + } + else if (handles[idx] == child_stderr_rd) + { + /* Discard the stderr data just like non-windows version of this function does. */ + if (PeekNamedPipe(child_stderr_rd, NULL, 0, NULL, &bytes_avail, NULL)) + { + if (bytes_avail > 0) + { + bytes_avail = min(bytes_avail, BUFFER_SIZE); + ReadFile(child_stderr_rd, &buffer[0], bytes_avail, &bytes_read, NULL); + } + } + } + } + else + { + g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "WaitForMultipleObjects returned 0x%08X. Error %d", dw, GetLastError()); + break; + } } + + /* At this point the process is finished but there might still be unread data in the pipe. */ + while (PeekNamedPipe(child_stdout_rd, NULL, 0, NULL, &bytes_avail, NULL)) + { + if (bytes_avail == 0) + { + /* Pipe is drained. */ + break; + } + bytes_avail = min(bytes_avail, BUFFER_SIZE); + if (ReadFile(child_stdout_rd, &buffer[0], BUFFER_SIZE, &bytes_read, NULL)) + { + g_string_append_len(output_string, buffer, bytes_read); + } + } + g_free(buffer); + status = GetExitCodeProcess(processInfo.hProcess, &dw); + if (status && dw != 0) + { + status = FALSE; + } + + local_output = g_string_free(output_string, FALSE); + CloseHandle(child_stdout_rd); CloseHandle(child_stdout_wr); CloseHandle(child_stderr_rd);