/* * 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 */ #define _GNU_SOURCE #include "config.h" #include "pcap-monitor.h" #include #include #include #include #include #include #include #include #include #include #include "pcap-reader.h" /* Prefix of name claimed by the connection that collects name owners. */ const char *BUSTLE_MONITOR_NAME_PREFIX = "org.freedesktop.Bustle.Monitor."; static gboolean RUNNING_IN_FLATPAK = FALSE; /* * 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", }; typedef struct _BustlePcapMonitor { GObject parent; GBusType bus_type; gchar *address; BustlePcapMonitorState state; GCancellable *cancellable; guint cancellable_cancelled_id; /* input */ GSubprocess *dbus_monitor; /* If >= 0, master side of controlling terminal for dbus_monitor */ int pt_master; GSubprocess *tee_proc; GSource *tee_source; BustlePcapReader *reader; /* output */ gchar *filename; /* errors */ GError *pcap_error; GError *subprocess_error; guint await_both_errors_id; } BustlePcapMonitor; 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); ) /* A sad echo of the functions in libglnx. */ static inline void * throw_errno (GError **error, const gchar *prefix) { int errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), "%s: %s", prefix, g_strerror (errsv)); return NULL; } static void bustle_pcap_monitor_init (BustlePcapMonitor *self) { self->bus_type = G_BUS_TYPE_SESSION; self->state = STATE_NEW; self->cancellable = g_cancellable_new (); self->pt_master = -1; } static void bustle_pcap_monitor_get_property ( GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); switch (property_id) { case PROP_BUS_TYPE: g_value_set_enum (value, self->bus_type); break; case PROP_ADDRESS: g_value_set_string (value, self->address); break; case PROP_FILENAME: g_value_set_string (value, self->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); switch (property_id) { case PROP_BUS_TYPE: self->bus_type = g_value_get_enum (value); break; case PROP_ADDRESS: self->address = g_value_dup_string (value); break; case PROP_FILENAME: self->filename = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void bustle_pcap_monitor_dispose (GObject *object) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); GObjectClass *parent_class = bustle_pcap_monitor_parent_class; if (self->cancellable_cancelled_id != 0) { g_assert (self->cancellable != NULL); g_cancellable_disconnect (self->cancellable, self->cancellable_cancelled_id); self->cancellable_cancelled_id = 0; } g_clear_object (&self->cancellable); g_clear_pointer (&self->tee_source, g_source_destroy); g_clear_object (&self->tee_proc); g_clear_object (&self->reader); g_clear_object (&self->dbus_monitor); if (parent_class->dispose != NULL) parent_class->dispose (object); } static void bustle_pcap_monitor_finalize (GObject *object) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (object); GObjectClass *parent_class = bustle_pcap_monitor_parent_class; g_clear_pointer (&self->address, g_free); g_clear_pointer (&self->filename, g_free); g_clear_error (&self->pcap_error); g_clear_error (&self->subprocess_error); if (self->pt_master >= 0) { g_close (self->pt_master, NULL); self->pt_master = -1; } 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; #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. * @message: @blob as a #GDBusMessage. */ signals[SIG_MESSAGE_LOGGED] = g_signal_new ("message-logged", BUSTLE_TYPE_PCAP_MONITOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 5, G_TYPE_LONG, G_TYPE_LONG, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_DBUS_MESSAGE); /** * 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); RUNNING_IN_FLATPAK = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); } static void handle_error (BustlePcapMonitor *self) { g_autoptr(GError) error = NULL; g_return_if_fail (self->pcap_error != NULL || self->subprocess_error != NULL); if (self->pcap_error != NULL) g_debug ("%s: pcap_error: %s", G_STRFUNC, self->pcap_error->message); if (self->subprocess_error != NULL) g_debug ("%s: subprocess_error: %s", G_STRFUNC, self->subprocess_error->message); if (self->state == STATE_STOPPED) { g_debug ("%s: already stopped", G_STRFUNC); return; } /* Check for pkexec errors. Signal these in preference to all others. */ if (self->subprocess_error != NULL && self->bus_type == G_BUS_TYPE_SYSTEM) { if (g_error_matches (self->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 (self->subprocess_error, G_SPAWN_EXIT_ERROR, 127)) { /* not authorized, authorization couldn't be obtained through * authentication, or an self->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 (self->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, self->subprocess_error->message); } if (error == NULL) { /* If no pkexec errors, prefer potentially more informative errors from * libpcap, including the wonderful snaplen bug. */ if (self->pcap_error != NULL) { error = g_steal_pointer (&self->pcap_error); } /* Otherwise, the "subprocess didn't work" error will have to do. */ else { error = g_steal_pointer (&self->subprocess_error); if (self->state == STATE_STARTING) g_prefix_error (&error, "Failed to start dbus-monitor: "); } } self->state = STATE_STOPPED; 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 (&self->await_both_errors_id, g_source_remove); } static gboolean await_both_errors_cb (gpointer data) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (data); handle_error (self); self->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) { if (self->state == STATE_STOPPED) return; else if (self->subprocess_error != NULL && self->pcap_error != NULL) handle_error (self); else if (self->await_both_errors_id == 0) self->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) { g_autofree gchar *address_to_free = NULL; const gchar *address = self->address; if (self->address != NULL) { address = self->address; } else { address_to_free = g_dbus_address_get_for_bus_sync (self->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) { const gchar *unique_name = g_dbus_connection_get_unique_name (connection); if (unique_name != NULL) { g_autofree gchar *mangled = g_strdup (unique_name); g_autofree gchar *well_known_name = g_strconcat (BUSTLE_MONITOR_NAME_PREFIX, /* ":3.14" -> "_3_14", a legal bus name component */ g_strcanon (mangled, "0123456789", '_'), NULL); g_debug ("%s: attempting to own %s", G_STRFUNC, well_known_name); /* Ignore returned ID: we'll own it until we disconnect. * * Also ignore callbacks: we don't need to be notified, since this * name is just for the benefit of the viewer. On the system bus we * won't be able to own this name, but if we're smart we can teach * the viewer that merely requesting the name is enough to hide this * connection. */ g_bus_own_name_on_connection (connection, well_known_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL /* acquired */, NULL /* lost */, NULL /* user_data */, NULL /* free_func */); } bus = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, 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) { g_autoptr(GTask) task = g_task_new (self, self->cancellable, dump_names_cb, NULL); g_task_run_in_thread (task, dump_names_thread_func); } /** * send_sigint: * * Send SIGINT to the dbus-monitor subprocess, in as many ways as possible. */ static void send_sigint (BustlePcapMonitor *self) { /* Send the signal directly; this has no effect on the privileged subprocess * used on the system bus. */ if (self->dbus_monitor != NULL) g_subprocess_send_signal (self->dbus_monitor, SIGINT); /* Send it via the PTY that we set as the subprocess's controlling terminal; * this works even for a privileged child. */ if (self->pt_master >= 0) { char ctrl_c = '\x03'; if (write (self->pt_master, &ctrl_c, 1) != 1) { g_autoptr(GError) local_error = NULL; throw_errno (&local_error, "write to pt_master failed"); g_warning ("%s: %s", G_STRFUNC, local_error->message); } } } static gboolean start_pcap ( BustlePcapMonitor *self, GError **error) { GInputStream *stdout_pipe = NULL; gint stdout_fd = -1; FILE *dbus_monitor_filep = NULL; stdout_pipe = g_subprocess_get_stdout_pipe (self->tee_proc); 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) { throw_errno (error, "fdopen failed"); 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. */ self->reader = bustle_pcap_reader_fopen (g_steal_pointer (&dbus_monitor_filep), error); if (self->reader == NULL) { g_prefix_error (error, "Couldn't read messages from dbus-monitor: "); /* Try to terminate dbus-monitor immediately. The reader closes the FILE * on error. */ send_sigint (self); return FALSE; } dump_names_async (self); self->state = STATE_RUNNING; return TRUE; } static gboolean read_one ( BustlePcapMonitor *self, GError **error) { glong sec, usec; const guchar *blob; guint length; g_autoptr(GDBusMessage) message = NULL; if (!bustle_pcap_reader_read_one (self->reader, &sec, &usec, &blob, &length, &message, error)) { return FALSE; } else if (message == NULL) { /* 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; } else { g_signal_emit (self, signals[SIG_MESSAGE_LOGGED], 0, sec, usec, blob, length, message); return TRUE; } } static gboolean dbus_monitor_readable ( GObject *pollable_input_stream, gpointer user_data) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (user_data); gboolean (*read_func) (BustlePcapMonitor *, GError **); g_return_val_if_fail (self->pcap_error == NULL, FALSE); if (g_cancellable_set_error_if_cancelled (self->cancellable, &self->pcap_error)) { await_both_errors (self); return FALSE; } switch (self->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, self->state, STATES[self->state]); return FALSE; } if (!read_func (self, &self->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); GSubprocess *dbus_monitor = G_SUBPROCESS (source); g_return_if_fail (self->subprocess_error == NULL); if (g_subprocess_wait_check_finish (dbus_monitor, result, &self->subprocess_error)) { g_set_error (&self->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, presumably by send_sigint() * - 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); /* Closes the stream; should cause dbus-monitor to quit in due course when it * tries to write to the other end of the pipe. */ bustle_pcap_reader_close (self->reader); /* And try to terminate it immediately. */ send_sigint (self); } static const char ** build_argv (BustlePcapMonitor *self, GError **error) { g_autoptr(GPtrArray) dbus_monitor_argv = g_ptr_array_sized_new (8); if (RUNNING_IN_FLATPAK) { g_ptr_array_add (dbus_monitor_argv, "flatpak-spawn"); g_ptr_array_add (dbus_monitor_argv, "--host"); } if (self->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 (self->bus_type) { case G_BUS_TYPE_SESSION: g_return_val_if_fail (self->address == NULL, FALSE); g_ptr_array_add (dbus_monitor_argv, "--session"); break; case G_BUS_TYPE_SYSTEM: g_return_val_if_fail (self->address == NULL, FALSE); g_ptr_array_add (dbus_monitor_argv, "--system"); break; case G_BUS_TYPE_NONE: g_return_val_if_fail (self->address != NULL, FALSE); g_ptr_array_add (dbus_monitor_argv, "--address"); g_ptr_array_add (dbus_monitor_argv, self->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 NULL; } g_ptr_array_add (dbus_monitor_argv, NULL); return (const char **) g_ptr_array_free (g_steal_pointer (&dbus_monitor_argv), FALSE); } /** * set_controlling_tty_to_stdin: * * child_setup function for the dbus-monitor subprocess which arranges for its * controlling TTY to be a PTY, so we can send SIGINT to it even if it's * privileged. */ static void set_controlling_tty_to_stdin (void *user_data G_GNUC_UNUSED) { /* Move this child process to a new session, unsetting any existing * controlling terminal. */ setsid (); /* Make stdin (the worker end of a PTY pair) the controlling terminal for * this child process. */ ioctl (STDIN_FILENO, TIOCSCTTY, 0); } static GSubprocess * spawn_monitor (BustlePcapMonitor *self, const char *const *argv, GError **error) { g_autoptr(GSubprocessLauncher) launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); self->pt_master = posix_openpt (O_RDWR | O_NOCTTY | O_CLOEXEC); if (self->pt_master < 0) return throw_errno (error, "posix_openpt failed"); if (unlockpt (self->pt_master) < 0) return throw_errno (error, "unlockpt failed"); char sname[PATH_MAX] = { 0 }; if (ptsname_r (self->pt_master, sname, G_N_ELEMENTS (sname)) != 0) return throw_errno (error, "ptsname_r failed"); int pt_slave = open (sname, O_RDWR); if (pt_slave < 0) return throw_errno (error, "open(sname) failed"); g_subprocess_launcher_take_stdin_fd (launcher, pt_slave); if (!RUNNING_IN_FLATPAK) g_subprocess_launcher_set_child_setup (launcher, set_controlling_tty_to_stdin, NULL, NULL); /* otherwise, the session-helper process already does this for us */ GSubprocess *child = g_subprocess_launcher_spawnv (launcher, argv, error); g_close (pt_slave, NULL); return child; } static GSubprocess * spawn_tee (BustlePcapMonitor *self, GError **error) { g_autoptr(GSubprocessLauncher) launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); GInputStream *stdout_pipe = NULL; gint stdout_fd = -1; stdout_pipe = g_subprocess_get_stdout_pipe (self->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); g_subprocess_launcher_take_stdin_fd (launcher, stdout_fd); return g_subprocess_launcher_spawn (launcher, error, "tee", self->filename, NULL); } static gboolean initable_init ( GInitable *initable, GCancellable *cancellable, GError **error) { BustlePcapMonitor *self = BUSTLE_PCAP_MONITOR (initable); g_autofree const char **argv = NULL; GInputStream *stdout_pipe = NULL; argv = build_argv (self, error); if (NULL == argv) return FALSE; if (self->filename == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "You must specify a filename"); return FALSE; } self->cancellable_cancelled_id = g_cancellable_connect (self->cancellable, G_CALLBACK (cancellable_cancelled_cb), self, NULL); self->dbus_monitor = spawn_monitor (self, (const char * const *) argv, error); if (self->dbus_monitor == NULL) return FALSE; self->tee_proc = spawn_tee (self, error); if (self->tee_proc == NULL) return FALSE; stdout_pipe = g_subprocess_get_stdout_pipe (self->tee_proc); 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); self->tee_source = g_pollable_input_stream_create_source ( G_POLLABLE_INPUT_STREAM (stdout_pipe), self->cancellable); g_source_set_callback (self->tee_source, (GSourceFunc) dbus_monitor_readable, self, NULL); g_source_attach (self->tee_source, NULL); g_subprocess_wait_check_async ( self->dbus_monitor, self->cancellable, wait_check_cb, g_object_ref (self)); self->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) { if (self->state == STATE_STOPPED || self->state == STATE_STOPPING || self->state == STATE_NEW) { g_debug ("%s: already in state %s", G_STRFUNC, STATES[self->state]); return; } self->state = STATE_STOPPING; g_cancellable_cancel (self->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); }