blob: fdb1ef5c2c3a135706c94cb7654feff658e0832c [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* This file is part of libmount from util-linux project.
*
* Copyright (C) 2011-2018 Karel Zak <kzak@redhat.com>
*
* libmount is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*/
/**
* SECTION: tabdiff
* @title: Compare changes in mount tables
* @short_description: compare changes in the list of the mounted filesystems
*/
#include "mountP.h"
struct tabdiff_entry {
int oper; /* MNT_TABDIFF_* flags; */
struct libmnt_fs *old_fs; /* pointer to the old FS */
struct libmnt_fs *new_fs; /* pointer to the new FS */
struct list_head changes;
};
struct libmnt_tabdiff {
int nchanges; /* number of changes */
struct list_head changes; /* list with modified entries */
struct list_head unused; /* list with unused entries */
};
/**
* mnt_new_tabdiff:
*
* Allocates a new table diff struct.
*
* Returns: new diff handler or NULL.
*/
struct libmnt_tabdiff *mnt_new_tabdiff(void)
{
struct libmnt_tabdiff *df = calloc(1, sizeof(*df));
if (!df)
return NULL;
DBG(DIFF, ul_debugobj(df, "alloc"));
INIT_LIST_HEAD(&df->changes);
INIT_LIST_HEAD(&df->unused);
return df;
}
static void free_tabdiff_entry(struct tabdiff_entry *de)
{
if (!de)
return;
list_del(&de->changes);
mnt_unref_fs(de->new_fs);
mnt_unref_fs(de->old_fs);
free(de);
}
/**
* mnt_free_tabdiff:
* @df: tab diff
*
* Deallocates tab diff struct and all entries.
*/
void mnt_free_tabdiff(struct libmnt_tabdiff *df)
{
if (!df)
return;
DBG(DIFF, ul_debugobj(df, "free"));
while (!list_empty(&df->changes)) {
struct tabdiff_entry *de = list_entry(df->changes.next,
struct tabdiff_entry, changes);
free_tabdiff_entry(de);
}
free(df);
}
/**
* mnt_tabdiff_next_change:
* @df: tabdiff pointer
* @itr: iterator
* @old_fs: returns the old entry or NULL if new entry added
* @new_fs: returns the new entry or NULL if old entry removed
* @oper: MNT_TABDIFF_{MOVE,UMOUNT,REMOUNT,MOUNT} flags
*
* The options @old_fs, @new_fs and @oper are optional.
*
* Returns: 0 on success, negative number in case of error or 1 at the end of list.
*/
int mnt_tabdiff_next_change(struct libmnt_tabdiff *df, struct libmnt_iter *itr,
struct libmnt_fs **old_fs, struct libmnt_fs **new_fs, int *oper)
{
int rc = 1;
struct tabdiff_entry *de = NULL;
if (!df || !itr)
return -EINVAL;
if (!itr->head)
MNT_ITER_INIT(itr, &df->changes);
if (itr->p != itr->head) {
MNT_ITER_ITERATE(itr, de, struct tabdiff_entry, changes);
rc = 0;
}
if (old_fs)
*old_fs = de ? de->old_fs : NULL;
if (new_fs)
*new_fs = de ? de->new_fs : NULL;
if (oper)
*oper = de ? de->oper : 0;
return rc;
}
static int tabdiff_reset(struct libmnt_tabdiff *df)
{
assert(df);
DBG(DIFF, ul_debugobj(df, "resetting"));
/* zeroize all entries and move them to the list of unused
*/
while (!list_empty(&df->changes)) {
struct tabdiff_entry *de = list_entry(df->changes.next,
struct tabdiff_entry, changes);
list_del_init(&de->changes);
list_add_tail(&de->changes, &df->unused);
mnt_unref_fs(de->new_fs);
mnt_unref_fs(de->old_fs);
de->new_fs = de->old_fs = NULL;
de->oper = 0;
}
df->nchanges = 0;
return 0;
}
static int tabdiff_add_entry(struct libmnt_tabdiff *df, struct libmnt_fs *old,
struct libmnt_fs *new, int oper)
{
struct tabdiff_entry *de;
assert(df);
DBG(DIFF, ul_debugobj(df, "add change on %s",
mnt_fs_get_target(new ? new : old)));
if (!list_empty(&df->unused)) {
de = list_entry(df->unused.next, struct tabdiff_entry, changes);
list_del(&de->changes);
} else {
de = calloc(1, sizeof(*de));
if (!de)
return -ENOMEM;
}
INIT_LIST_HEAD(&de->changes);
mnt_ref_fs(new);
mnt_ref_fs(old);
mnt_unref_fs(de->new_fs);
mnt_unref_fs(de->old_fs);
de->old_fs = old;
de->new_fs = new;
de->oper = oper;
list_add_tail(&de->changes, &df->changes);
df->nchanges++;
return 0;
}
static struct tabdiff_entry *tabdiff_get_mount(struct libmnt_tabdiff *df,
const char *src,
int id)
{
struct list_head *p;
assert(df);
list_for_each(p, &df->changes) {
struct tabdiff_entry *de;
de = list_entry(p, struct tabdiff_entry, changes);
if (de->oper == MNT_TABDIFF_MOUNT && de->new_fs &&
mnt_fs_get_id(de->new_fs) == id) {
const char *s = mnt_fs_get_source(de->new_fs);
if (s == NULL && src == NULL)
return de;
if (s && src && strcmp(s, src) == 0)
return de;
}
}
return NULL;
}
/**
* mnt_diff_tables:
* @df: diff handler
* @old_tab: old table
* @new_tab: new table
*
* Compares @old_tab and @new_tab, the result is stored in @df and accessible by
* mnt_tabdiff_next_change().
*
* Returns: number of changes, negative number in case of error.
*/
int mnt_diff_tables(struct libmnt_tabdiff *df, struct libmnt_table *old_tab,
struct libmnt_table *new_tab)
{
struct libmnt_fs *fs;
struct libmnt_iter itr;
int no, nn;
if (!df || !old_tab || !new_tab)
return -EINVAL;
tabdiff_reset(df);
no = mnt_table_get_nents(old_tab);
nn = mnt_table_get_nents(new_tab);
if (!no && !nn) /* both tables are empty */
return 0;
DBG(DIFF, ul_debugobj(df, "analyze new (%d entries), "
"old (%d entries)",
nn, no));
mnt_reset_iter(&itr, MNT_ITER_FORWARD);
/* all mounted or umounted */
if (!no && nn) {
while(mnt_table_next_fs(new_tab, &itr, &fs) == 0)
tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT);
goto done;
} else if (no && !nn) {
while(mnt_table_next_fs(old_tab, &itr, &fs) == 0)
tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT);
goto done;
}
/* search newly mounted or modified */
while(mnt_table_next_fs(new_tab, &itr, &fs) == 0) {
struct libmnt_fs *o_fs;
const char *src = mnt_fs_get_source(fs),
*tgt = mnt_fs_get_target(fs);
o_fs = mnt_table_find_pair(old_tab, src, tgt, MNT_ITER_FORWARD);
if (!o_fs)
/* 'fs' is not in the old table -- so newly mounted */
tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT);
else {
/* is modified? */
const char *v1 = mnt_fs_get_vfs_options(o_fs),
*v2 = mnt_fs_get_vfs_options(fs),
*f1 = mnt_fs_get_fs_options(o_fs),
*f2 = mnt_fs_get_fs_options(fs);
if ((v1 && v2 && strcmp(v1, v2)) || (f1 && f2 && strcmp(f1, f2)))
tabdiff_add_entry(df, o_fs, fs, MNT_TABDIFF_REMOUNT);
}
}
/* search umounted or moved */
mnt_reset_iter(&itr, MNT_ITER_FORWARD);
while(mnt_table_next_fs(old_tab, &itr, &fs) == 0) {
const char *src = mnt_fs_get_source(fs),
*tgt = mnt_fs_get_target(fs);
if (!mnt_table_find_pair(new_tab, src, tgt, MNT_ITER_FORWARD)) {
struct tabdiff_entry *de;
de = tabdiff_get_mount(df, src, mnt_fs_get_id(fs));
if (de) {
mnt_ref_fs(fs);
mnt_unref_fs(de->old_fs);
de->oper = MNT_TABDIFF_MOVE;
de->old_fs = fs;
} else
tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT);
}
}
done:
DBG(DIFF, ul_debugobj(df, "%d changes detected", df->nchanges));
return df->nchanges;
}
#ifdef TEST_PROGRAM
static int test_diff(struct libmnt_test *ts, int argc, char *argv[])
{
struct libmnt_table *tb_old, *tb_new;
struct libmnt_tabdiff *diff;
struct libmnt_iter *itr;
struct libmnt_fs *old, *new;
int rc = -1, change;
tb_old = mnt_new_table_from_file(argv[1]);
tb_new = mnt_new_table_from_file(argv[2]);
diff = mnt_new_tabdiff();
itr = mnt_new_iter(MNT_ITER_FORWARD);
if (!tb_old || !tb_new || !diff || !itr) {
warnx("failed to allocate resources");
goto done;
}
rc = mnt_diff_tables(diff, tb_old, tb_new);
if (rc < 0)
goto done;
while(mnt_tabdiff_next_change(diff, itr, &old, &new, &change) == 0) {
printf("%s on %s: ", mnt_fs_get_source(new ? new : old),
mnt_fs_get_target(new ? new : old));
switch(change) {
case MNT_TABDIFF_MOVE:
printf("MOVED to %s\n", mnt_fs_get_target(new));
break;
case MNT_TABDIFF_UMOUNT:
printf("UMOUNTED\n");
break;
case MNT_TABDIFF_REMOUNT:
printf("REMOUNTED from '%s' to '%s'\n",
mnt_fs_get_options(old),
mnt_fs_get_options(new));
break;
case MNT_TABDIFF_MOUNT:
printf("MOUNTED\n");
break;
default:
printf("unknown change!\n");
}
}
rc = 0;
done:
mnt_unref_table(tb_old);
mnt_unref_table(tb_new);
mnt_free_tabdiff(diff);
mnt_free_iter(itr);
return rc;
}
int main(int argc, char *argv[])
{
struct libmnt_test tss[] = {
{ "--diff", test_diff, "<old> <new> prints change" },
{ NULL }
};
return mnt_run_test(tss, argc, argv);
}
#endif /* TEST_PROGRAM */