From 7e7657dd0353cd4d8e57dd498e7373e3b50024e4 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 2 Aug 2019 12:56:14 -0700 Subject: [PATCH] libsysprof: add mountinfo helper The goal of this helper is to simplify the process of parsing information about mounts and the mountinfo for per-process maps. We should be able to change sysprof-proc-source to use this and have better support for getting the libraries within different mount namespaces. --- src/libsysprof/meson.build | 1 + src/libsysprof/sysprof-mountinfo.c | 283 +++++++++++++++++++++++++++++ src/libsysprof/sysprof-mountinfo.h | 41 +++++ src/tests/meson.build | 7 + src/tests/test-mountinfo.c | 59 ++++++ 5 files changed, 391 insertions(+) create mode 100644 src/libsysprof/sysprof-mountinfo.c create mode 100644 src/libsysprof/sysprof-mountinfo.h create mode 100644 src/tests/test-mountinfo.c diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 14f99057..2f21dd3b 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -69,6 +69,7 @@ libsysprof_private_sources = [ 'sysprof-kallsyms.c', 'sysprof-line-reader.c', 'sysprof-map-lookaside.c', + 'sysprof-mountinfo.c', 'sysprof-polkit.c', 'sysprof-symbol-map.c', ipc_service_src, diff --git a/src/libsysprof/sysprof-mountinfo.c b/src/libsysprof/sysprof-mountinfo.c new file mode 100644 index 00000000..82d9e0f3 --- /dev/null +++ b/src/libsysprof/sysprof-mountinfo.c @@ -0,0 +1,283 @@ +/* sysprof-mountinfo.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-mountinfo" + +#include "config.h" + +#include "sysprof-mountinfo.h" + +typedef struct +{ + gchar *device; + gchar *mountpoint; +} Mount; + +typedef struct +{ + gchar *host_path; + gchar *mount_path; +} Mountinfo; + +struct _SysprofMountinfo +{ + GArray *mounts; + GArray *mountinfos; + GHashTable *dircache; +}; + +enum { + COLUMN_MOUNT_ID = 0, + COLUMN_MOUNT_PARENT_ID, + COLUMN_MAJOR_MINOR, + COLUMN_ROOT, + COLUMN_MOUNT_POINT, + COLUMN_MOUNT_OPTIONS, +}; + +static void +mount_clear (gpointer data) +{ + Mount *m = data; + + g_free (m->device); + g_free (m->mountpoint); +} + +static void +mountinfo_clear (gpointer data) +{ + Mountinfo *m = data; + + g_free (m->host_path); + g_free (m->mount_path); +} + +SysprofMountinfo * +sysprof_mountinfo_new (void) +{ + SysprofMountinfo *self; + + self = g_slice_new0 (SysprofMountinfo); + self->mounts = g_array_new (FALSE, FALSE, sizeof (Mount)); + g_array_set_clear_func (self->mounts, mount_clear); + self->mountinfos = g_array_new (FALSE, FALSE, sizeof (Mountinfo)); + g_array_set_clear_func (self->mountinfos, mountinfo_clear); + self->dircache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + return g_steal_pointer (&self); +} + +void +sysprof_mountinfo_free (SysprofMountinfo *self) +{ + g_clear_pointer (&self->mounts, g_array_unref); + g_clear_pointer (&self->mountinfos, g_array_unref); + g_clear_pointer (&self->dircache, g_hash_table_unref); + g_slice_free (SysprofMountinfo, self); +} + +gchar * +sysprof_mountinfo_translate (SysprofMountinfo *self, + const gchar *path) +{ + g_autofree gchar *dir = NULL; + const gchar *translate; + + g_assert (self != NULL); + + if (path == NULL) + return NULL; + + /* First try the dircache by looking up the translated parent + * directory and appending basename to it. + */ + dir = g_path_get_dirname (path); + + if ((translate = g_hash_table_lookup (self->dircache, dir))) + { + g_autofree gchar *name = g_path_get_basename (path); + return g_build_filename (translate, name, NULL); + } + + for (guint i = 0; i < self->mountinfos->len; i++) + { + const Mountinfo *m = &g_array_index (self->mountinfos, Mountinfo, i); + + if (g_str_has_prefix (path, m->mount_path)) + { + gchar *ret; + + ret = g_build_filename (m->host_path, path + strlen (m->mount_path), NULL); + g_hash_table_insert (self->dircache, + g_steal_pointer (&dir), + g_path_get_dirname (ret)); + return ret; + } + } + + return NULL; +} + +void +sysprof_mountinfo_parse_mounts (SysprofMountinfo *self, + const gchar *contents) +{ + g_auto(GStrv) lines = NULL; + + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (contents != NULL); + + lines = g_strsplit (contents, "\n", 0); + + for (guint i = 0; lines[i]; i++) + { + g_auto(GStrv) parts = g_strsplit (lines[i], " ", 3); + Mount m; + + /* Field 1 and 2 are "device" and "mountpoint" */ + if (parts[0] == NULL || parts[1] == NULL) + continue; + + /* Replace encoded space "\040" with ' ' */ + if (strstr (parts[1], "\\040") != NULL) + { + g_auto(GStrv) ep = g_strsplit (parts[1], "\\040", 0); + g_free (parts[1]); + parts[1] = g_strjoinv (" ", ep); + } + + m.device = g_strdup (parts[0]); + m.mountpoint = g_strdup (parts[1]); + g_array_append_val (self->mounts, m); + } +} + +void +sysprof_mountinfo_reset (SysprofMountinfo *self) +{ + g_assert (self != NULL); + g_assert (self->mountinfos != NULL); + + /* Keep mounts, but release mountinfos */ + if (self->mountinfos->len) + g_array_remove_range (self->mountinfos, 0, self->mountinfos->len); + g_hash_table_remove_all (self->dircache); +} + +static const gchar * +get_device_mount (SysprofMountinfo *self, + const gchar *device) +{ + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (device != NULL); + + for (guint i = 0; i < self->mounts->len; i++) + { + const Mount *m = &g_array_index (self->mounts, Mount, i); + + if (strcmp (device, m->device) == 0) + return m->mountpoint; + } + + return NULL; +} + +static void +sysprof_mountinfo_parse_mountinfo_line (SysprofMountinfo *self, + const gchar *line) +{ + g_auto(GStrv) parts = NULL; + const gchar *prefix; + const gchar *src; + Mountinfo m; + gsize n_parts; + guint i; + + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (self->mountinfos != NULL); + + parts = g_strsplit (line, " ", 0); + n_parts = g_strv_length (parts); + + if (n_parts < 10) + return; + + /* The device identifier is the 2nd column after "-" */ + for (i = 5; i < n_parts; i++) + { + if (strcmp (parts[i], "-") == 0) + break; + } + if (i >= n_parts || parts[i][0] != '-' || parts[i+1] == NULL || parts[i+2] == NULL) + return; + + prefix = get_device_mount (self, parts[i+2]); + + src = parts[COLUMN_ROOT]; + while (*src == '/') + src++; + + if (prefix != NULL) + m.host_path = g_build_filename (prefix, src, NULL); + else + m.host_path = g_strdup (src); + + m.mount_path = g_strdup (parts[COLUMN_MOUNT_POINT]); + g_array_append_val (self->mountinfos, m); +} + +static gint +sort_by_length (gconstpointer a, + gconstpointer b) +{ + const Mountinfo *mpa = a; + const Mountinfo *mpb = b; + gsize alen = strlen (mpa->mount_path); + gsize blen = strlen (mpb->mount_path); + + if (alen > blen) + return -1; + else if (blen > alen) + return 1; + else + return 0; +} + +void +sysprof_mountinfo_parse_mountinfo (SysprofMountinfo *self, + const gchar *contents) +{ + g_auto(GStrv) lines = NULL; + + g_assert (self != NULL); + g_assert (self->mounts != NULL); + g_assert (self->mountinfos != NULL); + + lines = g_strsplit (contents, "\n", 0); + + for (guint i = 0; lines[i]; i++) + sysprof_mountinfo_parse_mountinfo_line (self, lines[i]); + + g_array_sort (self->mountinfos, sort_by_length); +} diff --git a/src/libsysprof/sysprof-mountinfo.h b/src/libsysprof/sysprof-mountinfo.h new file mode 100644 index 00000000..85c661e2 --- /dev/null +++ b/src/libsysprof/sysprof-mountinfo.h @@ -0,0 +1,41 @@ +/* sysprof-mountinfo.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 + +G_BEGIN_DECLS + +typedef struct _SysprofMountinfo SysprofMountinfo; + +SysprofMountinfo *sysprof_mountinfo_new (void); +void sysprof_mountinfo_parse_mounts (SysprofMountinfo *self, + const gchar *contents); +void sysprof_mountinfo_parse_mountinfo (SysprofMountinfo *self, + const gchar *contents); +void sysprof_mountinfo_reset (SysprofMountinfo *self); +gchar *sysprof_mountinfo_translate (SysprofMountinfo *self, + const gchar *path); +void sysprof_mountinfo_free (SysprofMountinfo *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMountinfo, sysprof_mountinfo_free) + +G_END_DECLS diff --git a/src/tests/meson.build b/src/tests/meson.build index 53a3785c..690ec5c1 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -43,6 +43,13 @@ test_addr_decode = executable('test-addr-decode', 'test-addr-decode.c', dependencies: test_deps, ) +test_mountinfo = executable('test-mountinfo', + [ 'test-mountinfo.c', + '../libsysprof/sysprof-mountinfo.c'], + c_args: test_cflags, + dependencies: test_deps, +) + if get_option('enable_gtk') diff --git a/src/tests/test-mountinfo.c b/src/tests/test-mountinfo.c new file mode 100644 index 00000000..e3902112 --- /dev/null +++ b/src/tests/test-mountinfo.c @@ -0,0 +1,59 @@ +/* test-mountinfo.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 + */ + +#include "sysprof-mountinfo.h" + +gint +main (gint argc, + gchar *argv[]) +{ + g_autoptr(SysprofMountinfo) info = NULL; + g_autofree gchar *contents = NULL; + g_auto(GStrv) lines = NULL; + g_autoptr(GError) error = NULL; + g_autofree gchar *lookup = NULL; + gsize len; + + if (argc != 3) + { + g_printerr ("usage: %s MOUNTINFO_FILE PATH_TO_TRANSLATE\n", argv[0]); + return 1; + } + + info = sysprof_mountinfo_new (); + + if (!g_file_get_contents ("/proc/mounts", &contents, &len, &error)) + g_error ("%s", error->message); + sysprof_mountinfo_parse_mounts (info, contents); + g_free (g_steal_pointer (&contents)); + + if (!g_file_get_contents (argv[1], &contents, &len, &error)) + g_error ("%s", error->message); + sysprof_mountinfo_parse_mountinfo (info, contents); + + lookup = sysprof_mountinfo_translate (info, argv[2]); + + if (lookup) + g_print ("%s\n", lookup); + else + g_print ("%s\n", argv[2]); + + return 0; +}