diff options
Diffstat (limited to 'libmount/src/tab_diff.c')
-rw-r--r-- | libmount/src/tab_diff.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/libmount/src/tab_diff.c b/libmount/src/tab_diff.c new file mode 100644 index 0000000..70440bf --- /dev/null +++ b/libmount/src/tab_diff.c @@ -0,0 +1,377 @@ +/* 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(&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 */ |