[sysprof/wip/chergert/path-resolver] start on new resolver for paths




commit e02785ba238c6fa18e14641e97f68ec357bead2a
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 | 554 +++++++++++++++++++++++++++++++++
 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, 1159 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..7d7d9be
--- /dev/null
+++ b/src/libsysprof/sysprof-path-resolver.c
@@ -0,0 +1,554 @@
+/* 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 (&copy);
+  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
+    {
+      while (*path == '/')
+        path++;
+      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]