Files
sysprof/src/libsysprof/sysprof-mountinfo.c
Christian Hergert 7e7657dd03 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.
2019-08-02 12:56:54 -07:00

284 lines
6.7 KiB
C

/* sysprof-mountinfo.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
*/
#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);
}