[sysprof/wip/chergert/path-resolver] start on new resolver for paths
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [sysprof/wip/chergert/path-resolver] start on new resolver for paths
- Date: Wed, 15 Sep 2021 02:35:51 +0000 (UTC)
commit dad0e22a3aefeeb211268912e101527db75bf365
Author: Christian Hergert <chergert redhat com>
Date: Tue Sep 14 19:35:28 2021 -0700
start on new resolver for paths
src/libsysprof-ui/meson.build | 2 +-
src/libsysprof/meson.build | 29 +-
src/libsysprof/sysprof-mountinfo.c | 9 +
src/libsysprof/sysprof-path-resolver.c | 548 +++++++++++++++++++++++++++++++++
src/libsysprof/sysprof-path-resolver.h | 41 +++
src/libsysprof/sysprof-podman.c | 162 ++++++++++
src/libsysprof/sysprof-podman.h | 10 +-
src/tests/list-all-maps.c | 358 +++++++++++++++++++++
src/tests/meson.build | 6 +
9 files changed, 1153 insertions(+), 12 deletions(-)
---
diff --git a/src/libsysprof-ui/meson.build b/src/libsysprof-ui/meson.build
index 4a84a6c..406b47e 100644
--- a/src/libsysprof-ui/meson.build
+++ b/src/libsysprof-ui/meson.build
@@ -109,7 +109,7 @@ libsysprof_ui = shared_library(
'sysprof-ui-@0@'.format(libsysprof_api_version),
libsysprof_ui_public_sources + libsysprof_ui_private_sources + libsysprof_ui_resources,
- dependencies: libsysprof_ui_deps,
+ dependencies: libsysprof_ui_deps + [librax_dep],
install_dir: get_option('libdir'),
install: true,
c_args: [ '-DSYSPROF_UI_COMPILATION' ],
diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build
index b95c66e..3d9c0b2 100644
--- a/src/libsysprof/meson.build
+++ b/src/libsysprof/meson.build
@@ -79,6 +79,7 @@ libsysprof_private_sources = [
'sysprof-line-reader.c',
'sysprof-map-lookaside.c',
'sysprof-mountinfo.c',
+ 'sysprof-path-resolver.c',
'sysprof-podman.c',
'sysprof-polkit.c',
'sysprof-symbol-map.c',
@@ -90,9 +91,10 @@ libsysprof_private_sources = [
libsysprof_public_sources += libsysprof_capture_sources
librax = static_library('rax', ['rax.c'],
- c_args: [ '-Wno-declaration-after-statement',
- '-Wno-format-nonliteral',
- '-Wno-shadow' ],
+ c_args: [ '-Wno-declaration-after-statement',
+ '-Wno-format-nonliteral',
+ '-Wno-shadow' ],
+ gnu_symbol_visibility: 'hidden',
)
librax_dep = declare_dependency(
@@ -139,10 +141,7 @@ if host_machine.system() == 'darwin'
libsysprof_c_args += [ '-DNT_GNU_BUILD_ID=3', '-DELF_NOTE_GNU="GNU"', '-D__LIBELF_INTERNAL__' ]
endif
-# Meson's pkgconfig module doesn't understand this one
-libsysprof_deps = libsysprof_pkg_deps + [
- librax_dep,
-]
+libsysprof_deps = libsysprof_pkg_deps
libsysprof_libs_private = []
@@ -151,8 +150,8 @@ if host_machine.system() != 'darwin'
libsysprof_libs_private += '-lstdc++'
endif
-libsysprof = shared_library(
- 'sysprof-@0@'.format(libsysprof_api_version),
+libsysprof_static = static_library(
+ 'sysprof',
libsysprof_public_sources + libsysprof_private_sources,
include_directories: [include_directories('.'),
@@ -160,9 +159,19 @@ libsysprof = shared_library(
libsysprof_capture_include_dirs],
dependencies: libsysprof_deps,
c_args: libsysprof_c_args,
+ gnu_symbol_visibility: 'hidden',
+)
+
+libsysprof_static_dep = declare_dependency(
+ link_whole: libsysprof_static,
+ dependencies: libsysprof_deps + [librax_dep],
+ include_directories: [include_directories('.'), libsysprof_capture_include_dirs],
+)
+
+libsysprof = shared_library('sysprof-@0@'.format(libsysprof_api_version),
+ dependencies: libsysprof_deps + [libsysprof_static_dep],
install: true,
install_dir: get_option('libdir'),
- gnu_symbol_visibility: 'hidden',
)
libsysprof_dep = declare_dependency(
diff --git a/src/libsysprof/sysprof-mountinfo.c b/src/libsysprof/sysprof-mountinfo.c
index 9a4a05d..826ce8d 100644
--- a/src/libsysprof/sysprof-mountinfo.c
+++ b/src/libsysprof/sysprof-mountinfo.c
@@ -298,6 +298,9 @@ sysprof_mountinfo_parse_mountinfo_line (SysprofMountinfo *self,
while (*src == '/')
src++;
+ if (*src == 0)
+ return;
+
if (prefix != NULL)
m.host_path = g_build_filename (prefix, src, NULL);
else
@@ -340,4 +343,10 @@ sysprof_mountinfo_parse_mountinfo (SysprofMountinfo *self,
sysprof_mountinfo_parse_mountinfo_line (self, lines[i]);
g_array_sort (self->mountinfos, sort_by_length);
+
+ for (guint i = 0; i < self->mountinfos->len; i++)
+ {
+ const Mountinfo *m = &g_array_index (self->mountinfos, Mountinfo, i);
+ g_print ("MM %s => %s\n", m->host_path, m->mount_path);
+ }
}
diff --git a/src/libsysprof/sysprof-path-resolver.c b/src/libsysprof/sysprof-path-resolver.c
new file mode 100644
index 0000000..1025ea0
--- /dev/null
+++ b/src/libsysprof/sysprof-path-resolver.c
@@ -0,0 +1,548 @@
+/* sysprof-path-resolver.c
+ *
+ * Copyright 2021 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 <string.h>
+
+#include "sysprof-path-resolver.h"
+
+struct _SysprofPathResolver
+{
+ GArray *mounts;
+ GArray *mountpoints;
+};
+
+typedef struct
+{
+ /* The path on the host system */
+ char *on_host;
+
+ /* The path inside the process domain */
+ char *in_process;
+
+ /* The length of @in_process in bytes */
+ guint in_process_len;
+
+ /* The depth of the mount (for FUSE overlays) */
+ int depth;
+} Mountpoint;
+
+typedef struct
+{
+ char *device;
+ char *mountpoint;
+ char *filesystem;
+ char *subvolid;
+ char *subvol;
+} Mount;
+
+typedef struct _st_mountinfo
+{
+ char *id;
+ char *parent_id;
+ char *st_dev;
+ char *root;
+ char *mount_point;
+ char *mount_options;
+ char *filesystem;
+ char *mount_source;
+ char *super_options;
+} st_mountinfo;
+
+static void
+clear_st_mountinfo (st_mountinfo *st)
+{
+ g_free (st->id);
+ g_free (st->parent_id);
+ g_free (st->st_dev);
+ g_free (st->root);
+ g_free (st->mount_point);
+ g_free (st->mount_options);
+ g_free (st->filesystem);
+ g_free (st->mount_source);
+ g_free (st->super_options);
+}
+
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (st_mountinfo, clear_st_mountinfo)
+
+static void
+clear_mount (Mount *m)
+{
+ g_clear_pointer (&m->device, g_free);
+ g_clear_pointer (&m->mountpoint, g_free);
+ g_clear_pointer (&m->subvolid, g_free);
+ g_clear_pointer (&m->subvol, g_free);
+ g_clear_pointer (&m->filesystem, g_free);
+}
+
+static void
+clear_mountpoint (Mountpoint *mp)
+{
+ g_clear_pointer (&mp->on_host, g_free);
+ g_clear_pointer (&mp->in_process, g_free);
+}
+
+static gboolean
+ignore_fs (const char *fs)
+{
+ static gsize initialized;
+ static GHashTable *ignored;
+
+ if (g_once_init_enter (&initialized))
+ {
+ ignored = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_add (ignored, (char *)"autofs");
+ g_hash_table_add (ignored, (char *)"binfmt_misc");
+ g_hash_table_add (ignored, (char *)"bpf");
+ g_hash_table_add (ignored, (char *)"cgroup");
+ g_hash_table_add (ignored, (char *)"cgroup2");
+ g_hash_table_add (ignored, (char *)"configfs");
+ g_hash_table_add (ignored, (char *)"debugfs");
+ g_hash_table_add (ignored, (char *)"devpts");
+ g_hash_table_add (ignored, (char *)"devtmpfs");
+ g_hash_table_add (ignored, (char *)"efivarfs");
+ g_hash_table_add (ignored, (char *)"fusectl");
+ g_hash_table_add (ignored, (char *)"hugetlbfs");
+ g_hash_table_add (ignored, (char *)"mqueue");
+ g_hash_table_add (ignored, (char *)"none");
+ g_hash_table_add (ignored, (char *)"portal");
+ g_hash_table_add (ignored, (char *)"proc");
+ g_hash_table_add (ignored, (char *)"pstore");
+ g_hash_table_add (ignored, (char *)"ramfs");
+ g_hash_table_add (ignored, (char *)"rpc_pipefs");
+ g_hash_table_add (ignored, (char *)"securityfs");
+ g_hash_table_add (ignored, (char *)"selinuxfs");
+ g_hash_table_add (ignored, (char *)"sunrpc");
+ g_hash_table_add (ignored, (char *)"sysfs");
+ g_hash_table_add (ignored, (char *)"systemd-1");
+ g_hash_table_add (ignored, (char *)"tmpfs");
+ g_hash_table_add (ignored, (char *)"tracefs");
+ g_once_init_leave (&initialized, (gsize)1);
+ }
+
+ if (g_str_has_prefix (fs, "fuse."))
+ return TRUE;
+
+ return g_hash_table_contains (ignored, fs);
+}
+
+static char *
+path_copy_with_trailing_slash (const char *path)
+{
+ if (g_str_has_suffix (path, "/"))
+ return g_strdup (path);
+ else
+ return g_strdup_printf ("%s/", path);
+}
+
+static void
+decode_space (char **str)
+{
+ /* Replace encoded space "\040" with ' ' */
+ if (strstr (*str, "\\040") != NULL)
+ {
+ g_auto(GStrv) parts = g_strsplit (*str, "\\040", 0);
+ g_free (*str);
+ *str = g_strjoinv (" ", parts);
+ }
+}
+
+static char *
+strdup_decode_space (const char *str)
+{
+ char *copy = g_strdup (str);
+ decode_space (©);
+ return copy;
+}
+
+static gboolean
+has_prefix_or_equal (const char *str,
+ const char *prefix)
+{
+ return g_str_has_prefix (str, prefix) || strcmp (str, prefix) == 0;
+}
+
+static char *
+get_option (const char *options,
+ const char *option)
+{
+ g_auto(GStrv) parts = NULL;
+
+ g_assert (option != NULL);
+ g_assert (g_str_has_suffix (option, "="));
+
+ if (options == NULL)
+ return NULL;
+
+ parts = g_strsplit (options, ",", 0);
+
+ for (guint i = 0; parts[i] != NULL; i++)
+ {
+ if (g_str_has_prefix (parts[i], option))
+ {
+ const char *ret = parts[i] + strlen (option);
+
+ /* Easier to handle "" as NULL */
+ if (*ret == 0)
+ return NULL;
+
+ return g_strdup (ret);
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+parse_st_mountinfo (st_mountinfo *mi,
+ const char *line)
+{
+ g_auto(GStrv) parts = NULL;
+ guint i;
+
+ g_assert (mi != NULL);
+ g_assert (line != NULL);
+
+ memset (mi, 0, sizeof *mi);
+
+ parts = g_strsplit (line, " ", 0);
+ if (g_strv_length (parts) < 10)
+ return FALSE;
+
+ mi->id = g_strdup (parts[0]);
+ mi->parent_id = g_strdup (parts[1]);
+ mi->st_dev = g_strdup (parts[2]);
+ mi->root = strdup_decode_space (parts[3]);
+ mi->mount_point = strdup_decode_space (parts[4]);
+ mi->mount_options = strdup_decode_space (parts[5]);
+
+ for (i = 6; parts[i] != NULL && !g_str_equal (parts[i], "-"); i++)
+ {
+ /* Do nothing. We just want to skip until after the optional tags
+ * section which is finished with " - ".
+ */
+ }
+
+ /* Skip past - if there is anything */
+ if (parts[i] == NULL || parts[++i] == NULL)
+ return TRUE;
+
+ /* Get filesystem if provided */
+ mi->filesystem = g_strdup (parts[i++]);
+ if (parts[i] == NULL)
+ return TRUE;
+
+ /* Get mount source if provided */
+ mi->mount_source = strdup_decode_space (parts[i++]);
+ if (parts[i] == NULL)
+ return TRUE;
+
+ /* Get super options if provided */
+ mi->super_options = strdup_decode_space (parts[i++]);
+ if (parts[i] == NULL)
+ return TRUE;
+
+ /* Perhaps mountinfo will be extended once again ... */
+
+ return TRUE;
+}
+
+static void
+parse_mounts (SysprofPathResolver *self,
+ const char *mounts)
+{
+ g_auto(GStrv) lines = NULL;
+
+ g_assert (self != NULL);
+ g_assert (self->mounts != NULL);
+ g_assert (mounts != NULL);
+
+ lines = g_strsplit (mounts, "\n", 0);
+
+ for (guint i = 0; lines[i]; i++)
+ {
+ g_auto(GStrv) parts = g_strsplit (lines[i], " ", 5);
+ g_autofree char *subvolid = NULL;
+ g_autofree char *subvol = NULL;
+ const char *filesystem;
+ const char *mountpoint;
+ const char *device;
+ const char *options;
+ Mount m;
+
+ /* Field 0: device
+ * Field 1: mountpoint
+ * Field 2: filesystem
+ * Field 3: Options
+ * .. Ignored ..
+ */
+ if (g_strv_length (parts) != 5)
+ continue;
+
+ filesystem = parts[2];
+ if (ignore_fs (filesystem))
+ continue;
+
+ for (guint j = 0; parts[j]; j++)
+ decode_space (&parts[j]);
+
+ device = parts[0];
+ mountpoint = parts[1];
+ options = parts[3];
+
+
+ if (g_strcmp0 (filesystem, "btrfs") == 0)
+ {
+ subvolid = get_option (options, "subvolid=");
+ subvol = get_option (options, "subvol=");
+ }
+
+ m.device = g_strdup (device);
+ m.filesystem = g_strdup (filesystem);
+ m.mountpoint = path_copy_with_trailing_slash (mountpoint);
+ m.subvolid = g_steal_pointer (&subvolid);
+ m.subvol = g_steal_pointer (&subvol);
+
+ g_array_append_val (self->mounts, m);
+ }
+}
+
+static const Mount *
+find_mount (SysprofPathResolver *self,
+ const st_mountinfo *mi)
+{
+ g_autofree char *subvolid = NULL;
+
+ g_assert (self != NULL);
+ g_assert (mi != NULL);
+
+ subvolid = get_option (mi->super_options, "subvolid=");
+
+ for (guint i = 0; i < self->mounts->len; i++)
+ {
+ const Mount *mount = &g_array_index (self->mounts, Mount, i);
+
+ if (g_strcmp0 (mount->device, mi->mount_source) == 0)
+ {
+ /* Sanity check that filesystems match */
+ if (g_strcmp0 (mount->filesystem, mi->filesystem) != 0)
+ continue;
+
+ /* If we have a subvolume (btrfs) make sure they match */
+ if (subvolid == NULL ||
+ g_strcmp0 (subvolid, mount->subvolid) == 0)
+ return mount;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+parse_mountinfo_line (SysprofPathResolver *self,
+ const char *line)
+{
+ g_auto(st_mountinfo) st_mi = {0};
+ g_autofree char *subvol = NULL;
+ const char *path;
+ const Mount *mount;
+ Mountpoint mp = {0};
+
+ g_assert (self != NULL);
+ g_assert (self->mounts != NULL);
+ g_assert (self->mountpoints != NULL);
+
+ if (!parse_st_mountinfo (&st_mi, line))
+ return;
+
+ if (ignore_fs (st_mi.filesystem))
+ return;
+
+ if (!(mount = find_mount (self, &st_mi)))
+ return;
+
+ subvol = get_option (st_mi.super_options, "subvol=");
+ path = st_mi.root;
+
+ /* If the mount root has a prefix of the subvolume, then
+ * subtract that from the path (as we will resolve relative
+ * to the location where it is mounted via the Mount.mountpoint.
+ */
+ if (subvol != NULL && has_prefix_or_equal (path, subvol))
+ {
+ path += strlen (subvol);
+ if (*path == 0)
+ path = NULL;
+ }
+
+ if (path == NULL)
+ mp.on_host = g_strdup (mount->mountpoint);
+ else
+ mp.on_host = g_build_filename (mount->mountpoint, path, NULL);
+
+ if (g_str_has_suffix (mp.on_host, "/") &&
+ !g_str_has_suffix (st_mi.mount_point, "/"))
+ mp.in_process = g_build_filename (st_mi.mount_point, "/", NULL);
+ else
+ mp.in_process = g_strdup (st_mi.mount_point);
+
+ mp.in_process_len = strlen (mp.in_process);
+ mp.depth = -1;
+
+ g_array_append_val (self->mountpoints, mp);
+}
+
+static gint
+sort_by_length (gconstpointer a,
+ gconstpointer b)
+{
+ const Mountpoint *mpa = a;
+ const Mountpoint *mpb = b;
+ gsize alen = strlen (mpa->in_process);
+ gsize blen = strlen (mpb->in_process);
+
+ if (alen > blen)
+ return -1;
+ else if (blen > alen)
+ return 1;
+
+ if (mpa->depth < mpb->depth)
+ return -1;
+ else if (mpa->depth > mpb->depth)
+ return 1;
+
+ return 0;
+}
+
+static void
+parse_mountinfo (SysprofPathResolver *self,
+ const char *mountinfo)
+{
+ g_auto(GStrv) lines = NULL;
+
+ g_assert (self != NULL);
+ g_assert (self->mounts != NULL);
+ g_assert (self->mountpoints != NULL);
+ g_assert (mountinfo != NULL);
+
+ lines = g_strsplit (mountinfo, "\n", 0);
+
+ for (guint i = 0; lines[i]; i++)
+ parse_mountinfo_line (self, lines[i]);
+
+ g_array_sort (self->mountpoints, sort_by_length);
+}
+
+SysprofPathResolver *
+_sysprof_path_resolver_new (const char *mounts,
+ const char *mountinfo)
+{
+ SysprofPathResolver *self;
+
+ self = g_slice_new0 (SysprofPathResolver);
+
+ self->mounts = g_array_new (FALSE, FALSE, sizeof (Mount));
+ self->mountpoints = g_array_new (FALSE, FALSE, sizeof (Mountpoint));
+
+ g_array_set_clear_func (self->mounts, (GDestroyNotify)clear_mount);
+ g_array_set_clear_func (self->mountpoints, (GDestroyNotify)clear_mountpoint);
+
+ if (mounts != NULL)
+ parse_mounts (self, mounts);
+
+ if (mountinfo != NULL)
+ parse_mountinfo (self, mountinfo);
+
+ return self;
+}
+
+void
+_sysprof_path_resolver_free (SysprofPathResolver *self)
+{
+ g_clear_pointer (&self->mountpoints, g_array_unref);
+ g_clear_pointer (&self->mounts, g_array_unref);
+ g_slice_free (SysprofPathResolver, self);
+}
+
+void
+_sysprof_path_resolver_add_overlay (SysprofPathResolver *self,
+ const char *in_process,
+ const char *on_host,
+ int depth)
+{
+ Mountpoint mp;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (in_process != NULL);
+ g_return_if_fail (on_host != NULL);
+
+ mp.in_process = path_copy_with_trailing_slash (in_process);
+ mp.in_process_len = strlen (mp.in_process);
+ mp.on_host = path_copy_with_trailing_slash (on_host);
+ mp.depth = depth;
+
+ g_array_append_val (self->mountpoints, mp);
+ g_array_sort (self->mountpoints, sort_by_length);
+}
+
+char *
+_sysprof_path_resolver_resolve (SysprofPathResolver *self,
+ const char *path)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ /* TODO: Cache the directory name of @path and use that for followup
+ * searches like we did in SysprofMountinfo.
+ */
+
+ for (guint i = 0; i < self->mountpoints->len; i++)
+ {
+ const Mountpoint *mp = &g_array_index (self->mountpoints, Mountpoint, i);
+
+ if (g_str_has_prefix (path, mp->in_process))
+ {
+ g_autofree char *dst = g_build_filename (mp->on_host,
+ path + mp->in_process_len,
+ NULL);
+
+ /* If the depth is > -1, then we are dealing with an overlay. We
+ * unfortunately have to stat() to see if the file exists and then
+ * skip over this if not.
+ *
+ * TODO: This is going to break when we are recording from within
+ * flatpak as we'll not be able to stat files unless they are
+ * within the current users home, so system containers would
+ * be unlilkely to resolve.
+ */
+ if (mp->depth > -1)
+ {
+ if (g_file_test (dst, G_FILE_TEST_EXISTS))
+ return g_steal_pointer (&dst);
+ continue;
+ }
+
+ return g_steal_pointer (&dst);
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/libsysprof/sysprof-path-resolver.h b/src/libsysprof/sysprof-path-resolver.h
new file mode 100644
index 0000000..5267cf9
--- /dev/null
+++ b/src/libsysprof/sysprof-path-resolver.h
@@ -0,0 +1,41 @@
+/* sysprof-path-resolver.h
+ *
+ * Copyright 2021 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
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SysprofPathResolver SysprofPathResolver;
+
+SysprofPathResolver *_sysprof_path_resolver_new (const char *mounts,
+ const char *mountinfo);
+void _sysprof_path_resolver_add_overlay (SysprofPathResolver *self,
+ const char *in_process,
+ const char *on_host,
+ int depth);
+void _sysprof_path_resolver_free (SysprofPathResolver *self);
+char *_sysprof_path_resolver_resolve (SysprofPathResolver *self,
+ const char *path);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofPathResolver, _sysprof_path_resolver_free)
+
+G_END_DECLS
diff --git a/src/libsysprof/sysprof-podman.c b/src/libsysprof/sysprof-podman.c
index cc690f3..d1498d7 100644
--- a/src/libsysprof/sysprof-podman.c
+++ b/src/libsysprof/sysprof-podman.c
@@ -22,6 +22,8 @@
#include "config.h"
+#include <json-glib/json-glib.h>
+
#include "sysprof-podman.h"
static const char *debug_dirs[] = {
@@ -67,3 +69,163 @@ sysprof_podman_debug_dirs (void)
g_ptr_array_add (dirs, NULL);
return (gchar **)g_ptr_array_free (dirs, FALSE);
}
+
+struct _SysprofPodman
+{
+ JsonParser *containers_parser;
+ JsonParser *layers_parser;
+};
+
+void
+sysprof_podman_free (SysprofPodman *self)
+{
+ g_clear_object (&self->containers_parser);
+ g_clear_object (&self->layers_parser);
+ g_slice_free (SysprofPodman, self);
+}
+
+static void
+load_containers (SysprofPodman *self)
+{
+ g_autofree char *path = NULL;
+
+ g_assert (self != NULL);
+
+ path = g_build_filename (g_get_user_data_dir (),
+ "containers",
+ "storage",
+ "overlay-containers",
+ "containers.json",
+ NULL);
+ json_parser_load_from_file (self->containers_parser, path, NULL);
+}
+
+static void
+load_layers (SysprofPodman *self)
+{
+ g_autofree char *path = NULL;
+
+ g_assert (self != NULL);
+
+ path = g_build_filename (g_get_user_data_dir (),
+ "containers",
+ "storage",
+ "overlay-layers",
+ "layers.json",
+ NULL);
+ json_parser_load_from_file (self->layers_parser, path, NULL);
+}
+
+SysprofPodman *
+sysprof_podman_snapshot_current_user (void)
+{
+ SysprofPodman *self;
+
+ self = g_slice_new0 (SysprofPodman);
+ self->containers_parser = json_parser_new ();
+ self->layers_parser = json_parser_new ();
+
+ load_containers (self);
+ load_layers (self);
+
+ return self;
+}
+
+static const char *
+find_parent_layer (JsonParser *parser,
+ const char *layer,
+ GPtrArray *seen)
+{
+ JsonNode *root;
+ JsonArray *ar;
+ guint n_items;
+
+ g_assert (JSON_IS_PARSER (parser));
+ g_assert (layer != NULL);
+ g_assert (seen != NULL);
+
+ if (!(root = json_parser_get_root (parser)) ||
+ !JSON_NODE_HOLDS_ARRAY (root) ||
+ !(ar = json_node_get_array (root)))
+ return NULL;
+
+ n_items = json_array_get_length (ar);
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ JsonObject *item = json_array_get_object_element (ar, i);
+ const char *parent;
+ const char *id;
+
+ if (item == NULL ||
+ !json_object_has_member (item, "id") ||
+ !json_object_has_member (item, "parent") ||
+ !(id = json_object_get_string_member (item, "id")) ||
+ strcmp (id, layer) != 0 ||
+ !(parent = json_object_get_string_member (item, "parent")))
+ continue;
+
+ /* Avoid cycles by checking if we've seen this parent */
+ for (guint j = 0; j < seen->len; j++)
+ {
+ if (strcmp (parent, g_ptr_array_index (seen, j)) == 0)
+ return NULL;
+ }
+
+ return parent;
+ }
+
+ return NULL;
+}
+
+gchar **
+sysprof_podman_get_layers (SysprofPodman *self,
+ const char *container)
+{
+ const char *layer = NULL;
+ GPtrArray *layers;
+ JsonNode *root;
+ JsonArray *ar;
+ guint n_items;
+
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (container != NULL, NULL);
+
+ if (!(root = json_parser_get_root (self->containers_parser)) ||
+ !JSON_NODE_HOLDS_ARRAY (root) ||
+ !(ar = json_node_get_array (root)))
+ return NULL;
+
+ n_items = json_array_get_length (ar);
+
+ /* First try to locate the "layer" identifier for the container
+ * in question.
+ */
+ for (guint i = 0; i < n_items; i++)
+ {
+ JsonObject *item = json_array_get_object_element (ar, i);
+ const char *item_layer;
+ const char *id;
+
+ if (item == NULL ||
+ !(id = json_object_get_string_member (item, "id")) ||
+ strcmp (id, container) != 0 ||
+ !(item_layer = json_object_get_string_member (item, "layer")))
+ continue;
+
+ layer = item_layer;
+ break;
+ }
+
+ /* Now we need to try to locate the layer and all of the parents
+ * within the layers.json so that we populate from the most recent
+ * layer to those beneath it.
+ */
+ layers = g_ptr_array_new ();
+ g_ptr_array_add (layers, g_strdup (layer));
+ while ((layer = find_parent_layer (self->layers_parser, layer, layers)))
+ g_ptr_array_add (layers, g_strdup (layer));
+ g_ptr_array_add (layers, NULL);
+
+ return (char **)g_ptr_array_free (layers, FALSE);
+}
diff --git a/src/libsysprof/sysprof-podman.h b/src/libsysprof/sysprof-podman.h
index b113f6f..053c625 100644
--- a/src/libsysprof/sysprof-podman.h
+++ b/src/libsysprof/sysprof-podman.h
@@ -24,6 +24,14 @@
G_BEGIN_DECLS
-gchar **sysprof_podman_debug_dirs (void);
+typedef struct _SysprofPodman SysprofPodman;
+
+gchar **sysprof_podman_debug_dirs (void);
+SysprofPodman *sysprof_podman_snapshot_current_user (void);
+gchar **sysprof_podman_get_layers (SysprofPodman *self,
+ const char *container);
+void sysprof_podman_free (SysprofPodman *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofPodman, sysprof_podman_free)
G_END_DECLS
diff --git a/src/tests/list-all-maps.c b/src/tests/list-all-maps.c
new file mode 100644
index 0000000..e53ea6b
--- /dev/null
+++ b/src/tests/list-all-maps.c
@@ -0,0 +1,358 @@
+/* list-all-maps.c
+ *
+ * Copyright 2021 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 <sysprof.h>
+#include <stdio.h>
+
+#include "../libsysprof/sysprof-helpers.h"
+#include "../libsysprof/sysprof-mountinfo.h"
+#include "../libsysprof/sysprof-path-resolver.h"
+#include "../libsysprof/sysprof-podman.h"
+
+typedef enum {
+ PROCESS_KIND_HOST,
+ PROCESS_KIND_FLATPAK,
+ PROCESS_KIND_PODMAN,
+} ProcessKind;
+
+typedef struct
+{
+ ProcessKind kind : 2;
+ int pid : 29;
+ char *kind_identifier;
+ char *mountinfo;
+ char *comm;
+ GArray *maps;
+ GArray *overlays;
+} ProcessInfo;
+
+typedef struct
+{
+ char *on_host;
+ char *in_process;
+} ProcessOverlay;
+
+typedef struct
+{
+ char *file;
+ SysprofCaptureAddress start;
+ SysprofCaptureAddress end;
+ SysprofCaptureAddress offset;
+ ino_t inode;
+} ProcessMap;
+
+static const char *
+kind_to_string (ProcessKind kind)
+{
+ switch (kind)
+ {
+ case PROCESS_KIND_HOST:
+ return "Host";
+ case PROCESS_KIND_PODMAN:
+ return "Podman";
+ case PROCESS_KIND_FLATPAK:
+ return "Flatpak";
+ default:
+ return "Unknown";
+ }
+}
+
+static void
+process_overlay_clear (ProcessOverlay *overlay)
+{
+ g_free (overlay->on_host);
+ g_free (overlay->in_process);
+}
+
+static void
+process_info_clear (ProcessInfo *info)
+{
+ g_free (info->kind_identifier);
+ g_free (info->mountinfo);
+ g_free (info->comm);
+ g_clear_pointer (&info->maps, g_array_unref);
+ g_clear_pointer (&info->overlays, g_array_unref);
+}
+
+static void
+process_map_clear (ProcessMap *map)
+{
+ g_free (map->file);
+}
+
+static ProcessKind
+get_process_kind_from_cgroup (int pid,
+ const char *cgroup,
+ char **identifier)
+{
+ static GRegex *podman_regex;
+ static GRegex *flatpak_regex;
+
+ g_autoptr(GMatchInfo) podman_match = NULL;
+ g_autoptr(GMatchInfo) flatpak_match = NULL;
+
+ if G_UNLIKELY (podman_regex == NULL)
+ podman_regex = g_regex_new ("libpod-([a-z0-9]{64})\\.scope", G_REGEX_OPTIMIZE, 0, NULL);
+
+ if G_UNLIKELY (flatpak_regex == NULL)
+ flatpak_regex = g_regex_new ("app-flatpak-([a-zA-Z_\\-\\.]+)-[0-9]+\\.scope", G_REGEX_OPTIMIZE, 0, NULL);
+
+ if (g_regex_match (podman_regex, cgroup, 0, &podman_match))
+ {
+ if (identifier != NULL)
+ *identifier = g_match_info_fetch (podman_match, 1);
+ return PROCESS_KIND_PODMAN;
+ }
+ else if (g_regex_match (flatpak_regex, cgroup, 0, &flatpak_match))
+ {
+ if (identifier != NULL)
+ *identifier = g_match_info_fetch (flatpak_match, 1);
+ return PROCESS_KIND_FLATPAK;
+ }
+ else
+ {
+ if (identifier != NULL)
+ *identifier = NULL;
+ return PROCESS_KIND_HOST;
+ }
+}
+
+static void
+process_info_populate_podman_overlays (ProcessInfo *proc,
+ SysprofPodman *podman)
+{
+ g_auto(GStrv) layers = NULL;
+
+ g_assert (proc != NULL);
+ g_assert (proc->kind == PROCESS_KIND_PODMAN);
+
+ if ((layers = sysprof_podman_get_layers (podman, proc->kind_identifier)))
+ {
+ for (guint i = 0; layers[i]; i++)
+ {
+ ProcessOverlay overlay;
+ g_autofree char *path = g_build_filename (g_get_user_data_dir (),
+ "containers",
+ "storage",
+ "overlay",
+ layers[i],
+ "diff",
+ NULL);
+
+ /* XXX: this really only supports one layer */
+ overlay.in_process = g_strdup ("/");
+ overlay.on_host = g_steal_pointer (&path);
+
+ if (proc->overlays == NULL)
+ {
+ proc->overlays = g_array_new (FALSE, FALSE, sizeof (ProcessOverlay));
+ g_array_set_clear_func (proc->overlays, (GDestroyNotify)process_overlay_clear);
+ }
+
+ g_array_append_val (proc->overlays, overlay);
+ }
+ }
+}
+
+static void
+process_info_populate_maps (ProcessInfo *proc,
+ const char *maps)
+{
+ g_auto(GStrv) lines = NULL;
+
+ g_assert (proc != NULL);
+ g_assert (maps != NULL);
+
+ if (proc->maps == NULL)
+ {
+ proc->maps = g_array_new (FALSE, FALSE, sizeof (ProcessMap));
+ g_array_set_clear_func (proc->maps, (GDestroyNotify)process_map_clear);
+ }
+
+ lines = g_strsplit (maps, "\n", 0);
+
+ for (guint i = 0; lines[i] != NULL; i++)
+ {
+ ProcessMap map;
+ gchar file[512];
+ gulong start;
+ gulong end;
+ gulong offset;
+ gulong inode;
+ int r;
+
+ r = sscanf (lines[i],
+ "%lx-%lx %*15s %lx %*x:%*x %lu %512s",
+ &start, &end, &offset, &inode, file);
+ file[sizeof file - 1] = '\0';
+
+ if (r != 5)
+ continue;
+
+ if (strcmp ("[vdso]", file) == 0)
+ {
+ /*
+ * Søren Sandmann Pedersen says:
+ *
+ * For the vdso, the kernel reports 'offset' as the
+ * the same as the mapping address. This doesn't make
+ * any sense to me, so we just zero it here. There
+ * is code in binfile.c (read_inode) that returns 0
+ * for [vdso].
+ */
+ offset = 0;
+ inode = 0;
+ }
+
+ map.file = g_strdup (file);
+ map.start = start;
+ map.end = end;
+ map.offset = offset;
+ map.inode = inode;
+
+ g_array_append_val (proc->maps, map);
+ }
+}
+
+static gboolean
+process_info_populate (ProcessInfo *proc,
+ GVariant *variant,
+ SysprofPodman *podman)
+{
+ const char *str;
+ int pid;
+
+ g_assert (proc != NULL);
+ g_assert (proc->pid == 0);
+ g_assert (variant != NULL);
+ g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT));
+
+ if (g_variant_lookup (variant, "pid", "i", &pid))
+ proc->pid = pid;
+ else
+ return FALSE;
+
+ if (g_variant_lookup (variant, "comm", "&s", &str))
+ proc->comm = g_strdup (str);
+
+ if (g_variant_lookup (variant, "cgroup", "&s", &str))
+ proc->kind = get_process_kind_from_cgroup (pid, str, &proc->kind_identifier);
+ else
+ proc->kind = PROCESS_KIND_HOST;
+
+ if (proc->kind == PROCESS_KIND_PODMAN)
+ process_info_populate_podman_overlays (proc, podman);
+
+ if (g_variant_lookup (variant, "mountinfo", "&s", &str))
+ proc->mountinfo = g_strdup (str);
+
+ if (g_variant_lookup (variant, "maps", "&s", &str))
+ process_info_populate_maps (proc, str);
+
+ return TRUE;
+}
+
+static gboolean
+list_all_maps (GError **error)
+{
+ SysprofHelpers *helpers = sysprof_helpers_get_default ();
+ g_autoptr(SysprofPodman) podman = NULL;
+ g_autofree char *mounts = NULL;
+ g_autoptr(GVariant) info = NULL;
+ g_autoptr(GArray) processes = NULL;
+ GVariant *value;
+ GVariantIter iter;
+
+ if (!sysprof_helpers_get_process_info (helpers, "pid,maps,mountinfo,cmdline,comm,cgroup", FALSE, NULL,
&info, error))
+ return FALSE;
+
+ if (!sysprof_helpers_get_proc_file (helpers, "/proc/mounts", NULL, &mounts, error))
+ return FALSE;
+
+ podman = sysprof_podman_snapshot_current_user ();
+
+ processes = g_array_new (FALSE, TRUE, sizeof (ProcessInfo));
+ g_array_set_clear_func (processes, (GDestroyNotify)process_info_clear);
+
+ g_variant_iter_init (&iter, info);
+ while (g_variant_iter_loop (&iter, "@a{sv}", &value))
+ {
+ ProcessInfo *proc;
+
+ g_array_set_size (processes, processes->len + 1);
+ proc = &g_array_index (processes, ProcessInfo, processes->len - 1);
+
+ if (!process_info_populate (proc, value, podman))
+ g_array_set_size (processes, processes->len - 1);
+ }
+
+ for (guint i = 0; i < processes->len; i++)
+ {
+ const ProcessInfo *proc = &g_array_index (processes, ProcessInfo, i);
+ g_autoptr(SysprofPathResolver) resolver = _sysprof_path_resolver_new (mounts, proc->mountinfo);
+
+ if (proc->maps == NULL || proc->maps->len == 0)
+ continue;
+
+ g_print ("Process %d", proc->pid);
+ if (proc->kind != PROCESS_KIND_HOST)
+ g_print (" (%s)", kind_to_string (proc->kind));
+ g_print (" %s\n", proc->comm);
+
+ if (proc->overlays != NULL)
+ {
+ for (guint j = 0; j < proc->overlays->len; j++)
+ {
+ const ProcessOverlay *overlay = &g_array_index (proc->overlays, ProcessOverlay, j);
+ _sysprof_path_resolver_add_overlay (resolver, overlay->in_process, overlay->on_host, j);
+ }
+ }
+
+ for (guint j = 0; j < proc->maps->len; j++)
+ {
+ const ProcessMap *map = &g_array_index (proc->maps, ProcessMap, j);
+ g_autofree char *path = _sysprof_path_resolver_resolve (resolver, map->file);
+
+ if (path != NULL)
+ {
+ g_print (" %s", path);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ g_print (" (missing)");
+ g_print ("\n");
+ }
+ }
+
+ g_print ("\n");
+ }
+
+ return TRUE;
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_autoptr(GError) error = NULL;
+ if (!list_all_maps (&error))
+ g_printerr ("%s\n", error->message);
+ return error ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 6519fb7..91c3a84 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -113,6 +113,12 @@ show_page_usage = executable('show-page-usage',
dependency('cairo') ],
)
+list_pid_maps = executable('list-all-maps', 'list-all-maps.c',
+ c_args: test_cflags,
+ dependencies: [libsysprof_static_dep],
+ include_directories: include_directories('..'),
+)
+
if get_option('enable_gtk')
test_ui_deps = [
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]