mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
I'd like to stop using tracefd, but since we need to maintain compatability with older tooling, lets at least make it more ergonomic than adding an instrument(s) for it.
381 lines
9.2 KiB
C
381 lines
9.2 KiB
C
/* sysprof-spawnable.c
|
|
*
|
|
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include "sysprof-spawnable.h"
|
|
|
|
typedef struct
|
|
{
|
|
gint dest_fd;
|
|
gint fd;
|
|
} FDMapping;
|
|
|
|
struct _SysprofSpawnable
|
|
{
|
|
GObject parent_instance;
|
|
GArray *fds;
|
|
GPtrArray *argv;
|
|
char **environ;
|
|
char *cwd;
|
|
gint next_fd;
|
|
GSubprocessFlags flags;
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* sysprof_spawnable_get_flags:
|
|
* @self: a #SysprofSpawnable
|
|
*
|
|
* Gets the subprocess flags for spawning.
|
|
*
|
|
* Returns: the #GSubprocessFlags bitwise-or'd
|
|
*/
|
|
GSubprocessFlags
|
|
sysprof_spawnable_get_flags (SysprofSpawnable *self)
|
|
{
|
|
g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), 0);
|
|
|
|
return self->flags;
|
|
}
|
|
|
|
/**
|
|
* sysprof_spawnable_set_flags:
|
|
* @self: a #SysprofSpawnable
|
|
*
|
|
* Set the flags to use when spawning the process.
|
|
*/
|
|
void
|
|
sysprof_spawnable_set_flags (SysprofSpawnable *self,
|
|
GSubprocessFlags flags)
|
|
{
|
|
g_return_if_fail (SYSPROF_IS_SPAWNABLE (self));
|
|
|
|
self->flags = flags;
|
|
}
|
|
|
|
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.
|
|
*/
|
|
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.
|
|
*/
|
|
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 (self->flags);
|
|
|
|
g_subprocess_launcher_set_environ (launcher, self->environ);
|
|
|
|
if (self->cwd != NULL)
|
|
g_subprocess_launcher_set_cwd (launcher, self->cwd);
|
|
else
|
|
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);
|
|
}
|
|
|
|
void
|
|
sysprof_spawnable_set_cwd (SysprofSpawnable *self,
|
|
const gchar *cwd)
|
|
{
|
|
g_return_if_fail (SYSPROF_IS_SPAWNABLE (self));
|
|
|
|
if (g_strcmp0 (cwd, self->cwd) != 0)
|
|
{
|
|
g_free (self->cwd);
|
|
self->cwd = g_strdup (cwd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* sysprof_spawnable_add_trace_fd:
|
|
* @self: a #SysprofSpawnable
|
|
* @envvar: (nullable): the environment variable
|
|
*
|
|
* Adds an environment variable to the spawnable that will contain a
|
|
* "tracing file-descriptor". The spawned process can use
|
|
* `sysprof_capture_writer_new_from_env()` if @envvar is %NULL
|
|
* or with `getenv()` and `sysprof_capture_writer_new_from_fd()`.
|
|
*
|
|
* If @envvar is %NULL, "SYSPROF_TRACE_FD" will be used.
|
|
*
|
|
* The caller is responsible for closin the resulting FD.
|
|
*
|
|
* Returns: A file-descriptor which can be used to read the trace or
|
|
* -1 upon failure and `errno` is set. Caller must `close()` the
|
|
* FD if >= 0.
|
|
*/
|
|
int
|
|
sysprof_spawnable_add_trace_fd (SysprofSpawnable *self,
|
|
const char *envvar)
|
|
{
|
|
g_autofd int fd = -1;
|
|
g_autofd int dest = -1;
|
|
g_autofree char *name = NULL;
|
|
g_autofree char *fdstr = NULL;
|
|
|
|
g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), -1);
|
|
|
|
if (envvar == NULL)
|
|
envvar = "SYSPROF_TRACE_FD";
|
|
|
|
name = g_strdup_printf ("[sysprof-tracefd:%s]", envvar);
|
|
|
|
if (-1 == (fd = sysprof_memfd_create (name)))
|
|
return -1;
|
|
|
|
if (-1 == (dest = dup (fd)))
|
|
return -1;
|
|
|
|
fdstr = g_strdup_printf ("%d", dest);
|
|
|
|
sysprof_spawnable_setenv (self, envvar, fdstr);
|
|
sysprof_spawnable_take_fd (self, g_steal_fd (&dest), -1);
|
|
|
|
return g_steal_fd (&fd);
|
|
}
|