capture: Move capture pipe polling out of UI

Both CLI and Qt interfaces spin GLib mainloop. Move the capture pipe
polling into common code to reduce code duplication.
This commit is contained in:
Tomasz Moń 2022-07-29 20:16:16 +02:00
parent 5aba5772e9
commit 2d1380ae5b
No known key found for this signature in database
GPG Key ID: 92BA8820D4D517C8
10 changed files with 91 additions and 355 deletions

View File

@ -88,7 +88,9 @@ typedef void (*closed_fn)(capture_session *cap_session, gchar *msg);
struct _capture_session {
ws_process_id fork_child; /**< If not WS_INVALID_PID, in parent, process ID of child */
int fork_child_status; /**< Child exit status */
int pipe_input_id; /**< GLib input pipe source ID */
#ifdef _WIN32
int sync_pipe_read_fd; /**< Input pipe descriptor */
int signal_pipe_write_fd; /**< the pipe to signal the child */
#endif
capture_state state; /**< current state of the capture engine */

View File

@ -28,6 +28,8 @@
#include <wsutil/unicode-utils.h>
#include <wsutil/win32-utils.h>
#include <wsutil/ws_pipe.h>
#else
#include <glib-unix.h>
#endif
#ifdef HAVE_SYS_WAIT_H
@ -124,7 +126,9 @@ capture_session_init(capture_session *cap_session, capture_file *cf,
{
cap_session->cf = cf;
cap_session->fork_child = WS_INVALID_PID; /* invalid process handle */
cap_session->pipe_input_id = 0;
#ifdef _WIN32
cap_session->sync_pipe_read_fd = 0;
cap_session->signal_pipe_write_fd = -1;
#endif
cap_session->state = CAPTURE_STOPPED;
@ -201,6 +205,67 @@ init_pipe_args(int *argc) {
return argv;
}
#ifdef _WIN32
/* The timer has expired, see if there's stuff to read from the pipe,
if so, do the callback */
static gint
pipe_timer_cb(gpointer user_data)
{
capture_session *cap_session = (capture_session *)user_data;
HANDLE handle;
DWORD avail = 0;
gboolean result;
DWORD childstatus;
gint iterations = 0;
/* try to read data from the pipe only 5 times, to avoid blocking */
while(iterations < 5) {
/* Oddly enough although Named pipes don't work on win9x,
PeekNamedPipe does !!! */
handle = (HANDLE) _get_osfhandle (cap_session->sync_pipe_read_fd);
result = PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL);
/* Get the child process exit status */
GetExitCodeProcess((HANDLE)cap_session->fork_child,
&childstatus);
/* If the Peek returned an error, or there are bytes to be read
or the childwatcher thread has terminated then call the normal
callback */
if (!result || avail > 0 || childstatus != STILL_ACTIVE) {
/* And call the real handler */
if (!sync_pipe_input_cb(cap_session->sync_pipe_read_fd, user_data)) {
ws_debug("input pipe closed, iterations: %u", iterations);
/* pipe closed, stop the timer */
cap_session->pipe_input_id = 0;
return G_SOURCE_REMOVE;
}
}
else {
/* No data, stop now */
break;
}
iterations++;
}
/* we didn't stop the timer, so let it run */
return G_SOURCE_CONTINUE;
}
#else
static gboolean
pipe_fd_cb(gint fd, GIOCondition condition _U_, gpointer user_data)
{
capture_session *cap_session = (capture_session *)user_data;
if (!sync_pipe_input_cb(fd, user_data)) {
cap_session->pipe_input_id = 0;
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
#endif
#define ARGV_NUMBER_LEN 24
/* a new capture run: start a new dumpcap task and hand over parameters through command line */
gboolean
@ -664,9 +729,20 @@ sync_pipe_start(capture_options *capture_opts, GPtrArray *capture_comments,
the child process wants to tell us something. */
/* we have a running capture, now wait for the real capture filename */
pipe_input_set_handler(sync_pipe_read_fd, (gpointer) cap_session,
&cap_session->fork_child, sync_pipe_input_cb);
if (cap_session->pipe_input_id) {
g_source_remove(cap_session->pipe_input_id);
cap_session->pipe_input_id = 0;
}
#ifdef _WIN32
/* Tricky to use pipes in win9x, as no concept of wait. NT can
do this but that doesn't cover all win32 platforms. Attempt to do
something similar here, start a timer and check for data on every
timeout. */
cap_session->sync_pipe_read_fd = sync_pipe_read_fd;
cap_session->pipe_input_id = g_timeout_add(200, pipe_timer_cb, cap_session);
#else
cap_session->pipe_input_id = g_unix_fd_add(sync_pipe_read_fd, G_IO_IN | G_IO_HUP, pipe_fd_cb, cap_session);
#endif
return TRUE;
}

View File

@ -30,7 +30,6 @@
#ifndef _WIN32
#include <signal.h>
#include <glib-unix.h>
#endif
#include <glib.h>
@ -2502,94 +2501,6 @@ clean_exit:
gboolean loop_running = FALSE;
guint32 packet_count = 0;
typedef struct pipe_input_tag {
gint source;
gpointer user_data;
ws_process_id *child_process;
pipe_input_cb_t input_cb;
guint pipe_input_id;
} pipe_input_t;
static pipe_input_t pipe_input;
#ifdef _WIN32
/* The timer has expired, see if there's stuff to read from the pipe,
if so, do the callback */
static gint
pipe_timer_cb(gpointer data)
{
HANDLE handle;
DWORD avail = 0;
gboolean result;
DWORD childstatus;
pipe_input_t *pipe_input_p = data;
gint iterations = 0;
/* try to read data from the pipe only 5 times, to avoid blocking */
while(iterations < 5) {
/* Oddly enough although Named pipes don't work on win9x,
PeekNamedPipe does !!! */
handle = (HANDLE) _get_osfhandle (pipe_input_p->source);
result = PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL);
/* Get the child process exit status */
GetExitCodeProcess((HANDLE)*(pipe_input_p->child_process),
&childstatus);
/* If the Peek returned an error, or there are bytes to be read
or the childwatcher thread has terminated then call the normal
callback */
if (!result || avail > 0 || childstatus != STILL_ACTIVE) {
/* And call the real handler */
if (!pipe_input_p->input_cb(pipe_input_p->source, pipe_input_p->user_data)) {
ws_debug("input pipe closed, iterations: %u", iterations);
/* pipe closed, stop the timer */
return G_SOURCE_REMOVE;
}
}
else {
/* No data, stop now */
break;
}
iterations++;
}
/* we didn't stopped the timer, so let it run */
return G_SOURCE_CONTINUE;
}
#else
static gboolean
pipe_fd_cb(gint fd _U_, GIOCondition condition _U_, gpointer user_data)
{
pipe_input_t *pipe_input_p = (pipe_input_t *)user_data;
return pipe_input_p->input_cb(pipe_input_p->source, pipe_input_p->user_data);
}
#endif
void
pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
{
pipe_input.source = source;
pipe_input.child_process = child_process;
pipe_input.user_data = user_data;
pipe_input.input_cb = input_cb;
#ifdef _WIN32
/* Tricky to use pipes in win9x, as no concept of wait. NT can
do this but that doesn't cover all win32 platforms. GTK can do
this but doesn't seem to work over processes. Attempt to do
something similar here, start a timer and check for data on every
timeout. */
pipe_input.pipe_input_id = g_timeout_add(200, pipe_timer_cb, &pipe_input);
#else
pipe_input.pipe_input_id = g_unix_fd_add(source, G_IO_IN | G_IO_HUP, pipe_fd_cb, &pipe_input);
#endif
}
static const nstime_t *
tshark_get_frame_ts(struct packet_provider_data *prov, guint32 frame_num)
{

View File

@ -95,11 +95,6 @@ DIAG_ON(frame-larger-than=)
// If we ever add support for multiple windows this will need to be replaced.
static LograyMainWindow *gbl_cur_main_window_ = NULL;
void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
{
gbl_cur_main_window_->setPipeInputHandler(source, user_data, child_process, input_cb);
}
static void plugin_if_mainwindow_apply_filter(GHashTable * data_set)
{
if (!gbl_cur_main_window_ || !data_set)
@ -334,11 +329,6 @@ LograyMainWindow::LograyMainWindow(QWidget *parent) :
#endif
, display_filter_dlg_(NULL)
, capture_filter_dlg_(NULL)
#ifdef _WIN32
, pipe_timer_(NULL)
#else
, pipe_notifier_(NULL)
#endif
#if defined(Q_OS_MAC)
, dock_menu_(NULL)
#endif
@ -844,43 +834,6 @@ void LograyMainWindow::removeInterfaceToolbar(const gchar *menu_title)
menu->menuAction()->setVisible(!menu->actions().isEmpty());
}
void LograyMainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
{
pipe_source_ = source;
pipe_child_process_ = child_process;
pipe_user_data_ = user_data;
pipe_input_cb_ = input_cb;
#ifdef _WIN32
/* Tricky to use pipes in win9x, as no concept of wait. NT can
do this but that doesn't cover all win32 platforms. GTK can do
this but doesn't seem to work over processes. Attempt to do
something similar here, start a timer and check for data on every
timeout. */
/*ws_log(NULL, LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
if (pipe_timer_) {
disconnect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
delete pipe_timer_;
}
pipe_timer_ = new QTimer(this);
connect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
connect(pipe_timer_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
pipe_timer_->start(200);
#else
if (pipe_notifier_) {
disconnect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
delete pipe_notifier_;
}
pipe_notifier_ = new QSocketNotifier(pipe_source_, QSocketNotifier::Read);
// XXX ui/gtk/gui_utils.c sets the encoding. Do we need to do the same?
connect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
connect(pipe_notifier_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
#endif
}
bool LograyMainWindow::eventFilter(QObject *obj, QEvent *event) {
// The user typed some text. Start filling in a filter.

View File

@ -106,7 +106,6 @@ class LograyMainWindow : public MainWindow
public:
explicit LograyMainWindow(QWidget *parent = nullptr);
~LograyMainWindow();
void setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb);
#ifdef HAVE_LIBPCAP
capture_session *captureSession() { return &cap_session_; }
@ -195,17 +194,6 @@ private:
FilterDialog *display_filter_dlg_;
FilterDialog *capture_filter_dlg_;
// Pipe input
gint pipe_source_;
gpointer pipe_user_data_;
ws_process_id *pipe_child_process_;
pipe_input_cb_t pipe_input_cb_;
#ifdef _WIN32
QTimer *pipe_timer_;
#else
QSocketNotifier *pipe_notifier_;
#endif
#if defined(Q_OS_MAC)
QMenu *dock_menu_;
#endif
@ -333,9 +321,7 @@ private slots:
*/
void startCapture(QStringList);
void startCapture();
void pipeTimeout();
void pipeActivated(int source);
void pipeNotifierDestroyed();
void popLiveCaptureInProgress();
void stopCapture();
void loadWindowGeometry();

View File

@ -516,6 +516,7 @@ void LograyMainWindow::captureCaptureUpdateFinished(capture_session *session) {
setMenusForCaptureFile();
setWindowIcon(mainApp->normalIcon());
popLiveCaptureInProgress();
if (global_commandline_info.quit_after_cap) {
// Command line asked us to quit after capturing.
@ -538,6 +539,7 @@ void LograyMainWindow::captureCaptureFixedFinished(capture_session *) {
setMenusForCaptureFile(true);
setWindowIcon(mainApp->normalIcon());
popLiveCaptureInProgress();
if (global_commandline_info.quit_after_cap) {
// Command line asked us to quit after capturing.
@ -558,6 +560,7 @@ void LograyMainWindow::captureCaptureFailed(capture_session *) {
mainApp->popStatus(WiresharkApplication::FileStatus);
setWindowIcon(mainApp->normalIcon());
popLiveCaptureInProgress();
if (global_commandline_info.quit_after_cap) {
// Command line asked us to quit after capturing.
@ -917,75 +920,9 @@ void LograyMainWindow::startCapture(QStringList interfaces _U_) {
#endif // HAVE_LIBPCAP
}
// Copied from ui/gtk/gui_utils.c
void LograyMainWindow::pipeTimeout() {
#ifdef _WIN32
HANDLE handle;
DWORD avail = 0;
gboolean result, result1;
DWORD childstatus;
gint iterations = 0;
/* try to read data from the pipe only 5 times, to avoid blocking */
while (iterations < 5) {
/* Oddly enough although Named pipes don't work on win9x,
PeekNamedPipe does !!! */
handle = (HANDLE)_get_osfhandle(pipe_source_);
result = PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL);
/* Get the child process exit status */
result1 = GetExitCodeProcess((HANDLE)*(pipe_child_process_),
&childstatus);
/* If the Peek returned an error, or there are bytes to be read
or the childwatcher thread has terminated then call the normal
callback */
if (!result || avail > 0 || childstatus != STILL_ACTIVE) {
/* And call the real handler */
if (!pipe_input_cb_(pipe_source_, pipe_user_data_)) {
ws_log(LOG_DOMAIN_MAIN, LOG_LEVEL_DEBUG, "pipe_timer_cb: input pipe closed, iterations: %u", iterations);
/* pipe closed, return false so that the old timer is not run again */
delete pipe_timer_;
return;
}
} else {
/* No data, stop now */
break;
}
iterations++;
}
#endif // _WIN32
}
void LograyMainWindow::pipeActivated(int source) {
Q_UNUSED(source)
#ifndef _WIN32
ws_assert(source == pipe_source_);
pipe_notifier_->setEnabled(false);
if (pipe_input_cb_(pipe_source_, pipe_user_data_)) {
pipe_notifier_->setEnabled(true);
}
else {
delete pipe_notifier_;
}
#endif // _WIN32
}
void LograyMainWindow::pipeNotifierDestroyed()
{
void LograyMainWindow::popLiveCaptureInProgress() {
/* Pop the "<live capture in progress>" message off the status bar. */
main_ui_->statusBar->setFileName(capture_file_);
#ifdef _WIN32
pipe_timer_ = NULL;
#else
pipe_notifier_ = NULL;
#endif // _WIN32
}
void LograyMainWindow::stopCapture() {

View File

@ -97,11 +97,6 @@ DIAG_ON(frame-larger-than=)
// If we ever add support for multiple windows this will need to be replaced.
static WiresharkMainWindow *gbl_cur_main_window_ = NULL;
void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
{
gbl_cur_main_window_->setPipeInputHandler(source, user_data, child_process, input_cb);
}
static void plugin_if_mainwindow_apply_filter(GHashTable * data_set)
{
if (!gbl_cur_main_window_ || !data_set)
@ -336,11 +331,6 @@ WiresharkMainWindow::WiresharkMainWindow(QWidget *parent) :
#endif
, display_filter_dlg_(NULL)
, capture_filter_dlg_(NULL)
#ifdef _WIN32
, pipe_timer_(NULL)
#else
, pipe_notifier_(NULL)
#endif
#if defined(Q_OS_MAC)
, dock_menu_(NULL)
#endif
@ -875,43 +865,6 @@ void WiresharkMainWindow::removeInterfaceToolbar(const gchar *menu_title)
menu->menuAction()->setVisible(!menu->actions().isEmpty());
}
void WiresharkMainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
{
pipe_source_ = source;
pipe_child_process_ = child_process;
pipe_user_data_ = user_data;
pipe_input_cb_ = input_cb;
#ifdef _WIN32
/* Tricky to use pipes in win9x, as no concept of wait. NT can
do this but that doesn't cover all win32 platforms. GTK can do
this but doesn't seem to work over processes. Attempt to do
something similar here, start a timer and check for data on every
timeout. */
/*ws_log(NULL, LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
if (pipe_timer_) {
disconnect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
delete pipe_timer_;
}
pipe_timer_ = new QTimer(this);
connect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
connect(pipe_timer_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
pipe_timer_->start(200);
#else
if (pipe_notifier_) {
disconnect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
delete pipe_notifier_;
}
pipe_notifier_ = new QSocketNotifier(pipe_source_, QSocketNotifier::Read);
// XXX ui/gtk/gui_utils.c sets the encoding. Do we need to do the same?
connect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
connect(pipe_notifier_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
#endif
}
bool WiresharkMainWindow::eventFilter(QObject *obj, QEvent *event) {
// The user typed some text. Start filling in a filter.

View File

@ -112,7 +112,6 @@ class WiresharkMainWindow : public MainWindow
public:
explicit WiresharkMainWindow(QWidget *parent = nullptr);
~WiresharkMainWindow();
void setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb);
#ifdef HAVE_LIBPCAP
capture_session *captureSession() { return &cap_session_; }
@ -204,17 +203,6 @@ private:
FilterDialog *display_filter_dlg_;
FilterDialog *capture_filter_dlg_;
// Pipe input
gint pipe_source_;
gpointer pipe_user_data_;
ws_process_id *pipe_child_process_;
pipe_input_cb_t pipe_input_cb_;
#ifdef _WIN32
QTimer *pipe_timer_;
#else
QSocketNotifier *pipe_notifier_;
#endif
#if defined(Q_OS_MAC)
QMenu *dock_menu_;
#endif
@ -356,9 +344,7 @@ private slots:
*/
void startCapture(QStringList);
void startCapture();
void pipeTimeout();
void pipeActivated(int source);
void pipeNotifierDestroyed();
void popLiveCaptureInProgress();
void stopCapture();
void loadWindowGeometry();

View File

@ -547,6 +547,7 @@ void WiresharkMainWindow::captureCaptureUpdateFinished(capture_session *session)
setMenusForCaptureFile();
setWindowIcon(mainApp->normalIcon());
popLiveCaptureInProgress();
if (global_commandline_info.quit_after_cap) {
// Command line asked us to quit after capturing.
@ -569,6 +570,7 @@ void WiresharkMainWindow::captureCaptureFixedFinished(capture_session *) {
setMenusForCaptureFile(true);
setWindowIcon(mainApp->normalIcon());
popLiveCaptureInProgress();
if (global_commandline_info.quit_after_cap) {
// Command line asked us to quit after capturing.
@ -589,6 +591,7 @@ void WiresharkMainWindow::captureCaptureFailed(capture_session *) {
mainApp->popStatus(WiresharkApplication::FileStatus);
setWindowIcon(mainApp->normalIcon());
popLiveCaptureInProgress();
if (global_commandline_info.quit_after_cap) {
// Command line asked us to quit after capturing.
@ -977,75 +980,9 @@ DIAG_ON(stringop-overread)
#endif // HAVE_LIBPCAP
}
// Copied from ui/gtk/gui_utils.c
void WiresharkMainWindow::pipeTimeout() {
#ifdef _WIN32
HANDLE handle;
DWORD avail = 0;
gboolean result, result1;
DWORD childstatus;
gint iterations = 0;
/* try to read data from the pipe only 5 times, to avoid blocking */
while (iterations < 5) {
/* Oddly enough although Named pipes don't work on win9x,
PeekNamedPipe does !!! */
handle = (HANDLE)_get_osfhandle(pipe_source_);
result = PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL);
/* Get the child process exit status */
result1 = GetExitCodeProcess((HANDLE)*(pipe_child_process_),
&childstatus);
/* If the Peek returned an error, or there are bytes to be read
or the childwatcher thread has terminated then call the normal
callback */
if (!result || avail > 0 || childstatus != STILL_ACTIVE) {
/* And call the real handler */
if (!pipe_input_cb_(pipe_source_, pipe_user_data_)) {
ws_log(LOG_DOMAIN_MAIN, LOG_LEVEL_DEBUG, "pipe_timer_cb: input pipe closed, iterations: %u", iterations);
/* pipe closed, return false so that the old timer is not run again */
delete pipe_timer_;
return;
}
} else {
/* No data, stop now */
break;
}
iterations++;
}
#endif // _WIN32
}
void WiresharkMainWindow::pipeActivated(int source) {
Q_UNUSED(source)
#ifndef _WIN32
ws_assert(source == pipe_source_);
pipe_notifier_->setEnabled(false);
if (pipe_input_cb_(pipe_source_, pipe_user_data_)) {
pipe_notifier_->setEnabled(true);
}
else {
delete pipe_notifier_;
}
#endif // _WIN32
}
void WiresharkMainWindow::pipeNotifierDestroyed()
{
void WiresharkMainWindow::popLiveCaptureInProgress() {
/* Pop the "<live capture in progress>" message off the status bar. */
main_ui_->statusBar->setFileName(capture_file_);
#ifdef _WIN32
pipe_timer_ = NULL;
#else
pipe_notifier_ = NULL;
#endif // _WIN32
}
void WiresharkMainWindow::stopCapture() {

View File

@ -45,11 +45,6 @@ extern void main_window_update(void);
/* Exit routine provided by UI-specific code. */
extern void exit_application(int status);
/* read from a pipe (callback) */
typedef gboolean (*pipe_input_cb_t) (gint source, gpointer user_data);
/* install callback function, called if pipe input is available */
extern void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb);
/* XXX - Yes this isn't the best place, but they are used by file_dlg_win32.c, which is supposed
to be GUI independent, but has lots of GTK leanings. But if you put these in a GTK UI
header file, file_dlg_win32.c complains about all of the GTK structures also in the header