/* * pcap-monitor.c - monitors a bus and dumps messages to a pcap file * Copyright © 2011–2012 Collabora Ltd. * Copyright © 2018 Will Thompson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "pcap-monitor.h" #include #include #include #include #include #ifndef DLT_DBUS # define DLT_DBUS 231 #endif /* * Transitions: * * NEW --[ initable_init() errors ]--> STOPPED * NEW --[ initable_init() ]--> STARTING * * STARTING --[ start_pcap() ]--> RUNNING * STARTING --[ error ]--> STOPPING * * RUNNING --[ user request/error ]--> STOPPING * * STOPPING --[ all finished ]--> STOPPED */ typedef enum { /* Nothing's happened yet */ STATE_NEW, /* We're waiting to read the pcap header from the subprocess */ STATE_STARTING, /* We've read the pcap header, messages are flowing freely */ STATE_RUNNING, /* Error or user request is causing us to stop; we're waiting to consume * everything from the pipe and for the dbus-monitor subprocess to exit. */ STATE_STOPPING, /* We've stopped, whether by user decision or because of an error. * Everything has been torn down (except possibly a root-owned subprocess), * the output file has been fully written and closed, and no more signals * will be emitted. */ STATE_STOPPED, } BustlePcapMonitorState; static const gchar * const STATES[] = { "NEW", "STARTING", "RUNNING", "STOPPING", "STOPPED", }; struct _BustlePcapMonitorPrivate { GBusType bus_type; gchar *address; BustlePcapMonitorState state; GCancellable *cancellable; guint cancellable_cancelled_id; /* input */ GSubprocess *dbus_monitor; GSource *dbus_monitor_source; pcap_t *pcap_in; /* output */ gchar *filename; pcap_t *pcap_out; pcap_dumper_t *dumper; /* errors */ GError *pcap_error; GError *subprocess_error; guint await_both_errors_id; }; enum { PROP_BUS_TYPE = 1, PROP_ADDRESS, PROP_FILENAME, }; enum { SIG_MESSAGE_LOGGED, SIG_STOPPED, N_SIGNALS }; static guint signals[N_SIGNALS]; static void initable_iface_init ( gpointer g_class, gpointer unused); G_DEFINE_TYPE_WITH_CODE (BustlePcapMonitor, bustle_pcap_monitor, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init); ) static void bustle_pcap_monitor_init (BustlePcapMonitor *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, BUSTLE_TYPE_PCAP_MONITOR, BustlePcapMonitorPrivate); self->priv->bus_type = G_BUS_TYPE_SESSION; self->priv->state = STATE_NEW; self->priv->cancellable = g_cancellable_new (); } static void bustle_pcap_monitor_get_property ( GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); BustlePcapMonitorPrivate *priv = self->priv; switch (property_id) { case PROP_BUS_TYPE: g_value_set_enum (value, priv->bus_type); break; case PROP_ADDRESS: g_value_set_string (value, priv->address); break; case PROP_FILENAME: g_value_set_string (value, priv->filename); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void bustle_pcap_monitor_set_property ( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); BustlePcapMonitorPrivate *priv = self->priv; switch (property_id) { case PROP_BUS_TYPE: priv->bus_type = g_value_get_enum (value); break; case PROP_ADDRESS: priv->address = g_value_dup_string (value); break; case PROP_FILENAME: priv->filename = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void close_dump (BustlePcapMonitor *self) { BustlePcapMonitorPrivate *priv = self->priv; if (priv->dumper != NULL) pcap_dump_flush (priv->dumper); g_clear_pointer (&priv->dumper, pcap_dump_close); g_clear_pointer (&priv->pcap_out, pcap_close); } static void bustle_pcap_monitor_dispose (GObject *object) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); BustlePcapMonitorPrivate *priv = self->priv; GObjectClass *parent_class = bustle_pcap_monitor_parent_class; if (priv->cancellable_cancelled_id != 0) { g_assert (priv->cancellable != NULL); g_cancellable_disconnect (priv->cancellable, priv->cancellable_cancelled_id); priv->cancellable_cancelled_id = 0; } g_clear_object (&priv->cancellable); g_clear_pointer (&priv->dbus_monitor_source, g_source_destroy); g_clear_pointer (&priv->pcap_in, pcap_close); g_clear_object (&priv->dbus_monitor); close_dump (self); if (parent_class->dispose != NULL) parent_class->dispose (object); } static void bustle_pcap_monitor_finalize (GObject *object) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); BustlePcapMonitorPrivate *priv = self->priv; GObjectClass *parent_class = bustle_pcap_monitor_parent_class; g_clear_pointer (&priv->address, g_free); g_clear_pointer (&priv->filename, g_free); g_clear_error (&priv->pcap_error); g_clear_error (&priv->subprocess_error); if (parent_class->finalize != NULL) parent_class->finalize (object); } static void bustle_pcap_monitor_class_init (BustlePcapMonitorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *param_spec; object_class->get_property = bustle_pcap_monitor_get_property; object_class->set_property = bustle_pcap_monitor_set_property; object_class->dispose = bustle_pcap_monitor_dispose; object_class->finalize = bustle_pcap_monitor_finalize; g_type_class_add_private (klass, sizeof (BustlePcapMonitorPrivate)); #define THRICE(x) x, x, x param_spec = g_param_spec_enum ( "bus-type", "Bus type", "Which standard bus to monitor. If NONE, :address must be non-NULL.", G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_BUS_TYPE, param_spec); param_spec = g_param_spec_string ( "address", "Address", "Address of bus to monitor. If non-NULL, :bus-type must be G_BUS_TYPE_NONE", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_ADDRESS, param_spec); param_spec = g_param_spec_string (THRICE ("filename"), NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_FILENAME, param_spec); /** * BustlePcapMonitor::message-logged: * @self: the monitor. * @sec: seconds since 1970. * @usec: microseconds! (These are not combined into a single %gint64 because * gtk2hs as of 2018-01-08 crashes when it encounters %G_TYPE_UINT64 in a * #GValue.) * @blob: an array of bytes containing the serialized message. * @length: the size in bytes of @blob. */ signals[SIG_MESSAGE_LOGGED] = g_signal_new ("message-logged", BUSTLE_TYPE_PCAP_MONITOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 4, G_TYPE_LONG, G_TYPE_LONG, G_TYPE_POINTER, G_TYPE_UINT); /** * BustlePcapMonitor::stopped: * @self: the monitor * @domain: domain of a #GError (as G_TYPE_UINT because there is no * G_TYPE_UINT32) * @code: code of a #GError * @message: message of a #GError * * Emitted once when monitoring stops, whether triggered (asynchronously) by * calling bustle_pcap_monitor_stop(), in which case @domain will be * %G_IO_ERROR and @code will be %G_IO_ERROR_CANCELLED, or because an error * occurs. */ signals[SIG_STOPPED] = g_signal_new ("stopped", BUSTLE_TYPE_PCAP_MONITOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING); } static void handle_error (BustlePcapMonitor *self) { BustlePcapMonitorPrivate *priv = self->priv; g_autoptr(GError) error = NULL; g_return_if_fail (priv->pcap_error != NULL || priv->subprocess_error != NULL); if (priv->pcap_error != NULL) g_debug ("%s: pcap_error: %s", G_STRFUNC, priv->pcap_error->message); if (priv->subprocess_error != NULL) g_debug ("%s: subprocess_error: %s", G_STRFUNC, priv->subprocess_error->message); if (priv->state == STATE_STOPPED) { g_debug ("%s: already stopped", G_STRFUNC); return; } /* Check for pkexec errors. Signal these in preference to all others. */ if (priv->subprocess_error != NULL && priv->bus_type == G_BUS_TYPE_SYSTEM) { if (g_error_matches (priv->subprocess_error, G_SPAWN_EXIT_ERROR, 126)) { /* dialog dismissed */ g_set_error (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "User dismissed polkit authorization dialog"); } else if (g_error_matches (priv->subprocess_error, G_SPAWN_EXIT_ERROR, 127)) { /* not authorized, authorization couldn't be obtained through * authentication, or an priv->subprocess_error occurred */ g_set_error (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, "Not authorized to monitor system bus"); } } if (g_error_matches (priv->subprocess_error, G_SPAWN_EXIT_ERROR, 0)) { /* I believe clean exit only happens if the bus is shut down. This might * happen if you're using Bustle to monitor a test suite, or perhaps a * user session that you log out of. Let's consider this to be * cancellation. */ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, priv->subprocess_error->message); } if (error == NULL) { /* If no pkexec errors, prefer potentially more informative errors from * libpcap, including the wonderful snaplen bug. */ if (priv->pcap_error != NULL) { error = g_steal_pointer (&priv->pcap_error); } /* Otherwise, the "subprocess didn't work" error will have to do. */ else { error = g_steal_pointer (&priv->subprocess_error); if (priv->state == STATE_STARTING) g_prefix_error (&error, "Failed to start dbus-monitor: "); } } priv->state = STATE_STOPPED; close_dump (self); g_debug ("%s: emitting ::stopped(%s, %d, %s)", G_STRFUNC, g_quark_to_string (error->domain), error->code, error->message); g_signal_emit (self, signals[SIG_STOPPED], 0, (guint) error->domain, error->code, error->message); g_clear_handle_id (&priv->await_both_errors_id, g_source_remove); } static gboolean await_both_errors_cb (gpointer data) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (data); BustlePcapMonitorPrivate *priv = self->priv; handle_error (self); priv->await_both_errors_id = 0; return G_SOURCE_REMOVE; } /* Wait for the dbus-monitor subprocess to have exited, and for the pcap reader * to have finished. We expect the reader to finish promptly when the * subprocess does, but the subprocess may not die until it tries to read to a * closed pipe (if the user stops the recording). So we wait a couple of * seconds before pressing on. */ static void await_both_errors (BustlePcapMonitor *self) { BustlePcapMonitorPrivate *priv = self->priv; if (priv->state == STATE_STOPPED) return; else if (priv->subprocess_error != NULL && priv->pcap_error != NULL) handle_error (self); else if (priv->await_both_errors_id == 0) priv->await_both_errors_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 2, await_both_errors_cb, g_object_ref (self), g_object_unref); } static gboolean list_all_names ( GDBusProxy *bus, GError **error) { g_autoptr(GVariant) ret = NULL; gchar **names; /* borrowed from 'ret' */ g_return_val_if_fail (G_IS_DBUS_PROXY (bus), FALSE); ret = g_dbus_proxy_call_sync (bus, "ListNames", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (ret == NULL) { g_prefix_error (error, "Couldn't ListNames: "); return FALSE; } for (g_variant_get_child (ret, 0, "^a&s", &names); *names != NULL; names++) { gchar *name = *names; if (!g_dbus_is_unique_name (name) && strcmp (name, "org.freedesktop.DBus") != 0) { g_autoptr(GVariant) owner = g_dbus_proxy_call_sync (bus, "GetNameOwner", g_variant_new ("(s)", name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); /* Ignore returned value or error. These are just used by the UI to * fill in the initial owners of each well-known name. If we get an * error here, the owner disappeared between ListNames() and here; * but that means we'll have seen a NameOwnerChanged from which the * UI can (in theory) infer who the owner used to be. * * We cannot use G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED because we * do want the reply to be sent to us. */ } } return TRUE; } static GDBusConnection * get_connection ( BustlePcapMonitor *self, GCancellable *cancellable, GError **error) { BustlePcapMonitorPrivate *priv = self->priv; g_autofree gchar *address_to_free = NULL; const gchar *address = priv->address; if (priv->address != NULL) { address = priv->address; } else { address_to_free = g_dbus_address_get_for_bus_sync (priv->bus_type, cancellable, error); if (address_to_free == NULL) { g_prefix_error (error, "Couldn't get bus address: "); return FALSE; } address = address_to_free; } return g_dbus_connection_new_for_address_sync ( address, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, NULL, /* auth observer */ cancellable, error); } static void dump_names_thread_func ( GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (source_object); g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GDBusProxy) bus = NULL; g_autoptr(GError) error = NULL; connection = get_connection (self, cancellable, &error); if (connection != NULL) { bus = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", cancellable, &error); } if (bus != NULL && list_all_names (bus, &error)) g_task_return_boolean (task, TRUE); else g_task_return_error (task, g_steal_pointer (&error)); g_assert (error == NULL); if (connection != NULL && !g_dbus_connection_close_sync (connection, cancellable, &error)) g_warning ("%s: %s", G_STRFUNC, error->message); } static void dump_names_cb ( GObject *source_object, GAsyncResult *result, gpointer user_data) { g_autoptr(GError) error = NULL; if (!g_task_propagate_boolean (G_TASK (result), &error)) g_warning ("Failed to dump names: %s", error->message); } static void dump_names_async ( BustlePcapMonitor *self) { BustlePcapMonitorPrivate *priv = self->priv; g_autoptr(GTask) task = g_task_new (self, priv->cancellable, dump_names_cb, NULL); g_task_run_in_thread (task, dump_names_thread_func); } static gboolean start_pcap ( BustlePcapMonitor *self, GError **error) { BustlePcapMonitorPrivate *priv = self->priv; GInputStream *stdout_pipe = NULL; gint stdout_fd = -1; FILE *dbus_monitor_filep = NULL; char errbuf[PCAP_ERRBUF_SIZE] = {0}; stdout_pipe = g_subprocess_get_stdout_pipe (priv->dbus_monitor); g_return_val_if_fail (stdout_pipe != NULL, FALSE); stdout_fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (stdout_pipe)); g_return_val_if_fail (stdout_fd >= 0, FALSE); dbus_monitor_filep = fdopen (stdout_fd, "r"); if (dbus_monitor_filep == NULL) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "fdopen"); return FALSE; } /* fd is owned by the FILE * now */ g_unix_input_stream_set_close_fd (G_UNIX_INPUT_STREAM (stdout_pipe), FALSE); /* This reads the 4-byte pcap header from the pipe, in a single blocking * fread(). It's safe to do this on the main thread, since we know the pipe * is readable. On short read, pcap_fopen_offline() fails immediately. */ priv->pcap_in = pcap_fopen_offline (dbus_monitor_filep, errbuf); if (priv->pcap_in == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't read messages from dbus-monitor: %s", errbuf); /* Cause dbus-monitor to exit next time it tries to write a message */ g_clear_pointer (&dbus_monitor_filep, fclose); /* And try to terminate it immediately. When spawning via pkexec we may * not be able to kill it. */ g_subprocess_force_exit (priv->dbus_monitor); return FALSE; } /* pcap_close() will call fclose() on the FILE * passed to * pcap_fopen_offline() */ dump_names_async (self); priv->state = STATE_RUNNING; return TRUE; } static gboolean read_one ( BustlePcapMonitor *self, GError **error) { BustlePcapMonitorPrivate *priv = self->priv; struct pcap_pkthdr *hdr; const guchar *blob; int ret; ret = pcap_next_ex (priv->pcap_in, &hdr, &blob); switch (ret) { case 1: g_signal_emit (self, signals[SIG_MESSAGE_LOGGED], 0, hdr->ts.tv_sec, hdr->ts.tv_usec, blob, hdr->caplen); /* cast necessary because pcap_dump has a type matching the callback * argument to pcap_loop() * TODO don't block */ pcap_dump ((u_char *) priv->dumper, hdr, blob); return TRUE; case -2: /* EOF; shouldn't happen since we waited for the FD to be readable */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED, "EOF when reading from dbus-monitor"); return FALSE; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Error %i reading dbus-monitor stream: %s", ret, pcap_geterr (priv->pcap_in)); return FALSE; } } static gboolean dbus_monitor_readable ( GObject *pollable_input_stream, gpointer user_data) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data); BustlePcapMonitorPrivate *priv = self->priv; gboolean (*read_func) (BustlePcapMonitor *, GError **); g_return_val_if_fail (priv->pcap_error == NULL, FALSE); if (g_cancellable_set_error_if_cancelled (priv->cancellable, &priv->pcap_error)) { await_both_errors (self); return FALSE; } switch (priv->state) { case STATE_STARTING: read_func = start_pcap; break; case STATE_RUNNING: case STATE_STOPPING: /* may have a few last messages to read */ read_func = read_one; break; default: g_critical ("%s in unexpected state %d (%s)", G_STRFUNC, priv->state, STATES[priv->state]); return FALSE; } if (!read_func (self, &priv->pcap_error)) { await_both_errors (self); return FALSE; } return TRUE; } static void wait_check_cb ( GObject *source, GAsyncResult *result, gpointer user_data) { g_autoptr(BustlePcapMonitor) self = BUSTLE_PCAP_MONITOR (user_data); BustlePcapMonitorPrivate *priv = self->priv; GSubprocess *dbus_monitor = G_SUBPROCESS (source); g_return_if_fail (priv->subprocess_error == NULL); if (g_subprocess_wait_check_finish (dbus_monitor, result, &priv->subprocess_error)) { g_set_error (&priv->subprocess_error, G_SPAWN_EXIT_ERROR, 0, "dbus-monitor exited cleanly"); } /* cases: * - G_SPAWN_ERROR / G_SPAWN_ERROR_FAILED / "Child process killed by signal N": * dbus-monitor was killed, possibly by us calling * g_subprocess_force_exit(), though this doesn't work for pkexec'd * dbus-monitor * - G_SPAWN_EXIT_ERROR: * - 0: bus itself went away (assuming pkexec/flatpak-spawn propagate * errors correctly) * - 1: anything else went wrong in dbus-monitor, including invalid * arguments and broken pipe (when we close the read end) * - 126: User dismissed polkit authentication dialog * - 127: polkit auth failed * - 128 + N: killed by signal N, propagated by flatpak-spawn --host * * We just need to deal with 0, 126, 127 specially. */ await_both_errors (self); } static void cancellable_cancelled_cb (GCancellable *cancellable, gpointer user_data) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data); BustlePcapMonitorPrivate *priv = self->priv; /* Closes the stream; should cause dbus-monitor to quit in due course when it * tries to write to the other end of the pipe. */ g_clear_pointer (&priv->pcap_in, pcap_close); if (priv->dbus_monitor != NULL) { /* Try to make it stop sooner; this has no effect on a privileged * dbus-monitor. */ g_subprocess_force_exit (priv->dbus_monitor); } } static gboolean initable_init ( GInitable *initable, GCancellable *cancellable, GError **error) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (initable); BustlePcapMonitorPrivate *priv = self->priv; gboolean in_flatpak = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); g_autoptr(GPtrArray) dbus_monitor_argv = g_ptr_array_sized_new (8); GInputStream *stdout_pipe = NULL; if (in_flatpak) { g_ptr_array_add (dbus_monitor_argv, "flatpak-spawn"); g_ptr_array_add (dbus_monitor_argv, "--host"); } if (priv->bus_type == G_BUS_TYPE_SYSTEM) g_ptr_array_add (dbus_monitor_argv, "pkexec"); g_ptr_array_add (dbus_monitor_argv, "dbus-monitor"); g_ptr_array_add (dbus_monitor_argv, "--pcap"); switch (priv->bus_type) { case G_BUS_TYPE_SESSION: g_return_val_if_fail (priv->address == NULL, FALSE); g_ptr_array_add (dbus_monitor_argv, "--session"); break; case G_BUS_TYPE_SYSTEM: g_return_val_if_fail (priv->address == NULL, FALSE); g_ptr_array_add (dbus_monitor_argv, "--system"); break; case G_BUS_TYPE_NONE: g_return_val_if_fail (priv->address != NULL, FALSE); g_ptr_array_add (dbus_monitor_argv, "--address"); g_ptr_array_add (dbus_monitor_argv, priv->address); break; default: g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Can only log the session bus, system bus, or a given address"); return FALSE; } g_ptr_array_add (dbus_monitor_argv, NULL); if (priv->filename == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "You must specify a filename"); return FALSE; } priv->cancellable_cancelled_id = g_cancellable_connect (priv->cancellable, G_CALLBACK (cancellable_cancelled_cb), self, NULL); priv->pcap_out = pcap_open_dead (DLT_DBUS, 1 << 27); if (priv->pcap_out == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "pcap_open_dead failed. wtf"); return FALSE; } priv->dumper = pcap_dump_open (priv->pcap_out, priv->filename); if (priv->dumper == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't open target file %s", pcap_geterr (priv->pcap_out)); return FALSE; } priv->dbus_monitor = g_subprocess_newv ( (const gchar * const *) dbus_monitor_argv->pdata, G_SUBPROCESS_FLAGS_STDOUT_PIPE, error); if (priv->dbus_monitor == NULL) { return FALSE; } stdout_pipe = g_subprocess_get_stdout_pipe (priv->dbus_monitor); g_return_val_if_fail (stdout_pipe != NULL, FALSE); g_return_val_if_fail (G_IS_POLLABLE_INPUT_STREAM (stdout_pipe), FALSE); g_return_val_if_fail (G_IS_UNIX_INPUT_STREAM (stdout_pipe), FALSE); priv->dbus_monitor_source = g_pollable_input_stream_create_source ( G_POLLABLE_INPUT_STREAM (stdout_pipe), priv->cancellable); g_source_set_callback (priv->dbus_monitor_source, (GSourceFunc) dbus_monitor_readable, self, NULL); g_source_attach (priv->dbus_monitor_source, NULL); g_subprocess_wait_check_async ( priv->dbus_monitor, priv->cancellable, wait_check_cb, g_object_ref (self)); priv->state = STATE_STARTING; return TRUE; } /* FIXME: instead of GInitable + syncronous stop, have * bustle_pcap_monitor_record_{async,finish} */ void bustle_pcap_monitor_stop ( BustlePcapMonitor *self) { BustlePcapMonitorPrivate *priv = self->priv; if (priv->state == STATE_STOPPED || priv->state == STATE_STOPPING || priv->state == STATE_NEW) { g_debug ("%s: already in state %s", G_STRFUNC, STATES[priv->state]); return; } priv->state = STATE_STOPPING; g_cancellable_cancel (priv->cancellable); } static void initable_iface_init ( gpointer g_class, gpointer unused) { GInitableIface *iface = g_class; iface->init = initable_init; } BustlePcapMonitor * bustle_pcap_monitor_new ( GBusType bus_type, const gchar *address, const gchar *filename, GError **error) { return g_initable_new ( BUSTLE_TYPE_PCAP_MONITOR, NULL, error, "bus-type", bus_type, "address", address, "filename", filename, NULL); }