From e97490be162f753202bd9c848f5085027e649b24 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Tue, 11 Jun 2019 18:47:07 -0700 Subject: [PATCH] libsysprof: abstract subprocess usage For embedding cases, we need a bit more control here so that the application can be in control of how the process is spawned. --- src/libsysprof/meson.build | 2 + src/libsysprof/sysprof-gjs-source.c | 9 +- src/libsysprof/sysprof-local-profiler.c | 34 +-- src/libsysprof/sysprof-source.c | 10 +- src/libsysprof/sysprof-source.h | 14 +- src/libsysprof/sysprof-spawnable.c | 286 ++++++++++++++++++++++++ src/libsysprof/sysprof-spawnable.h | 78 +++++++ src/libsysprof/sysprof-tracefd-source.c | 15 +- src/libsysprof/sysprof.h | 1 + 9 files changed, 400 insertions(+), 49 deletions(-) create mode 100644 src/libsysprof/sysprof-spawnable.c create mode 100644 src/libsysprof/sysprof-spawnable.h diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 2968cc22..806b86ae 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -21,6 +21,7 @@ libsysprof_public_sources = [ 'sysprof-proxy-source.c', 'sysprof-selection.c', 'sysprof-source.c', + 'sysprof-spawnable.c', 'sysprof-symbol-dirs.c', 'sysprof-symbol-resolver.c', 'sysprof-symbols-source.c', @@ -46,6 +47,7 @@ libsysprof_public_headers = [ 'sysprof-proxy-source.h', 'sysprof-selection.h', 'sysprof-source.h', + 'sysprof-spawnable.h', 'sysprof-symbol-dirs.h', 'sysprof-symbol-resolver.h', 'sysprof-symbols-source.h', diff --git a/src/libsysprof/sysprof-gjs-source.c b/src/libsysprof/sysprof-gjs-source.c index ea32409f..168598dc 100644 --- a/src/libsysprof/sysprof-gjs-source.c +++ b/src/libsysprof/sysprof-gjs-source.c @@ -32,12 +32,11 @@ struct _SysprofGjsSource static SysprofSourceInterface *parent_iface; static void -sysprof_gjs_source_modify_spawn (SysprofSource *source, - GSubprocessLauncher *launcher, - GPtrArray *argv) +sysprof_gjs_source_modify_spawn (SysprofSource *source, + SysprofSpawnable *spawnable) { - g_subprocess_launcher_setenv (launcher, "GJS_ENABLE_PROFILER", "1", FALSE); - parent_iface->modify_spawn (source, launcher, argv); + sysprof_spawnable_setenv (spawnable, "GJS_ENABLE_PROFILER", "1"); + parent_iface->modify_spawn (source, spawnable); } static void diff --git a/src/libsysprof/sysprof-local-profiler.c b/src/libsysprof/sysprof-local-profiler.c index 25e95922..aa07c248 100644 --- a/src/libsysprof/sysprof-local-profiler.c +++ b/src/libsysprof/sysprof-local-profiler.c @@ -587,8 +587,7 @@ sysprof_local_profiler_authorize_cb (GObject *object, if (priv->spawn && priv->spawn_argv && priv->spawn_argv[0]) { g_autoptr(GPtrArray) env = g_ptr_array_new_with_free_func (g_free); - g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free); - g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(SysprofSpawnable) spawnable = sysprof_spawnable_new (); g_autoptr(GSubprocess) subprocess = NULL; GPid pid; @@ -612,34 +611,25 @@ sysprof_local_profiler_authorize_cb (GObject *object, g_ptr_array_add (env, NULL); - launcher = g_subprocess_launcher_new (0); - g_subprocess_launcher_set_environ (launcher, (gchar **)env->pdata); - g_subprocess_launcher_set_cwd (launcher, g_get_home_dir ()); - - if (priv->spawn_argv) - { - for (guint i = 0; priv->spawn_argv[i]; i++) - g_ptr_array_add (argv, g_strdup (priv->spawn_argv[i])); - } + sysprof_spawnable_set_environ (spawnable, (const gchar * const *)env->pdata); + sysprof_spawnable_append_args (spawnable, (const gchar * const *)priv->spawn_argv); /* Save argv before modifying */ - g_key_file_set_string_list (keyfile, - "profiler", - "spawn-argv", - (const gchar * const *)argv->pdata, - argv->len); + if (priv->spawn_argv != NULL) + g_key_file_set_string_list (keyfile, + "profiler", + "spawn-argv", + (const gchar * const *)priv->spawn_argv, + g_strv_length (priv->spawn_argv)); for (guint i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); - sysprof_source_modify_spawn (source, launcher, argv); + + sysprof_source_modify_spawn (source, spawnable); } - g_ptr_array_add (argv, NULL); - - if (!(subprocess = g_subprocess_launcher_spawnv (launcher, - (const gchar * const *)argv->pdata, - &error))) + if (!(subprocess = sysprof_spawnable_spawn (spawnable, &error))) { g_ptr_array_add (priv->failures, g_steal_pointer (&error)); } diff --git a/src/libsysprof/sysprof-source.c b/src/libsysprof/sysprof-source.c index da26f0a3..d571c291 100644 --- a/src/libsysprof/sysprof-source.c +++ b/src/libsysprof/sysprof-source.c @@ -141,16 +141,14 @@ sysprof_source_stop (SysprofSource *self) } void -sysprof_source_modify_spawn (SysprofSource *self, - GSubprocessLauncher *launcher, - GPtrArray *argv) +sysprof_source_modify_spawn (SysprofSource *self, + SysprofSpawnable *spawnable) { g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (G_IS_SUBPROCESS_LAUNCHER (launcher)); - g_return_if_fail (argv != NULL); + g_return_if_fail (SYSPROF_IS_SPAWNABLE (spawnable)); if (SYSPROF_SOURCE_GET_IFACE (self)->modify_spawn) - SYSPROF_SOURCE_GET_IFACE (self)->modify_spawn (self, launcher, argv); + SYSPROF_SOURCE_GET_IFACE (self)->modify_spawn (self, spawnable); } void diff --git a/src/libsysprof/sysprof-source.h b/src/libsysprof/sysprof-source.h index 1321830d..62e2f53c 100644 --- a/src/libsysprof/sysprof-source.h +++ b/src/libsysprof/sysprof-source.h @@ -25,8 +25,9 @@ #endif #include +#include -#include "sysprof-capture-writer.h" +#include "sysprof-spawnable.h" G_BEGIN_DECLS @@ -123,15 +124,13 @@ struct _SysprofSourceInterface /** * SysprofSource::modify-spawn: * @self: a #SysprofSource - * @launcher: a #GSubprocessLauncher - * @argv: (element-type utf8): arguments for spawning + * @spawnable: a #SysprofSpawnable * * Allows the source to modify the launcher or argv before the * process is spawned. */ - void (*modify_spawn) (SysprofSource *self, - GSubprocessLauncher *launcher, - GPtrArray *argv); + void (*modify_spawn) (SysprofSource *self, + SysprofSpawnable *spawnable); /** * SysprofSource::supplement: @@ -196,8 +195,7 @@ SYSPROF_AVAILABLE_IN_ALL void sysprof_source_stop (SysprofSource *self); SYSPROF_AVAILABLE_IN_ALL void sysprof_source_modify_spawn (SysprofSource *self, - GSubprocessLauncher *launcher, - GPtrArray *argv); + SysprofSpawnable *spawnable); SYSPROF_AVAILABLE_IN_ALL void sysprof_source_serialize (SysprofSource *self, GKeyFile *keyfile, diff --git a/src/libsysprof/sysprof-spawnable.c b/src/libsysprof/sysprof-spawnable.c new file mode 100644 index 00000000..aca38651 --- /dev/null +++ b/src/libsysprof/sysprof-spawnable.c @@ -0,0 +1,286 @@ +/* sysprof-spawnable.c + * + * Copyright 2019 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 + */ + +#define G_LOG_DOMAIN "sysprof-spawnable" + +#include "config.h" + +#include + +#include "sysprof-spawnable.h" + +typedef struct +{ + gint dest_fd; + gint fd; +} FDMapping; + +struct _SysprofSpawnable +{ + GObject parent_instance; + GArray *fds; + GPtrArray *argv; + gchar **environ; + gint next_fd; +}; + +G_DEFINE_TYPE (SysprofSpawnable, sysprof_spawnable, G_TYPE_OBJECT) + +/** + * sysprof_spawnable_new: + * + * Create a new #SysprofSpawnable. + * + * Returns: (transfer full): a newly created #SysprofSpawnable + */ +SysprofSpawnable * +sysprof_spawnable_new (void) +{ + return g_object_new (SYSPROF_TYPE_SPAWNABLE, NULL); +} + +static void +fd_mapping_clear (gpointer data) +{ + FDMapping *map = data; + + if (map->fd != -1) + close (map->fd); +} + +static void +sysprof_spawnable_finalize (GObject *object) +{ + SysprofSpawnable *self = (SysprofSpawnable *)object; + + g_clear_pointer (&self->fds, g_array_unref); + g_clear_pointer (&self->argv, g_ptr_array_unref); + g_clear_pointer (&self->environ, g_strfreev); + + G_OBJECT_CLASS (sysprof_spawnable_parent_class)->finalize (object); +} + +static void +sysprof_spawnable_class_init (SysprofSpawnableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_spawnable_finalize; +} + +static void +sysprof_spawnable_init (SysprofSpawnable *self) +{ + self->next_fd = 3; + + self->argv = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (self->argv, NULL); + + self->fds = g_array_new (FALSE, FALSE, sizeof (FDMapping)); + g_array_set_clear_func (self->fds, fd_mapping_clear); +} + +void +sysprof_spawnable_prepend_argv (SysprofSpawnable *self, + const gchar *argv) +{ + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + + if (argv != NULL) + g_ptr_array_insert (self->argv, 0, g_strdup (argv)); +} + +void +sysprof_spawnable_append_argv (SysprofSpawnable *self, + const gchar *argv) +{ + gint pos; + + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + + if (argv == NULL) + return; + + pos = self->argv->len - 1; + g_ptr_array_add (self->argv, NULL); + g_ptr_array_index (self->argv, pos) = g_strdup (argv); +} + +void +sysprof_spawnable_append_args (SysprofSpawnable *self, + const gchar * const *args) +{ + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + + if (args == NULL) + return; + + for (guint i = 0; args[i]; i++) + sysprof_spawnable_append_argv (self, args[i]); +} + +const gchar * const * +sysprof_spawnable_get_argv (SysprofSpawnable *self) +{ + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), NULL); + + return (const gchar * const *)(gpointer)self->argv->pdata; +} + +const gchar * const * +sysprof_spawnable_get_environ (SysprofSpawnable *self) +{ + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), NULL); + + return (const gchar * const *)self->environ; +} + +void +sysprof_spawnable_setenv (SysprofSpawnable *self, + const gchar *key, + const gchar *value) +{ + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + g_return_if_fail (key != NULL); + + self->environ = g_environ_setenv (self->environ, key, value, TRUE); +} + +void +sysprof_spawnable_set_environ (SysprofSpawnable *self, + const gchar * const *environ) +{ + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + + if (environ != (const gchar * const *)self->environ) + { + g_strfreev (self->environ); + self->environ = g_strdupv ((gchar **)environ); + } +} + +const gchar * +sysprof_spawnable_getenv (SysprofSpawnable *self, + const gchar *key) +{ + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), NULL); + g_return_val_if_fail (key != NULL, NULL); + + return g_environ_getenv (self->environ, key); +} + +gint +sysprof_spawnable_take_fd (SysprofSpawnable *self, + gint fd, + gint dest_fd) +{ + FDMapping map; + + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), -1); + + if (dest_fd < 0) + dest_fd = self->next_fd++; + + map.dest_fd = dest_fd; + map.fd = fd; + + if (dest_fd >= self->next_fd) + self->next_fd = dest_fd + 1; + + g_array_append_val (self->fds, map); + + return dest_fd; +} + +void +sysprof_spawnable_foreach_fd (SysprofSpawnable *self, + SysprofSpawnableFDForeach foreach, + gpointer user_data) +{ + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + g_return_if_fail (foreach != NULL); + + for (guint i = 0; i < self->fds->len; i++) + { + const FDMapping *map = &g_array_index (self->fds, FDMapping, i); + + foreach (map->dest_fd, map->fd, user_data); + } +} + +/** + * sysprof_spawnable_set_starting_fd: + * + * Sets the next FD number to use when mapping a child FD. This helps + * in situations where the embedder knows that some lower-numbered FDs + * will be taken and therefore unknown to the spawnable. + * + * The default for this is 2. + * + * Since: 3.34 + */ +void +sysprof_spawnable_set_starting_fd (SysprofSpawnable *self, + gint starting_fd) +{ + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + + if (starting_fd < 0) + starting_fd = 2; + + self->next_fd = starting_fd; +} + +/** + * sysprof_spawnable_spawn: + * + * Creates a new subprocess using the configured options. + * + * Returns: (transfer full): a #GSubprocess or %NULL on failure and + * @error is set. + * + * Since: 3.34 + */ +GSubprocess * +sysprof_spawnable_spawn (SysprofSpawnable *self, + GError **error) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + const gchar * const *argv; + + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), NULL); + + launcher = g_subprocess_launcher_new (0); + + g_subprocess_launcher_set_environ (launcher, self->environ); + g_subprocess_launcher_set_cwd (launcher, g_get_home_dir ()); + + for (guint i = 0; i < self->fds->len; i++) + { + FDMapping *map = &g_array_index (self->fds, FDMapping, i); + + g_subprocess_launcher_take_fd (launcher, map->fd, map->dest_fd); + map->fd = -1; + } + + argv = sysprof_spawnable_get_argv (self); + + return g_subprocess_launcher_spawnv (launcher, argv, error); +} diff --git a/src/libsysprof/sysprof-spawnable.h b/src/libsysprof/sysprof-spawnable.h new file mode 100644 index 00000000..de8600e3 --- /dev/null +++ b/src/libsysprof/sysprof-spawnable.h @@ -0,0 +1,78 @@ +/* sysprof-spawnable.h + * + * Copyright 2019 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 + */ + +#pragma once + +#include + +#include "sysprof-version-macros.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SPAWNABLE (sysprof_spawnable_get_type()) + +typedef void (*SysprofSpawnableFDForeach) (gint dest_fd, + gint fd, + gpointer user_data); + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofSpawnable, sysprof_spawnable, SYSPROF, SPAWNABLE, GObject) + +SYSPROF_AVAILABLE_IN_ALL +SysprofSpawnable *sysprof_spawnable_new (void); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_prepend_argv (SysprofSpawnable *self, + const gchar *argv); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_append_argv (SysprofSpawnable *self, + const gchar *argv); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_append_args (SysprofSpawnable *self, + const gchar * const *argv); +SYSPROF_AVAILABLE_IN_ALL +const gchar * const *sysprof_spawnable_get_argv (SysprofSpawnable *self); +SYSPROF_AVAILABLE_IN_ALL +const gchar * const *sysprof_spawnable_get_environ (SysprofSpawnable *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_set_environ (SysprofSpawnable *self, + const gchar * const *environ); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_setenv (SysprofSpawnable *self, + const gchar *key, + const gchar *value); +SYSPROF_AVAILABLE_IN_ALL +const gchar *sysprof_spawnable_getenv (SysprofSpawnable *self, + const gchar *key); +SYSPROF_AVAILABLE_IN_ALL +gint sysprof_spawnable_take_fd (SysprofSpawnable *self, + gint fd, + gint dest_fd); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_foreach_fd (SysprofSpawnable *self, + SysprofSpawnableFDForeach foreach, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_set_starting_fd (SysprofSpawnable *self, + gint starting_fd); +SYSPROF_AVAILABLE_IN_ALL +GSubprocess *sysprof_spawnable_spawn (SysprofSpawnable *self, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-tracefd-source.c b/src/libsysprof/sysprof-tracefd-source.c index fdd49f81..15c8e044 100644 --- a/src/libsysprof/sysprof-tracefd-source.c +++ b/src/libsysprof/sysprof-tracefd-source.c @@ -163,18 +163,17 @@ sysprof_tracefd_source_set_envvar (SysprofTracefdSource *self, } static void -sysprof_tracefd_source_modify_spawn (SysprofSource *source, - GSubprocessLauncher *launcher, - GPtrArray *argv) +sysprof_tracefd_source_modify_spawn (SysprofSource *source, + SysprofSpawnable *spawnable) { SysprofTracefdSource *self = (SysprofTracefdSource *)source; SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); gchar fdstr[12]; + gint dest_fd; gint fd; g_assert (SYSPROF_IS_TRACEFD_SOURCE (self)); - g_assert (G_IS_SUBPROCESS_LAUNCHER (launcher)); - g_assert (argv != NULL); + g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); g_assert (priv->tracefd == -1); if (-1 == (fd = sysprof_memfd_create ("[sysprof-proxy-capture]"))) @@ -192,9 +191,9 @@ sysprof_tracefd_source_modify_spawn (SysprofSource *source, return; } - g_snprintf (fdstr, sizeof fdstr, "%d", fd); - g_subprocess_launcher_setenv (launcher, priv->envvar, fdstr, TRUE); - g_subprocess_launcher_take_fd (launcher, fd, fd); + dest_fd = sysprof_spawnable_take_fd (spawnable, fd, -1); + g_snprintf (fdstr, sizeof fdstr, "%d", dest_fd); + sysprof_spawnable_setenv (spawnable, priv->envvar, fdstr); } static void diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h index 213e03a2..10b38b2e 100644 --- a/src/libsysprof/sysprof.h +++ b/src/libsysprof/sysprof.h @@ -41,6 +41,7 @@ G_BEGIN_DECLS # include "sysprof-proxy-source.h" # include "sysprof-selection.h" # include "sysprof-source.h" +# include "sysprof-spawnable.h" # include "sysprof-symbol-dirs.h" # include "sysprof-symbol-resolver.h" # include "sysprof-symbols-source.h"