![Tomasz Moń](/assets/img/avatar_default.png)
Windows processes inherit all inheritable handles when a new process is created using CreateProcess() with bInheritHandles set to TRUE. This can lead to undesired object lifetime extension. That is, the child process will keep ineritable handles alive even if it does not use them. Up to Windows Vista it was not possible explicitly list handles that should be inherited. Wireshark no longer works on Windows releases earlier than Vista, so use the new API without checking Windows version. Require all callers to win32_create_process() to pass in the list of handles to inherit. Set the listed handles as inheritable shortly before calling CreateProcess() and set them as not inheritable shortly after the process is created. This minimizes possibility for other callers (especially in 3rd party libraries) to inherit handles by accident. Do not terminate mmdbresolve process on exit. Instead rely on process exit when EOF is received on standard input. Previously the EOF was never received because mmdbresolve inherited both ends of standard input pipe, i.e. the fact that Wireshark closed the write end was not observed by mmdbresolve because mmdbresolve kept write handle the standard input pipe open.
859 lines
27 KiB
C
859 lines
27 KiB
C
/* ws_pipe.c
|
|
*
|
|
* Routines for handling pipes.
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include <config.h>
|
|
#define WS_LOG_DOMAIN LOG_DOMAIN_CAPTURE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <fcntl.h> /* for _O_BINARY */
|
|
#include <wsutil/win32-utils.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
|
|
#ifdef __linux__
|
|
#define HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG
|
|
#include <fcntl.h>
|
|
#include <sys/syscall.h> /* for syscall and SYS_getdents64 */
|
|
#include <wsutil/file_util.h> /* for ws_open -> open to pacify checkAPIs.pl */
|
|
#endif
|
|
|
|
#include "wsutil/filesystem.h"
|
|
#include "wsutil/ws_pipe.h"
|
|
#include "wsutil/wslog.h"
|
|
|
|
#ifdef HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG
|
|
struct linux_dirent64 {
|
|
guint64 d_ino; /* 64-bit inode number */
|
|
guint64 d_off; /* 64-bit offset to next structure */
|
|
unsigned short d_reclen; /* Size of this dirent */
|
|
unsigned char d_type; /* File type */
|
|
char d_name[]; /* Filename (null-terminated) */
|
|
};
|
|
|
|
/* Async-signal-safe string to integer conversion. */
|
|
static gint
|
|
filename_to_fd(const char *p)
|
|
{
|
|
char c;
|
|
int fd = 0;
|
|
const int cutoff = G_MAXINT / 10;
|
|
const int cutlim = G_MAXINT % 10;
|
|
|
|
if (*p == '\0')
|
|
return -1;
|
|
|
|
while ((c = *p++) != '\0') {
|
|
if (!g_ascii_isdigit(c))
|
|
return -1;
|
|
c -= '0';
|
|
|
|
/* Check for overflow. */
|
|
if (fd > cutoff || (fd == cutoff && c > cutlim))
|
|
return -1;
|
|
|
|
fd = fd * 10 + c;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void
|
|
close_non_standard_fds_linux(gpointer user_data _U_)
|
|
{
|
|
/*
|
|
* GLib 2.14.2 and newer (up to at least GLib 2.58.1) on Linux with multiple
|
|
* threads can deadlock in the child process due to use of opendir (which
|
|
* is not async-signal-safe). To avoid this, disable the broken code path
|
|
* and manually close file descriptors using async-signal-safe code only.
|
|
* Use CLOEXEC to allow reporting of execve errors to the parent via a pipe.
|
|
* https://gitlab.gnome.org/GNOME/glib/issues/1014
|
|
* https://gitlab.gnome.org/GNOME/glib/merge_requests/490
|
|
*/
|
|
int dir_fd = ws_open("/proc/self/fd", O_RDONLY | O_DIRECTORY);
|
|
if (dir_fd >= 0) {
|
|
char buf[4096];
|
|
int nread, fd;
|
|
struct linux_dirent64 *de;
|
|
|
|
while ((nread = (int) syscall(SYS_getdents64, dir_fd, buf, sizeof(buf))) > 0) {
|
|
for (int pos = 0; pos < nread; pos += de->d_reclen) {
|
|
de = (struct linux_dirent64 *)(buf + pos);
|
|
fd = filename_to_fd(de->d_name);
|
|
if (fd > STDERR_FILENO && fd != dir_fd) {
|
|
/* Close all other (valid) file descriptors above stderr. */
|
|
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
}
|
|
}
|
|
}
|
|
|
|
close(dir_fd);
|
|
} else {
|
|
/* Slow fallback in case /proc is not mounted */
|
|
for (int fd = STDERR_FILENO + 1; fd < getdtablesize(); fd++) {
|
|
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
static ULONG pipe_serial_number;
|
|
|
|
/* Alternative for CreatePipe() where read handle is opened with FILE_FLAG_OVERLAPPED */
|
|
static gboolean
|
|
ws_pipe_create_overlapped_read(HANDLE *read_pipe_handle, HANDLE *write_pipe_handle,
|
|
SECURITY_ATTRIBUTES *sa, DWORD suggested_buffer_size)
|
|
{
|
|
HANDLE read_pipe, write_pipe;
|
|
guchar *name = ws_strdup_printf("\\\\.\\Pipe\\WiresharkWsPipe.%08x.%08x",
|
|
GetCurrentProcessId(),
|
|
InterlockedIncrement(&pipe_serial_number));
|
|
gunichar2 *wname = g_utf8_to_utf16(name, -1, NULL, NULL, NULL);
|
|
|
|
g_free(name);
|
|
|
|
read_pipe = CreateNamedPipe(wname, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_BYTE | PIPE_WAIT, 1,
|
|
suggested_buffer_size, suggested_buffer_size,
|
|
0, sa);
|
|
if (INVALID_HANDLE_VALUE == read_pipe)
|
|
{
|
|
g_free(wname);
|
|
return FALSE;
|
|
}
|
|
|
|
write_pipe = CreateFile(wname, GENERIC_WRITE, 0, sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (INVALID_HANDLE_VALUE == write_pipe)
|
|
{
|
|
DWORD error = GetLastError();
|
|
CloseHandle(read_pipe);
|
|
SetLastError(error);
|
|
g_free(wname);
|
|
return FALSE;
|
|
}
|
|
|
|
*read_pipe_handle = read_pipe;
|
|
*write_pipe_handle = write_pipe;
|
|
g_free(wname);
|
|
return(TRUE);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Helper to convert a command and argument list to an NULL-terminated 'argv'
|
|
* array, suitable for g_spawn_sync and friends. Free with g_strfreev.
|
|
*/
|
|
static gchar **
|
|
convert_to_argv(const char *command, int args_count, char *const *args)
|
|
{
|
|
gchar **argv = g_new(gchar *, args_count + 2);
|
|
// The caller does not seem to modify this, but g_spawn_sync uses 'gchar **'
|
|
// as opposed to 'const gchar **', so just to be sure clone it.
|
|
argv[0] = g_strdup(command);
|
|
for (int i = 0; i < args_count; i++) {
|
|
// Empty arguments may indicate a bug in Wireshark. Extcap for example
|
|
// omits arguments when their string value is empty. On Windows, empty
|
|
// arguments would silently be ignored because protect_arg returns an
|
|
// empty string, therefore we print a warning here.
|
|
if (!*args[i]) {
|
|
ws_warning("Empty argument %d in arguments list", i);
|
|
}
|
|
argv[1 + i] = g_strdup(args[i]);
|
|
}
|
|
argv[args_count + 1] = NULL;
|
|
return argv;
|
|
}
|
|
|
|
/**
|
|
* Convert a non-empty NULL-terminated array of command and arguments to a
|
|
* string for displaying purposes. On Windows, the returned string is properly
|
|
* escaped and can be executed directly.
|
|
*/
|
|
static gchar *
|
|
convert_to_command_line(gchar **argv)
|
|
{
|
|
GString *command_line = g_string_sized_new(200);
|
|
#ifdef _WIN32
|
|
// The first argument must always be quoted even if it does not contain
|
|
// special characters or else CreateProcess might consider arguments as part
|
|
// of the executable.
|
|
gchar *quoted_arg = protect_arg(argv[0]);
|
|
if (quoted_arg[0] != '"') {
|
|
g_string_append_c(command_line, '"');
|
|
g_string_append(command_line, quoted_arg);
|
|
g_string_append_c(command_line, '"');
|
|
} else {
|
|
g_string_append(command_line, quoted_arg);
|
|
}
|
|
g_free(quoted_arg);
|
|
|
|
for (int i = 1; argv[i]; i++) {
|
|
quoted_arg = protect_arg(argv[i]);
|
|
g_string_append_c(command_line, ' ');
|
|
g_string_append(command_line, quoted_arg);
|
|
g_free(quoted_arg);
|
|
}
|
|
#else
|
|
for (int i = 0; argv[i]; i++) {
|
|
gchar *quoted_arg = g_shell_quote(argv[i]);
|
|
if (i != 0) {
|
|
g_string_append_c(command_line, ' ');
|
|
}
|
|
g_string_append(command_line, quoted_arg);
|
|
g_free(quoted_arg);
|
|
}
|
|
#endif
|
|
return g_string_free(command_line, FALSE);
|
|
}
|
|
|
|
gboolean ws_pipe_spawn_sync(const gchar *working_directory, const gchar *command, gint argc, gchar **args, gchar **command_output)
|
|
{
|
|
gboolean status = FALSE;
|
|
gboolean result = FALSE;
|
|
gchar *local_output = NULL;
|
|
#ifdef _WIN32
|
|
|
|
#define BUFFER_SIZE 16384
|
|
|
|
STARTUPINFO info;
|
|
PROCESS_INFORMATION processInfo;
|
|
|
|
SECURITY_ATTRIBUTES sa;
|
|
HANDLE child_stdout_rd = NULL;
|
|
HANDLE child_stdout_wr = NULL;
|
|
HANDLE child_stderr_rd = NULL;
|
|
HANDLE child_stderr_wr = NULL;
|
|
HANDLE inherit_handles[2];
|
|
|
|
OVERLAPPED stdout_overlapped;
|
|
OVERLAPPED stderr_overlapped;
|
|
#else
|
|
gint exit_status = 0;
|
|
#endif
|
|
|
|
gchar **argv = convert_to_argv(command, argc, args);
|
|
gchar *command_line = convert_to_command_line(argv);
|
|
|
|
ws_debug("command line: %s", command_line);
|
|
|
|
guint64 start_time = g_get_monotonic_time();
|
|
|
|
#ifdef _WIN32
|
|
/* Setup overlapped structures. Create Manual Reset events, initially not signalled */
|
|
memset(&stdout_overlapped, 0, sizeof(OVERLAPPED));
|
|
memset(&stderr_overlapped, 0, sizeof(OVERLAPPED));
|
|
stdout_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (!stdout_overlapped.hEvent)
|
|
{
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stdout overlapped event");
|
|
return FALSE;
|
|
}
|
|
stderr_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (!stderr_overlapped.hEvent)
|
|
{
|
|
CloseHandle(stdout_overlapped.hEvent);
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stderr overlapped event");
|
|
return FALSE;
|
|
}
|
|
|
|
memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.bInheritHandle = FALSE;
|
|
sa.lpSecurityDescriptor = NULL;
|
|
|
|
if (!ws_pipe_create_overlapped_read(&child_stdout_rd, &child_stdout_wr, &sa, 0))
|
|
{
|
|
CloseHandle(stdout_overlapped.hEvent);
|
|
CloseHandle(stderr_overlapped.hEvent);
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stdout handle");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!ws_pipe_create_overlapped_read(&child_stderr_rd, &child_stderr_wr, &sa, 0))
|
|
{
|
|
CloseHandle(stdout_overlapped.hEvent);
|
|
CloseHandle(stderr_overlapped.hEvent);
|
|
CloseHandle(child_stdout_rd);
|
|
CloseHandle(child_stdout_wr);
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stderr handle");
|
|
return FALSE;
|
|
}
|
|
|
|
inherit_handles[0] = child_stderr_wr;
|
|
inherit_handles[1] = child_stdout_wr;
|
|
|
|
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
|
|
memset(&info, 0, sizeof(STARTUPINFO));
|
|
|
|
info.cb = sizeof(STARTUPINFO);
|
|
info.hStdError = child_stderr_wr;
|
|
info.hStdOutput = child_stdout_wr;
|
|
info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
|
info.wShowWindow = SW_HIDE;
|
|
|
|
if (win32_create_process(NULL, command_line, NULL, NULL, G_N_ELEMENTS(inherit_handles), inherit_handles,
|
|
CREATE_NEW_CONSOLE, NULL, working_directory, &info, &processInfo))
|
|
{
|
|
gchar* stdout_buffer = (gchar*)g_malloc(BUFFER_SIZE);
|
|
gchar* stderr_buffer = (gchar*)g_malloc(BUFFER_SIZE);
|
|
DWORD dw;
|
|
DWORD bytes_read;
|
|
GString *output_string = g_string_new(NULL);
|
|
gboolean process_finished = FALSE;
|
|
gboolean pending_stdout = TRUE;
|
|
gboolean pending_stderr = TRUE;
|
|
|
|
/* Start asynchronous reads from child process stdout and stderr */
|
|
if (!ReadFile(child_stdout_rd, stdout_buffer, BUFFER_SIZE, NULL, &stdout_overlapped))
|
|
{
|
|
if (GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
ws_debug("ReadFile on child stdout pipe failed. Error %d", GetLastError());
|
|
pending_stdout = FALSE;
|
|
}
|
|
}
|
|
|
|
if (!ReadFile(child_stderr_rd, stderr_buffer, BUFFER_SIZE, NULL, &stderr_overlapped))
|
|
{
|
|
if (GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
ws_debug("ReadFile on child stderr pipe failed. Error %d", GetLastError());
|
|
pending_stderr = FALSE;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
HANDLE handles[3];
|
|
DWORD n_handles = 0;
|
|
if (!process_finished)
|
|
{
|
|
handles[n_handles++] = processInfo.hProcess;
|
|
}
|
|
if (pending_stdout)
|
|
{
|
|
handles[n_handles++] = stdout_overlapped.hEvent;
|
|
}
|
|
if (pending_stderr)
|
|
{
|
|
handles[n_handles++] = stderr_overlapped.hEvent;
|
|
}
|
|
|
|
if (!n_handles)
|
|
{
|
|
/* No more things to wait */
|
|
break;
|
|
}
|
|
|
|
dw = WaitForMultipleObjects(n_handles, handles, FALSE, INFINITE);
|
|
if (dw < (WAIT_OBJECT_0 + n_handles))
|
|
{
|
|
int i = dw - WAIT_OBJECT_0;
|
|
if (handles[i] == processInfo.hProcess)
|
|
{
|
|
/* Process finished but there might still be unread data in the pipe.
|
|
* Close the write pipes, so ReadFile does not wait indefinitely.
|
|
*/
|
|
CloseHandle(child_stdout_wr);
|
|
CloseHandle(child_stderr_wr);
|
|
process_finished = TRUE;
|
|
}
|
|
else if (handles[i] == stdout_overlapped.hEvent)
|
|
{
|
|
bytes_read = 0;
|
|
if (!GetOverlappedResult(child_stdout_rd, &stdout_overlapped, &bytes_read, TRUE))
|
|
{
|
|
if (GetLastError() == ERROR_BROKEN_PIPE)
|
|
{
|
|
pending_stdout = FALSE;
|
|
continue;
|
|
}
|
|
ws_debug("GetOverlappedResult on stdout failed. Error %d", GetLastError());
|
|
}
|
|
if (process_finished && (bytes_read == 0))
|
|
{
|
|
/* We have drained the pipe and there isn't any process that holds active write handle to the pipe. */
|
|
pending_stdout = FALSE;
|
|
continue;
|
|
}
|
|
g_string_append_len(output_string, stdout_buffer, bytes_read);
|
|
if (!ReadFile(child_stdout_rd, stdout_buffer, BUFFER_SIZE, NULL, &stdout_overlapped))
|
|
{
|
|
if (GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
ws_debug("ReadFile on child stdout pipe failed. Error %d", GetLastError());
|
|
pending_stdout = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else if (handles[i] == stderr_overlapped.hEvent)
|
|
{
|
|
/* Discard the stderr data just like non-windows version of this function does. */
|
|
bytes_read = 0;
|
|
if (!GetOverlappedResult(child_stderr_rd, &stderr_overlapped, &bytes_read, TRUE))
|
|
{
|
|
if (GetLastError() == ERROR_BROKEN_PIPE)
|
|
{
|
|
pending_stderr = FALSE;
|
|
continue;
|
|
}
|
|
ws_debug("GetOverlappedResult on stderr failed. Error %d", GetLastError());
|
|
}
|
|
if (process_finished && (bytes_read == 0))
|
|
{
|
|
pending_stderr = FALSE;
|
|
continue;
|
|
}
|
|
if (!ReadFile(child_stderr_rd, stderr_buffer, BUFFER_SIZE, NULL, &stderr_overlapped))
|
|
{
|
|
if (GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
ws_debug("ReadFile on child stderr pipe failed. Error %d", GetLastError());
|
|
pending_stderr = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ws_debug("WaitForMultipleObjects returned 0x%08X. Error %d", dw, GetLastError());
|
|
}
|
|
}
|
|
|
|
g_free(stdout_buffer);
|
|
g_free(stderr_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_stderr_rd);
|
|
|
|
CloseHandle(processInfo.hProcess);
|
|
CloseHandle(processInfo.hThread);
|
|
}
|
|
else
|
|
{
|
|
status = FALSE;
|
|
|
|
CloseHandle(child_stdout_rd);
|
|
CloseHandle(child_stdout_wr);
|
|
CloseHandle(child_stderr_rd);
|
|
CloseHandle(child_stderr_wr);
|
|
}
|
|
|
|
CloseHandle(stdout_overlapped.hEvent);
|
|
CloseHandle(stderr_overlapped.hEvent);
|
|
#else
|
|
|
|
GSpawnFlags flags = (GSpawnFlags)0;
|
|
GSpawnChildSetupFunc child_setup = NULL;
|
|
#ifdef HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG
|
|
flags = (GSpawnFlags)(flags | G_SPAWN_LEAVE_DESCRIPTORS_OPEN);
|
|
child_setup = close_non_standard_fds_linux;
|
|
#endif
|
|
status = g_spawn_sync(working_directory, argv, NULL,
|
|
flags, child_setup, NULL, &local_output, NULL, &exit_status, NULL);
|
|
|
|
if (status && exit_status != 0)
|
|
status = FALSE;
|
|
#endif
|
|
|
|
ws_debug("%s finished in %.3fms", argv[0], (g_get_monotonic_time() - start_time) / 1000.0);
|
|
|
|
if (status)
|
|
{
|
|
if (local_output != NULL) {
|
|
ws_noisy("spawn output: %s", local_output);
|
|
if (command_output != NULL)
|
|
*command_output = g_strdup(local_output);
|
|
}
|
|
result = TRUE;
|
|
}
|
|
|
|
g_free(local_output);
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
|
|
return result;
|
|
}
|
|
|
|
void ws_pipe_init(ws_pipe_t *ws_pipe)
|
|
{
|
|
if (!ws_pipe) return;
|
|
memset(ws_pipe, 0, sizeof(ws_pipe_t));
|
|
ws_pipe->pid = WS_INVALID_PID;
|
|
}
|
|
|
|
GPid ws_pipe_spawn_async(ws_pipe_t *ws_pipe, GPtrArray *args)
|
|
{
|
|
GPid pid = WS_INVALID_PID;
|
|
gint stdin_fd, stdout_fd, stderr_fd;
|
|
#ifdef _WIN32
|
|
STARTUPINFO info;
|
|
PROCESS_INFORMATION processInfo;
|
|
|
|
SECURITY_ATTRIBUTES sa;
|
|
HANDLE child_stdin_rd = NULL;
|
|
HANDLE child_stdin_wr = NULL;
|
|
HANDLE child_stdout_rd = NULL;
|
|
HANDLE child_stdout_wr = NULL;
|
|
HANDLE child_stderr_rd = NULL;
|
|
HANDLE child_stderr_wr = NULL;
|
|
HANDLE inherit_handles[3];
|
|
#endif
|
|
|
|
// XXX harmonize handling of command arguments for the sync/async functions
|
|
// and make them const? This array ends with a trailing NULL by the way.
|
|
gchar **args_array = (gchar **)args->pdata;
|
|
gchar **argv = convert_to_argv(args_array[0], args->len - 2, args_array + 1);
|
|
gchar *command_line = convert_to_command_line(argv);
|
|
|
|
ws_debug("command line: %s", command_line);
|
|
|
|
#ifdef _WIN32
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.bInheritHandle = FALSE;
|
|
sa.lpSecurityDescriptor = NULL;
|
|
|
|
if (!CreatePipe(&child_stdin_rd, &child_stdin_wr, &sa, 0))
|
|
{
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stdin handle");
|
|
return WS_INVALID_PID;
|
|
}
|
|
|
|
if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0))
|
|
{
|
|
CloseHandle(child_stdin_rd);
|
|
CloseHandle(child_stdin_wr);
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stdout handle");
|
|
return WS_INVALID_PID;
|
|
}
|
|
|
|
if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0))
|
|
{
|
|
CloseHandle(child_stdin_rd);
|
|
CloseHandle(child_stdin_wr);
|
|
CloseHandle(child_stdout_rd);
|
|
CloseHandle(child_stdout_wr);
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
ws_debug("Could not create stderr handle");
|
|
return WS_INVALID_PID;
|
|
}
|
|
|
|
inherit_handles[0] = child_stdin_rd;
|
|
inherit_handles[1] = child_stderr_wr;
|
|
inherit_handles[2] = child_stdout_wr;
|
|
|
|
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
|
|
memset(&info, 0, sizeof(STARTUPINFO));
|
|
|
|
info.cb = sizeof(STARTUPINFO);
|
|
info.hStdInput = child_stdin_rd;
|
|
info.hStdError = child_stderr_wr;
|
|
info.hStdOutput = child_stdout_wr;
|
|
info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
|
info.wShowWindow = SW_HIDE;
|
|
|
|
if (win32_create_process(NULL, command_line, NULL, NULL, G_N_ELEMENTS(inherit_handles), inherit_handles,
|
|
CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo))
|
|
{
|
|
stdin_fd = _open_osfhandle((intptr_t)(child_stdin_wr), _O_BINARY);
|
|
stdout_fd = _open_osfhandle((intptr_t)(child_stdout_rd), _O_BINARY);
|
|
stderr_fd = _open_osfhandle((intptr_t)(child_stderr_rd), _O_BINARY);
|
|
pid = processInfo.hProcess;
|
|
CloseHandle(processInfo.hThread);
|
|
}
|
|
else
|
|
{
|
|
CloseHandle(child_stdin_wr);
|
|
CloseHandle(child_stdout_rd);
|
|
CloseHandle(child_stderr_rd);
|
|
}
|
|
|
|
/* We no longer need other (child) end of pipes. The child process holds
|
|
* its own handles that will be closed on process exit. However, we have
|
|
* to close *our* handles as otherwise read() on stdout_fd and stderr_fd
|
|
* will block indefinitely after the process exits.
|
|
*/
|
|
CloseHandle(child_stdin_rd);
|
|
CloseHandle(child_stdout_wr);
|
|
CloseHandle(child_stderr_wr);
|
|
#else
|
|
|
|
GError *error = NULL;
|
|
GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD;
|
|
GSpawnChildSetupFunc child_setup = NULL;
|
|
#ifdef HAS_G_SPAWN_LINUX_THREAD_SAFETY_BUG
|
|
flags = (GSpawnFlags)(flags | G_SPAWN_LEAVE_DESCRIPTORS_OPEN);
|
|
child_setup = close_non_standard_fds_linux;
|
|
#endif
|
|
gboolean spawned = g_spawn_async_with_pipes(NULL, argv, NULL,
|
|
flags, child_setup, NULL,
|
|
&pid, &stdin_fd, &stdout_fd, &stderr_fd, &error);
|
|
if (!spawned) {
|
|
ws_debug("Error creating async pipe: %s", error->message);
|
|
g_free(error->message);
|
|
}
|
|
#endif
|
|
|
|
g_free(command_line);
|
|
g_strfreev(argv);
|
|
|
|
ws_pipe->pid = pid;
|
|
|
|
if (pid != WS_INVALID_PID) {
|
|
#ifdef _WIN32
|
|
ws_pipe->stdin_io = g_io_channel_win32_new_fd(stdin_fd);
|
|
ws_pipe->stdout_io = g_io_channel_win32_new_fd(stdout_fd);
|
|
ws_pipe->stderr_io = g_io_channel_win32_new_fd(stderr_fd);
|
|
#else
|
|
ws_pipe->stdin_io = g_io_channel_unix_new(stdin_fd);
|
|
ws_pipe->stdout_io = g_io_channel_unix_new(stdout_fd);
|
|
ws_pipe->stderr_io = g_io_channel_unix_new(stderr_fd);
|
|
#endif
|
|
g_io_channel_set_encoding(ws_pipe->stdin_io, NULL, NULL);
|
|
g_io_channel_set_encoding(ws_pipe->stdout_io, NULL, NULL);
|
|
g_io_channel_set_encoding(ws_pipe->stderr_io, NULL, NULL);
|
|
g_io_channel_set_buffered(ws_pipe->stdin_io, FALSE);
|
|
g_io_channel_set_buffered(ws_pipe->stdout_io, FALSE);
|
|
g_io_channel_set_buffered(ws_pipe->stderr_io, FALSE);
|
|
g_io_channel_set_close_on_unref(ws_pipe->stdin_io, TRUE);
|
|
g_io_channel_set_close_on_unref(ws_pipe->stdout_io, TRUE);
|
|
g_io_channel_set_close_on_unref(ws_pipe->stderr_io, TRUE);
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
typedef struct
|
|
{
|
|
HANDLE pipeHandle;
|
|
OVERLAPPED ol;
|
|
BOOL pendingIO;
|
|
} PIPEINTS;
|
|
|
|
gboolean
|
|
ws_pipe_wait_for_pipe(HANDLE * pipe_handles, int num_pipe_handles, HANDLE pid)
|
|
{
|
|
PIPEINTS pipeinsts[3];
|
|
HANDLE handles[4];
|
|
gboolean result = TRUE;
|
|
|
|
SecureZeroMemory(pipeinsts, sizeof(pipeinsts));
|
|
|
|
if (num_pipe_handles == 0 || num_pipe_handles > 3)
|
|
{
|
|
ws_debug("Invalid number of pipes given as argument.");
|
|
return FALSE;
|
|
}
|
|
|
|
for (int i = 0; i < num_pipe_handles; ++i)
|
|
{
|
|
pipeinsts[i].ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (!pipeinsts[i].ol.hEvent)
|
|
{
|
|
ws_debug("Could not create overlapped event");
|
|
for (int j = 0; j < i; j++)
|
|
{
|
|
CloseHandle(pipeinsts[j].ol.hEvent);
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < num_pipe_handles; ++i)
|
|
{
|
|
pipeinsts[i].pipeHandle = pipe_handles[i];
|
|
pipeinsts[i].ol.Pointer = 0;
|
|
pipeinsts[i].pendingIO = FALSE;
|
|
if (!ConnectNamedPipe(pipeinsts[i].pipeHandle, &pipeinsts[i].ol))
|
|
{
|
|
DWORD error = GetLastError();
|
|
switch (error)
|
|
{
|
|
case ERROR_IO_PENDING:
|
|
pipeinsts[i].pendingIO = TRUE;
|
|
break;
|
|
|
|
case ERROR_PIPE_CONNECTED:
|
|
SetEvent(pipeinsts[i].ol.hEvent);
|
|
break;
|
|
|
|
default:
|
|
ws_debug("ConnectNamedPipe failed with %d\n.", error);
|
|
result = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (result)
|
|
{
|
|
DWORD dw;
|
|
int num_handles = 0;
|
|
for (int i = 0; i < num_pipe_handles; ++i)
|
|
{
|
|
if (pipeinsts[i].pendingIO)
|
|
{
|
|
handles[num_handles] = pipeinsts[i].ol.hEvent;
|
|
num_handles++;
|
|
}
|
|
}
|
|
if (num_handles == 0)
|
|
{
|
|
/* All pipes have been successfully connected */
|
|
break;
|
|
}
|
|
/* Wait for process in case it exits before the pipes have connected */
|
|
handles[num_handles] = pid;
|
|
num_handles++;
|
|
|
|
dw = WaitForMultipleObjects(num_handles, handles, FALSE, 30000);
|
|
int handle_idx = dw - WAIT_OBJECT_0;
|
|
if (dw == WAIT_TIMEOUT)
|
|
{
|
|
ws_debug("extcap didn't connect to pipe within 30 seconds.");
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
// If index points to our handles array
|
|
else if (handle_idx >= 0 && handle_idx < num_handles)
|
|
{
|
|
if (handles[handle_idx] == pid)
|
|
{
|
|
ws_debug("extcap terminated without connecting to pipe.");
|
|
result = FALSE;
|
|
}
|
|
for (int i = 0; i < num_pipe_handles; ++i)
|
|
{
|
|
if (handles[handle_idx] == pipeinsts[i].ol.hEvent)
|
|
{
|
|
DWORD cbRet;
|
|
BOOL success = GetOverlappedResult(
|
|
pipeinsts[i].pipeHandle, // handle to pipe
|
|
&pipeinsts[i].ol, // OVERLAPPED structure
|
|
&cbRet, // bytes transferred
|
|
TRUE); // wait
|
|
if (!success)
|
|
{
|
|
ws_debug("Error %d \n.", GetLastError());
|
|
result = FALSE;
|
|
}
|
|
pipeinsts[i].pendingIO = FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ws_debug("WaitForMultipleObjects returned 0x%08X. Error %d", dw, GetLastError());
|
|
result = FALSE;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < num_pipe_handles; ++i)
|
|
{
|
|
if (pipeinsts[i].pendingIO)
|
|
{
|
|
CancelIoEx(pipeinsts[i].pipeHandle, &pipeinsts[i].ol);
|
|
WaitForSingleObject(pipeinsts[i].ol.hEvent, INFINITE);
|
|
}
|
|
CloseHandle(pipeinsts[i].ol.hEvent);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
gboolean
|
|
ws_pipe_data_available(int pipe_fd)
|
|
{
|
|
#ifdef _WIN32 /* PeekNamedPipe */
|
|
HANDLE hPipe = (HANDLE) _get_osfhandle(pipe_fd);
|
|
DWORD bytes_avail;
|
|
|
|
if (hPipe == INVALID_HANDLE_VALUE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (! PeekNamedPipe(hPipe, NULL, 0, NULL, &bytes_avail, NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (bytes_avail > 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
#else /* select */
|
|
fd_set rfds;
|
|
struct timeval timeout;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(pipe_fd, &rfds);
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
if (select(pipe_fd + 1, &rfds, NULL, NULL, &timeout) > 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local variables:
|
|
* c-basic-offset: 4
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vi: set shiftwidth=4 tabstop=8 expandtab:
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
*/
|