/* sysprof-proxied-instrument.c * * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "config.h" #include #include "sysprof-proxied-instrument-private.h" #include "sysprof-recording-private.h" enum { PROP_0, PROP_BUS_TYPE, PROP_BUS_NAME, PROP_OBJECT_PATH, N_PROPS }; G_DEFINE_TYPE (SysprofProxiedInstrument, sysprof_proxied_instrument, SYSPROF_TYPE_INSTRUMENT) static GParamSpec *properties [N_PROPS]; typedef struct _Record { SysprofRecording *recording; DexFuture *cancellable; char *bus_name; char *object_path; GBusType bus_type; guint call_stop_first : 1; } Record; static void record_free (Record *record) { g_clear_object (&record->recording); dex_clear (&record->cancellable); g_clear_pointer (&record->bus_name, g_free); g_clear_pointer (&record->object_path, g_free); g_free (record); } static DexFuture * sysprof_proxied_instrument_record_fiber (gpointer user_data) { Record *record = user_data; SysprofCaptureWriter *writer; SysprofCaptureReader *reader; g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(DexFuture) started = NULL; g_autoptr(GError) error = NULL; GVariantDict options = G_VARIANT_DICT_INIT (NULL); g_autofd int proxy_fd = -1; int handle; g_assert (record != NULL); g_assert (record->bus_type != G_BUS_TYPE_NONE); g_assert (record->bus_name != NULL); g_assert (record->object_path != NULL); g_assert (SYSPROF_IS_RECORDING (record->recording)); g_assert (DEX_IS_CANCELLABLE (record->cancellable)); /* Wait for our connection to be available */ if (!(connection = dex_await_object (dex_bus_get (record->bus_type), &error))) return dex_future_new_for_error (g_steal_pointer (&error)); /* If we should try calling stop first to cancel any in-flight * recording. We could pipeline this, but if the other side * handles messages on threads, they would race. */ if (record->call_stop_first) dex_await (dex_dbus_connection_call (connection, record->bus_name, record->object_path, "org.gnome.Sysprof3.Profiler", "Stop", g_variant_new ("()"), G_VARIANT_TYPE ("()"), G_DBUS_CALL_FLAGS_NONE, -1), NULL); /* Create a new FD that the peer will be able to write to and * we will concatenate into the capture after recording. */ if (-1 == (proxy_fd = sysprof_memfd_create ("[sysprof-proxy]"))) return dex_future_new_for_errno (errno); /* Create FDList to pass to the peer so they can get our FD */ fd_list = g_unix_fd_list_new (); if (-1 == (handle = g_unix_fd_list_append (fd_list, proxy_fd, &error))) return dex_future_new_for_error (g_steal_pointer (&error)); /* Call the proxy and give it our FD to start recording */ if (!dex_await (dex_dbus_connection_call_with_unix_fd_list (connection, record->bus_name, record->object_path, "org.gnome.Sysprof3.Profiler", "Start", g_variant_new ("(@a{sv}h)", g_variant_dict_end (&options), handle), G_VARIANT_TYPE ("()"), G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, G_MAXUINT, fd_list), &error)) { g_debug ("Failed to start profiler at %s %s: %s", record->bus_name, record->object_path, error->message); return dex_future_new_for_error (g_steal_pointer (&error)); } /* Await completion of recording */ dex_await (dex_ref (record->cancellable), NULL); /* Call the proxy and let them know to stop */ dex_await (dex_dbus_connection_call (connection, record->bus_name, record->object_path, "org.gnome.Sysprof3.Profiler", "Stop", g_variant_new ("()"), G_VARIANT_TYPE ("()"), G_DBUS_CALL_FLAGS_NONE, -1), &error); if (error != NULL) g_warning ("Failed to stop profiler at %s %s: %s", record->bus_name, record->object_path, error->message); /* Reset the file position to the start */ lseek (proxy_fd, 0, SEEK_SET); /* Now cat the recording into our writer */ writer = _sysprof_recording_writer (record->recording); if ((reader = sysprof_capture_reader_new_from_fd (g_steal_fd (&proxy_fd)))) { sysprof_capture_writer_cat (writer, reader); sysprof_capture_reader_unref (reader); } return dex_future_new_for_boolean (TRUE); } static DexFuture * sysprof_proxied_instrument_record (SysprofInstrument *instrument, SysprofRecording *recording, GCancellable *cancellable) { SysprofProxiedInstrument *self = (SysprofProxiedInstrument *)instrument; Record *record; g_assert (SYSPROF_IS_PROXIED_INSTRUMENT (self)); g_assert (SYSPROF_IS_RECORDING (recording)); g_assert (G_IS_CANCELLABLE (cancellable)); record = g_new0 (Record, 1); record->recording = g_object_ref (recording); record->cancellable = dex_cancellable_new_from_cancellable (cancellable); record->bus_name = g_strdup (self->bus_name); record->object_path = g_strdup (self->object_path); record->bus_type = self->bus_type; record->call_stop_first = self->call_stop_first; return dex_scheduler_spawn (NULL, 0, sysprof_proxied_instrument_record_fiber, record, (GDestroyNotify)record_free); } static void sysprof_proxied_instrument_finalize (GObject *object) { SysprofProxiedInstrument *self = (SysprofProxiedInstrument *)object; g_clear_pointer (&self->bus_name, g_free); g_clear_pointer (&self->object_path, g_free); G_OBJECT_CLASS (sysprof_proxied_instrument_parent_class)->finalize (object); } static void sysprof_proxied_instrument_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SysprofProxiedInstrument *self = SYSPROF_PROXIED_INSTRUMENT (object); switch (prop_id) { case PROP_BUS_TYPE: g_value_set_enum (value, self->bus_type); break; case PROP_BUS_NAME: g_value_set_string (value, self->bus_name); break; case PROP_OBJECT_PATH: g_value_set_string (value, self->object_path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_proxied_instrument_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SysprofProxiedInstrument *self = SYSPROF_PROXIED_INSTRUMENT (object); switch (prop_id) { case PROP_BUS_TYPE: self->bus_type = g_value_get_enum (value); break; case PROP_BUS_NAME: self->bus_name = g_value_dup_string (value); break; case PROP_OBJECT_PATH: self->object_path = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_proxied_instrument_class_init (SysprofProxiedInstrumentClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); object_class->finalize = sysprof_proxied_instrument_finalize; object_class->get_property = sysprof_proxied_instrument_get_property; object_class->set_property = sysprof_proxied_instrument_set_property; instrument_class->record = sysprof_proxied_instrument_record; properties [PROP_BUS_TYPE] = g_param_spec_enum ("bus-type", NULL, NULL, G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); properties [PROP_BUS_NAME] = g_param_spec_string ("bus-name", NULL, NULL, NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); properties [PROP_OBJECT_PATH] = g_param_spec_string ("object-path", NULL, NULL, NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void sysprof_proxied_instrument_init (SysprofProxiedInstrument *self) { } SysprofInstrument * sysprof_proxied_instrument_new (GBusType bus_type, const char *bus_name, const char *object_path) { g_return_val_if_fail (bus_type == G_BUS_TYPE_SYSTEM || bus_type == G_BUS_TYPE_SESSION, NULL); g_return_val_if_fail (bus_name != NULL, NULL); g_return_val_if_fail (object_path != NULL, NULL); return g_object_new (SYSPROF_TYPE_PROXIED_INSTRUMENT, "bus-type", bus_type, "bus-name", bus_name, "object-path", object_path, NULL); }