diff options
Diffstat (limited to 'libmount/src/tab.c')
-rw-r--r-- | libmount/src/tab.c | 2278 |
1 files changed, 2278 insertions, 0 deletions
diff --git a/libmount/src/tab.c b/libmount/src/tab.c new file mode 100644 index 0000000..9725664 --- /dev/null +++ b/libmount/src/tab.c @@ -0,0 +1,2278 @@ + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-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: table + * @title: Table of filesystems + * @short_description: container for entries from fstab, mtab or mountinfo + * + * Note that mnt_table_find_* functions are mount(8) compatible. These functions + * try to find an entry in more iterations, where the first attempt is always + * based on comparison with unmodified (non-canonicalized or un-evaluated) + * paths or tags. For example a fstab with two entries: + * <informalexample> + * <programlisting> + * LABEL=foo /foo auto rw + * /dev/foo /foo auto rw + * </programlisting> + * </informalexample> + * + * where both lines are used for the *same* device, then + * <informalexample> + * <programlisting> + * mnt_table_find_source(tb, "/dev/foo", &fs); + * </programlisting> + * </informalexample> + * will returns the second line, and + * <informalexample> + * <programlisting> + * mnt_table_find_source(tb, "LABEL=foo", &fs); + * </programlisting> + * </informalexample> + * will returns the first entry, and + * <informalexample> + * <programlisting> + * mnt_table_find_source(tb, "UUID=anyuuid", &fs); + * </programlisting> + * </informalexample> + * will return the first entry (if UUID matches with the device). + */ +#include <blkid.h> + +#include "mountP.h" +#include "strutils.h" +#include "loopdev.h" +#include "fileutils.h" +#include "canonicalize.h" + +int is_mountinfo(struct libmnt_table *tb) +{ + struct libmnt_fs *fs; + + if (!tb) + return 0; + + fs = list_first_entry(&tb->ents, struct libmnt_fs, ents); + if (fs && mnt_fs_is_kernel(fs) && mnt_fs_get_root(fs)) + return 1; + + return 0; +} + +/** + * mnt_new_table: + * + * The tab is a container for struct libmnt_fs entries that usually represents a fstab, + * mtab or mountinfo file from your system. + * + * See also mnt_table_parse_file(). + * + * Returns: newly allocated tab struct. + */ +struct libmnt_table *mnt_new_table(void) +{ + struct libmnt_table *tb = NULL; + + tb = calloc(1, sizeof(*tb)); + if (!tb) + return NULL; + + DBG(TAB, ul_debugobj(tb, "alloc")); + tb->refcount = 1; + INIT_LIST_HEAD(&tb->ents); + return tb; +} + +/** + * mnt_reset_table: + * @tb: tab pointer + * + * Removes all entries (filesystems) from the table. The filesystems with zero + * reference count will be deallocated. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_reset_table(struct libmnt_table *tb) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "reset")); + + while (!list_empty(&tb->ents)) { + struct libmnt_fs *fs = list_entry(tb->ents.next, + struct libmnt_fs, ents); + mnt_table_remove_fs(tb, fs); + } + + tb->nents = 0; + return 0; +} + +/** + * mnt_ref_table: + * @tb: table pointer + * + * Increments reference counter. + */ +void mnt_ref_table(struct libmnt_table *tb) +{ + if (tb) { + tb->refcount++; + /*DBG(FS, ul_debugobj(tb, "ref=%d", tb->refcount));*/ + } +} + +/** + * mnt_unref_table: + * @tb: table pointer + * + * De-increments reference counter, on zero the @tb is automatically + * deallocated by mnt_free_table(). + */ +void mnt_unref_table(struct libmnt_table *tb) +{ + if (tb) { + tb->refcount--; + /*DBG(FS, ul_debugobj(tb, "unref=%d", tb->refcount));*/ + if (tb->refcount <= 0) + mnt_free_table(tb); + } +} + + +/** + * mnt_free_table: + * @tb: tab pointer + * + * Deallocates the table. This function does not care about reference count. Don't + * use this function directly -- it's better to use mnt_unref_table(). + * + * The table entries (filesystems) are unreferenced by mnt_reset_table() and + * cache by mnt_unref_cache(). + */ +void mnt_free_table(struct libmnt_table *tb) +{ + if (!tb) + return; + + mnt_reset_table(tb); + DBG(TAB, ul_debugobj(tb, "free [refcount=%d]", tb->refcount)); + + mnt_unref_cache(tb->cache); + free(tb->comm_intro); + free(tb->comm_tail); + free(tb); +} + +/** + * mnt_table_get_nents: + * @tb: pointer to tab + * + * Returns: number of entries in table. + */ +int mnt_table_get_nents(struct libmnt_table *tb) +{ + return tb ? tb->nents : 0; +} + +/** + * mnt_table_is_empty: + * @tb: pointer to tab + * + * Returns: 1 if the table is without filesystems, or 0. + */ +int mnt_table_is_empty(struct libmnt_table *tb) +{ + return tb == NULL || list_empty(&tb->ents) ? 1 : 0; +} + +/** + * mnt_table_set_userdata: + * @tb: pointer to tab + * @data: pointer to user data + * + * Sets pointer to the private user data. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_userdata(struct libmnt_table *tb, void *data) +{ + if (!tb) + return -EINVAL; + + tb->userdata = data; + return 0; +} + +/** + * mnt_table_get_userdata: + * @tb: pointer to tab + * + * Returns: pointer to user's data. + */ +void *mnt_table_get_userdata(struct libmnt_table *tb) +{ + return tb ? tb->userdata : NULL; +} + +/** + * mnt_table_enable_comments: + * @tb: pointer to tab + * @enable: TRUE or FALSE + * + * Enables parsing of comments. + * + * The initial (intro) file comment is accessible by + * mnt_table_get_intro_comment(). The intro and the comment of the first fstab + * entry has to be separated by blank line. The filesystem comments are + * accessible by mnt_fs_get_comment(). The trailing fstab comment is accessible + * by mnt_table_get_trailing_comment(). + * + * <informalexample> + * <programlisting> + * # + * # Intro comment + * # + * + * # this comments belongs to the first fs + * LABEL=foo /mnt/foo auto defaults 1 2 + * # this comments belongs to the second fs + * LABEL=bar /mnt/bar auto defaults 1 2 + * # tailing comment + * </programlisting> + * </informalexample> + */ +void mnt_table_enable_comments(struct libmnt_table *tb, int enable) +{ + if (tb) + tb->comms = enable; +} + +/** + * mnt_table_with_comments: + * @tb: pointer to table + * + * Returns: 1 if comments parsing is enabled, or 0. + */ +int mnt_table_with_comments(struct libmnt_table *tb) +{ + assert(tb); + return tb ? tb->comms : 0; +} + +/** + * mnt_table_get_intro_comment: + * @tb: pointer to tab + * + * Returns: initial comment in tb + */ +const char *mnt_table_get_intro_comment(struct libmnt_table *tb) +{ + return tb ? tb->comm_intro : NULL; +} + +/** + * mnt_table_set_into_comment: + * @tb: pointer to tab + * @comm: comment or NULL + * + * Sets the initial comment in tb. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm) +{ + return strdup_to_struct_member(tb, comm_intro, comm); +} + +/** + * mnt_table_append_into_comment: + * @tb: pointer to tab + * @comm: comment of NULL + * + * Appends the initial comment in tb. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm) +{ + if (!tb) + return -EINVAL; + return strappend(&tb->comm_intro, comm); +} + +/** + * mnt_table_get_trailing_comment: + * @tb: pointer to tab + * + * Returns: table trailing comment + */ +const char *mnt_table_get_trailing_comment(struct libmnt_table *tb) +{ + return tb ? tb->comm_tail : NULL; +} + +/** + * mnt_table_set_trailing_comment + * @tb: pointer to tab + * @comm: comment string + * + * Sets the trailing comment in table. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm) +{ + return strdup_to_struct_member(tb, comm_tail, comm); +} + +/** + * mnt_table_append_trailing_comment: + * @tb: pointer to tab + * @comm: comment of NULL + * + * Appends to the trailing table comment. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm) +{ + if (!tb) + return -EINVAL; + return strappend(&tb->comm_tail, comm); +} + +/** + * mnt_table_set_cache: + * @tb: pointer to tab + * @mpc: pointer to struct libmnt_cache instance + * + * Sets up a cache for canonicalized paths and evaluated tags (LABEL/UUID). The + * cache is recommended for mnt_table_find_*() functions. + * + * The cache could be shared between more tabs. Be careful when you share the + * same cache between more threads -- currently the cache does not provide any + * locking method. + * + * This function increments cache reference counter. It's recommended to use + * mnt_unref_cache() after mnt_table_set_cache() if you want to keep the cache + * referenced by @tb only. + * + * See also mnt_new_cache(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc) +{ + if (!tb) + return -EINVAL; + + mnt_ref_cache(mpc); /* new */ + mnt_unref_cache(tb->cache); /* old */ + tb->cache = mpc; + return 0; +} + +/** + * mnt_table_get_cache: + * @tb: pointer to tab + * + * Returns: pointer to struct libmnt_cache instance or NULL. + */ +struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb) +{ + return tb ? tb->cache : NULL; +} + +/** + * mnt_table_find_fs: + * @tb: tab pointer + * @fs: entry to look for + * + * Checks if @fs is part of table @tb. + * + * Returns: index of @fs in table, 0 if not found or negative number in case of error. + * + * Since: 2.34 + */ +int mnt_table_find_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + struct list_head *p; + int i = 0; + + if (!tb || !fs) + return -EINVAL; + + if (list_empty(&fs->ents)) + return 0; + + /* Let's use directly list rather than mnt_table_next_fs() as we + * compare list entry with fs only. + */ + list_for_each(p, &tb->ents) { + ++i; + if (list_entry(p, struct libmnt_fs, ents) == fs) + return i; + } + + return 0; +} + +/** + * mnt_table_add_fs: + * @tb: tab pointer + * @fs: new entry + * + * Adds a new entry to tab and increment @fs reference counter. Don't forget to + * use mnt_unref_fs() after mnt_table_add_fs() you want to keep the @fs + * referenced by the table only. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + if (!tb || !fs) + return -EINVAL; + + if (fs->tab) + return -EBUSY; + + mnt_ref_fs(fs); + list_add_tail(&fs->ents, &tb->ents); + fs->tab = tb; + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "add entry: %s %s", + mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + return 0; +} + +static int __table_insert_fs( + struct libmnt_table *tb, int before, + struct libmnt_fs *pos, struct libmnt_fs *fs) +{ + struct list_head *head = pos ? &pos->ents : &tb->ents; + + if (before) + list_add(&fs->ents, head); + else + list_add_tail(&fs->ents, head); + + fs->tab = tb; + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "insert entry: %s %s", + mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + return 0; +} + +/** + * mnt_table_insert_fs: + * @tb: tab pointer + * @before: 1 to insert before pos, 0 to insert after pos + * @pos: entry to specify position or NULL + * @fs: new entry + * + * Adds a new entry to @tb before or after a specific table entry @pos. If the + * @pos is NULL than add the begin of the @tab if @before is 1; or to the tail + * of the @tb if @before is 0. + * + * This function increments reference to @fs. Don't forget to use + * mnt_unref_fs() after mnt_table_insert_fs() if you want to keep the @fs + * referenced by the table only. + * + * Returns: 0 on success or negative number in case of error. + * + * Since: 2.34 + */ +int mnt_table_insert_fs(struct libmnt_table *tb, int before, + struct libmnt_fs *pos, struct libmnt_fs *fs) +{ + if (!tb || !fs) + return -EINVAL; + + if (fs->tab) + return -EBUSY; + + if (pos && pos->tab != tb) + return -ENOENT; + + mnt_ref_fs(fs); + return __table_insert_fs(tb, before, pos, fs); +} + +/** + * mnt_table_move_fs: + * @src: tab pointer of source table + * @dst: tab pointer of destination table + * @before: 1 to move before position, 0 to move after position + * @pos: entry to specify position or NULL + * @fs: entry to move + * + * Removes @fs from @src table and adds it before/after a specific entry @pos + * of @dst table. If the @pos is NULL than add the begin of the @dst if @before + * is 1; or to the tail of the @dst if @before is 0. + * + * The reference counter of @fs is not modified. + * + * Returns: 0 on success or negative number in case of error. + * + * Since: 2.34 + */ +int mnt_table_move_fs(struct libmnt_table *src, struct libmnt_table *dst, + int before, struct libmnt_fs *pos, struct libmnt_fs *fs) +{ + if (!src || !dst || !fs) + return -EINVAL; + + if (fs->tab != src || (pos && pos->tab != dst)) + return -ENOENT; + + /* remove from source */ + list_del_init(&fs->ents); + src->nents--; + + /* insert to the destination */ + return __table_insert_fs(dst, before, pos, fs); +} + + +/** + * mnt_table_remove_fs: + * @tb: tab pointer + * @fs: new entry + * + * Removes the @fs from the table and de-increment reference counter of the @fs. The + * filesystem with zero reference counter will be deallocated. Don't forget to use + * mnt_ref_fs() before call mnt_table_remove_fs() if you want to use @fs later. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + if (!tb || !fs || fs->tab != tb) + return -EINVAL; + + fs->tab = NULL; + list_del_init(&fs->ents); + + mnt_unref_fs(fs); + tb->nents--; + return 0; +} + +static inline struct libmnt_fs *get_parent_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + struct libmnt_iter itr; + struct libmnt_fs *x; + int parent_id = mnt_fs_get_parent_id(fs); + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (mnt_table_next_fs(tb, &itr, &x) == 0) { + if (mnt_fs_get_id(x) == parent_id) + return x; + } + + return NULL; +} + +/** + * mnt_table_get_root_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @root: NULL or returns pointer to the root filesystem (/) + * + * The function uses the parent ID from the mountinfo file to determine the + * root filesystem (the filesystem with the smallest ID with parent ID missing + * in the table). The function is designed mostly for applications where it is + * necessary to sort mountpoints by IDs to get the tree of the mountpoints + * (e.g. findmnt default output). + * + * If you're not sure, then use + * + * mnt_table_find_target(tb, "/", MNT_ITER_BACKWARD); + * + * this is more robust and usable for arbitrary tab files (including fstab). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs, *root_fs = NULL; + int root_id = 0; + + if (!tb || !is_mountinfo(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "lookup root fs")); + + /* get smallest possible ID from the table */ + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + int id = mnt_fs_get_parent_id(fs); + + if (!root_fs || id < root_id) { + root_fs = fs; + root_id = id; + } + } + + /* go to the root node by "parent_id -> id" relation */ + while (root_fs) { + struct libmnt_fs *x = get_parent_fs(tb, root_fs); + if (!x || x == root_fs) + break; + DBG(TAB, ul_debugobj(tb, " messy mountinfo, walk to %s", mnt_fs_get_target(x))); + root_fs = x; + } + + if (root) + *root = root_fs; + + return root_fs ? 0 : -EINVAL; +} + +/** + * mnt_table_next_child_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @itr: iterator + * @parent: parental FS + * @chld: NULL or returns the next child filesystem + * + * Since version 2.40, the filesystems are returned in the order specified by + * @itr. In the old versions the derection is always MNT_ITER_FORWARD. + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr, + struct libmnt_fs *parent, struct libmnt_fs **chld) +{ + struct libmnt_fs *fs, *chfs = NULL; + int parent_id, lastchld_id = 0, chld_id = 0; + int direction; + + if (!tb || !itr || !parent || !is_mountinfo(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "lookup next child of '%s'", + mnt_fs_get_target(parent))); + parent_id = mnt_fs_get_id(parent); + direction = mnt_iter_get_direction(itr); + + /* get ID of the previously returned child */ + if (itr->head && itr->p != itr->head) { + fs = MNT_ITER_GET_ENTRY(itr, struct libmnt_fs, ents); + MNT_ITER_ITERATE(itr); + lastchld_id = mnt_fs_get_id(fs); + } + + mnt_reset_iter(itr, direction); + while (mnt_table_next_fs(tb, itr, &fs) == 0) { + int id; + + if (mnt_fs_get_parent_id(fs) != parent_id) + continue; + + id = mnt_fs_get_id(fs); + + /* avoid an infinite loop. This only happens in rare cases + * such as in early userspace when the rootfs is its own parent */ + if (id == parent_id) + continue; + + if (direction == MNT_ITER_FORWARD) { + /* return in the order of mounting */ + if ((!lastchld_id || id > lastchld_id) && + (!chfs || id < chld_id)) { + chfs = fs; + chld_id = id; + } + } else { + /* return last child first */ + if ((!lastchld_id || id < lastchld_id) && + (!chfs || id > chld_id)) { + chfs = fs; + chld_id = id; + } + } + } + + if (chld) + *chld = chfs; + if (!chfs) + return 1; /* end of iterator */ + + /* set the iterator to the @chfs for the next call */ + mnt_table_set_iter(tb, itr, chfs); + + return 0; +} + +/** + * mnt_table_over_fs: + * @tb: tab pointer + * @parent: pointer to parental FS + * @child: returns pointer to FS which over-mounting parent (optional) + * + * This function returns by @child the first filesystenm which is over-mounted + * on @parent. It means the mountpoint of @child is the same as for @parent and + * parent->id is the same as child->parent_id. + * + * Note that you need to call this function in loop until it returns 1 to get + * the highest filesystem. + * + * Example: + * <informalexample> + * <programlisting> + * while (mnt_table_over_fs(tb, cur, &over) == 0) { + * printf("%s overmounted by %d\n", mnt_fs_get_target(cur), mnt_fs_get_id(over)); + * cur = over; + * } + * </programlisting> + * </informalexample> + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_over_fs(struct libmnt_table *tb, struct libmnt_fs *parent, + struct libmnt_fs **child) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + int id; + const char *tgt; + + if (!tb || !parent || !is_mountinfo(tb)) + return -EINVAL; + + if (child) + *child = NULL; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + id = mnt_fs_get_id(parent); + tgt = mnt_fs_get_target(parent); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_get_parent_id(fs) == id && + mnt_fs_streq_target(fs, tgt) == 1) { + if (child) + *child = fs; + return 0; + } + } + + return 1; /* nothing */ +} + +/** + * mnt_table_next_fs: + * @tb: tab pointer + * @itr: iterator + * @fs: NULL or returns the next tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + * + * Example: + * <informalexample> + * <programlisting> + * while(mnt_table_next_fs(tb, itr, &fs) == 0) { + * const char *dir = mnt_fs_get_target(fs); + * printf("mount point: %s\n", dir); + * } + * </programlisting> + * </informalexample> + * + * lists all mountpoints from fstab in reverse order. + */ +int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs **fs) +{ + int rc = 1; + + if (!tb || !itr) + return -EINVAL; + if (fs) + *fs = NULL; + + if (!itr->head) + MNT_ITER_INIT(itr, &tb->ents); + if (itr->p != itr->head) { + if (fs) + *fs = MNT_ITER_GET_ENTRY(itr, struct libmnt_fs, ents); + MNT_ITER_ITERATE(itr); + rc = 0; + } + + return rc; +} + +/** + * mnt_table_first_fs: + * @tb: tab pointer + * @fs: NULL or returns the first tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_first_fs(struct libmnt_table *tb, struct libmnt_fs **fs) +{ + if (!tb) + return -EINVAL; + if (list_empty(&tb->ents)) + return 1; + if (fs) + *fs = list_first_entry(&tb->ents, struct libmnt_fs, ents); + return 0; +} + +/** + * mnt_table_last_fs: + * @tb: tab pointer + * @fs: NULL or returns the last tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_last_fs(struct libmnt_table *tb, struct libmnt_fs **fs) +{ + if (!tb) + return -EINVAL; + if (list_empty(&tb->ents)) + return 1; + if (fs) + *fs = list_last_entry(&tb->ents, struct libmnt_fs, ents); + return 0; +} + +/** + * mnt_table_find_next_fs: + * @tb: table + * @itr: iterator + * @match_func: function returning 1 or 0 + * @userdata: extra data for match_func + * @fs: NULL or returns pointer to the next matching table entry + * + * This function allows searching in @tb. + * + * Returns: negative number in case of error, 1 at end of table or 0 o success. + */ +int mnt_table_find_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, + int (*match_func)(struct libmnt_fs *, void *), void *userdata, + struct libmnt_fs **fs) +{ + struct libmnt_fs *re = NULL; + int match = 0; + + if (!tb || !itr || !match_func) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "lookup next fs")); + + if (fs) + *fs = NULL; + if (!itr->head) + MNT_ITER_INIT(itr, &tb->ents); + + while (!match) { + if (itr->p != itr->head) { + re = MNT_ITER_GET_ENTRY(itr, struct libmnt_fs, ents); + MNT_ITER_ITERATE(itr); + } else + return 1; /*end */ + + match = match_func(re, userdata); + } + + if (fs) + *fs = re; + return 0; +} + +static int mnt_table_move_parent(struct libmnt_table *tb, int oldid, int newid) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + + if (!tb) + return -EINVAL; + if (list_empty(&tb->ents)) + return 0; + + DBG(TAB, ul_debugobj(tb, "moving parent ID from %d -> %d", oldid, newid)); + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (fs->parent == oldid) + fs->parent = newid; + } + return 0; +} + +/** + * mnt_table_uniq_fs: + * @tb: table + * @flags: MNT_UNIQ_* + * @cmp: function to compare filesystems + * + * This function de-duplicate the @tb, but does not change order of the + * filesystems. The @cmp function has to return 0 if the filesystems are + * equal, otherwise non-zero. + * + * The default is to keep in the table later mounted filesystems (function uses + * backward mode iterator). + * + * @MNT_UNIQ_FORWARD: remove later mounted filesystems + * @MNT_UNIQ_KEEPTREE: keep parent->id relationship still valid + * + * Returns: negative number in case of error, or 0 o success. + */ +int mnt_table_uniq_fs(struct libmnt_table *tb, int flags, + int (*cmp)(struct libmnt_table *, + struct libmnt_fs *, + struct libmnt_fs *)) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + int direction = MNT_ITER_BACKWARD; + + if (!tb || !cmp) + return -EINVAL; + if (list_empty(&tb->ents)) + return 0; + + if (flags & MNT_UNIQ_FORWARD) + direction = MNT_ITER_FORWARD; + + DBG(TAB, ul_debugobj(tb, "de-duplicate")); + mnt_reset_iter(&itr, direction); + + if ((flags & MNT_UNIQ_KEEPTREE) && !is_mountinfo(tb)) + flags &= ~MNT_UNIQ_KEEPTREE; + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + int want = 1; + struct libmnt_iter xtr; + struct libmnt_fs *x; + + mnt_reset_iter(&xtr, direction); + while (want && mnt_table_next_fs(tb, &xtr, &x) == 0) { + if (fs == x) + break; + want = cmp(tb, x, fs) != 0; + } + + if (!want) { + if (flags & MNT_UNIQ_KEEPTREE) + mnt_table_move_parent(tb, mnt_fs_get_id(fs), + mnt_fs_get_parent_id(fs)); + + DBG(TAB, ul_debugobj(tb, "remove duplicate %s", + mnt_fs_get_target(fs))); + mnt_table_remove_fs(tb, fs); + } + } + + return 0; +} + +/** + * mnt_table_set_iter: + * @tb: tab pointer + * @itr: iterator + * @fs: tab entry + * + * Sets @iter to the position of @fs in the file @tb. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_set_iter(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs *fs) +{ + if (!tb || !itr || !fs) + return -EINVAL; + + if (fs->tab != tb) + return -ENOENT; + + MNT_ITER_INIT(itr, &tb->ents); + itr->p = &fs->ents; + + return 0; +} + +/** + * mnt_table_find_mountpoint: + * @tb: tab pointer + * @path: directory + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Same as mnt_get_mountpoint(), except this function does not rely on + * st_dev numbers. + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb, + const char *path, + int direction) +{ + char *mnt; + + if (!tb || !path || !*path) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup MOUNTPOINT: '%s'", path)); + + if (!mnt_is_path(path)) + return NULL; + + mnt = strdup(path); + if (!mnt) + return NULL; + + do { + char *p; + struct libmnt_fs *fs; + + fs = mnt_table_find_target(tb, mnt, direction); + if (fs) { + free(mnt); + return fs; + } + + p = stripoff_last_component(mnt); + if (!p) + break; + } while (mnt && *(mnt + 1) != '\0'); + + free(mnt); + return mnt_table_find_target(tb, "/", direction); +} + +/** + * mnt_table_find_target: + * @tb: tab pointer + * @path: mountpoint directory + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Try to lookup an entry in the given tab, three iterations are possible, the first + * with @path, the second with realpath(@path) and the third with realpath(@path) + * against realpath(fs->target). The 2nd and 3rd iterations are not performed when + * the @tb cache is not set (see mnt_table_set_cache()). If + * mnt_cache_set_targets(cache, mtab) was called, the 3rd iteration skips any + * @fs->target found in @mtab (see mnt_resolve_target()). + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *path, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + char *cn; + + if (!tb || !path || !*path) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s'", path)); + + /* native @target */ + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, path)) + return fs; + } + + /* try absolute path */ + if (is_relative_path(path) && (cn = absolute_path(path))) { + DBG(TAB, ul_debugobj(tb, "lookup absolute TARGET: '%s'", cn)); + mnt_reset_iter(&itr, direction); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, cn)) { + free(cn); + return fs; + } + } + free(cn); + } + + if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache))) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup canonical TARGET: '%s'", cn)); + + /* canonicalized paths in struct libmnt_table */ + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, cn)) + return fs; + } + + /* non-canonical path in struct libmnt_table + * -- note that mountpoint in /proc/self/mountinfo is already + * canonicalized by the kernel + */ + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + char *p; + + if (!fs->target + || mnt_fs_is_swaparea(fs) + || mnt_fs_is_kernel(fs) + || (*fs->target == '/' && *(fs->target + 1) == '\0')) + continue; + + p = mnt_resolve_target(fs->target, tb->cache); + /* both canonicalized, strcmp() is fine here */ + if (p && strcmp(cn, p) == 0) + return fs; + } + return NULL; +} + +/** + * mnt_table_find_srcpath: + * @tb: tab pointer + * @path: source path (devname or dirname) or NULL + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Try to lookup an entry in the given tab, four iterations are possible, the first + * with @path, the second with realpath(@path), the third with tags (LABEL, UUID, ..) + * from @path and the fourth with realpath(@path) against realpath(entry->srcpath). + * + * The 2nd, 3rd and 4th iterations are not performed when the @tb cache is not + * set (see mnt_table_set_cache()). + * + * For btrfs returns tab entry for default id. + * + * Note that NULL is a valid source path; it will be replaced with "none". The + * "none" is used in /proc/{mounts,self/mountinfo} for pseudo filesystems. + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, const char *path, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + int ntags = 0, nents; + char *cn; + const char *p; + + if (!tb || !path || !*path) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup SRCPATH: '%s'", path)); + + /* native paths */ + mnt_reset_iter(&itr, direction); + + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + + if (mnt_fs_streq_srcpath(fs, path)) { +#ifdef HAVE_BTRFS_SUPPORT + if (fs->fstype && !strcmp(fs->fstype, "btrfs")) { + uint64_t default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs)); + char *val; + size_t len; + + if (default_id == UINT64_MAX) + DBG(TAB, ul_debug("not found btrfs volume setting")); + + else if (mnt_fs_get_option(fs, "subvolid", &val, &len) == 0) { + uint64_t subvol_id; + + if (mnt_parse_offset(val, len, &subvol_id)) { + DBG(TAB, ul_debugobj(tb, "failed to parse subvolid=")); + continue; + } + if (subvol_id != default_id) + continue; + } + } +#endif /* HAVE_BTRFS_SUPPORT */ + return fs; + } + if (mnt_fs_get_tag(fs, NULL, NULL) == 0) + ntags++; + } + + if (!path || !tb->cache || !(cn = mnt_resolve_path(path, tb->cache))) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup canonical SRCPATH: '%s'", cn)); + + nents = mnt_table_get_nents(tb); + + /* canonicalized paths in struct libmnt_table */ + if (ntags < nents) { + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_srcpath(fs, cn)) + return fs; + } + } + + /* evaluated tag */ + if (ntags) { + int rc = mnt_cache_read_tags(tb->cache, cn); + + mnt_reset_iter(&itr, direction); + + if (rc == 0) { + /* @path's TAGs are in the cache */ + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + const char *t, *v; + + if (mnt_fs_get_tag(fs, &t, &v)) + continue; + + if (mnt_cache_device_has_tag(tb->cache, cn, t, v)) + return fs; + } + } else if (rc < 0 && errno == EACCES) { + /* @path is inaccessible, try evaluating all TAGs in @tb + * by udev symlinks -- this could be expensive on systems + * with a huge fstab/mtab */ + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + const char *t, *v, *x; + if (mnt_fs_get_tag(fs, &t, &v)) + continue; + x = mnt_resolve_tag(t, v, tb->cache); + + /* both canonicalized, strcmp() is fine here */ + if (x && strcmp(x, cn) == 0) + return fs; + } + } + } + + /* non-canonicalized paths in struct libmnt_table */ + if (ntags <= nents) { + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_is_netfs(fs) || mnt_fs_is_pseudofs(fs)) + continue; + p = mnt_fs_get_srcpath(fs); + if (p) + p = mnt_resolve_path(p, tb->cache); + + /* both canonicalized, strcmp() is fine here */ + if (p && strcmp(p, cn) == 0) + return fs; + } + } + + return NULL; +} + + +/** + * mnt_table_find_tag: + * @tb: tab pointer + * @tag: tag name (e.g "LABEL", "UUID", ...) + * @val: tag value + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Try to lookup an entry in the given tab, the first attempt is to lookup by @tag and + * @val, for the second attempt the tag is evaluated (converted to the device + * name) and mnt_table_find_srcpath() is performed. The second attempt is not + * performed when @tb cache is not set (see mnt_table_set_cache()). + + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag, + const char *val, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + + if (!tb || !tag || !*tag || !val) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup by TAG: %s %s", tag, val)); + + /* look up by TAG */ + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (fs->tagname && fs->tagval && + strcmp(fs->tagname, tag) == 0 && + strcmp(fs->tagval, val) == 0) + return fs; + } + + if (tb->cache) { + /* look up by device name */ + char *cn = mnt_resolve_tag(tag, val, tb->cache); + if (cn) + return mnt_table_find_srcpath(tb, cn, direction); + } + return NULL; +} + +/** + * mnt_table_find_target_with_option: + * @tb: tab pointer + * @path: mountpoint directory + * @option: option name (e.g "subvol", "subvolid", ...) + * @val: option value or NULL + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Try to lookup an entry in the given tab that matches combination of @path + * and @option. In difference to mnt_table_find_target(), only @path iteration + * is done. No lookup by device name, no canonicalization. + * + * Returns: a tab entry or NULL. + * + * Since: 2.28 + */ +struct libmnt_fs *mnt_table_find_target_with_option( + struct libmnt_table *tb, const char *path, + const char *option, const char *val, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + char *optval = NULL; + size_t optvalsz = 0, valsz = val ? strlen(val) : 0; + + if (!tb || !path || !*path || !option || !*option || !val) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s' with OPTION %s %s", path, option, val)); + + /* look up by native @target with OPTION */ + mnt_reset_iter(&itr, direction); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, path) + && mnt_fs_get_option(fs, option, &optval, &optvalsz) == 0 + && (!val || (optvalsz == valsz + && strncmp(optval, val, optvalsz) == 0))) + return fs; + } + return NULL; +} + +/** + * mnt_table_find_source: + * @tb: tab pointer + * @source: TAG or path + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * This is a high-level API for mnt_table_find_{srcpath,tag}. You needn't care + * about the @source format (device, LABEL, UUID, ...). This function parses + * the @source and calls mnt_table_find_tag() or mnt_table_find_srcpath(). + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb, + const char *source, int direction) +{ + struct libmnt_fs *fs; + char *t = NULL, *v = NULL; + + if (!tb) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup SOURCE: '%s'", source)); + + if (blkid_parse_tag_string(source, &t, &v) || !mnt_valid_tagname(t)) + fs = mnt_table_find_srcpath(tb, source, direction); + else + fs = mnt_table_find_tag(tb, t, v, direction); + + free(t); + free(v); + + return fs; +} + +/** + * mnt_table_find_pair + * @tb: tab pointer + * @source: TAG or path + * @target: mountpoint + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * This function is implemented by mnt_fs_match_source() and + * mnt_fs_match_target() functions. It means that this is more expensive than + * others mnt_table_find_* function, because every @tab entry is fully evaluated. + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb, const char *source, + const char *target, int direction) +{ + struct libmnt_fs *fs = NULL; + struct libmnt_iter itr; + + if (!tb || !target || !*target || !source || !*source) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup SOURCE: %s TARGET: %s", source, target)); + + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + + if (mnt_fs_match_target(fs, target, tb->cache) && + mnt_fs_match_source(fs, source, tb->cache)) + return fs; + } + + return NULL; +} + +/** + * mnt_table_find_devno + * @tb: /proc/self/mountinfo + * @devno: device number + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Note that zero could be a valid device number for the root pseudo filesystem (e.g. + * tmpfs). + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb, + dev_t devno, int direction) +{ + struct libmnt_fs *fs = NULL; + struct libmnt_iter itr; + + if (!tb) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup DEVNO: %d", (int) devno)); + + mnt_reset_iter(&itr, direction); + + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_get_devno(fs) == devno) + return fs; + } + + return NULL; +} + +static char *remove_mountpoint_from_path(const char *path, const char *mnt) +{ + char *res; + const char *p; + size_t sz; + + sz = strlen(mnt); + p = sz > 1 ? path + sz : path; + + res = *p ? strdup(p) : strdup("/"); + DBG(UTILS, ul_debug("%s fs-root is %s", path, res)); + return res; +} + +#ifdef HAVE_BTRFS_SUPPORT +static int get_btrfs_fs_root(struct libmnt_table *tb, struct libmnt_fs *fs, char **root) +{ + char *vol = NULL, *p; + size_t sz, volsz = 0; + + DBG(BTRFS, ul_debug("lookup for btrfs FS root")); + *root = NULL; + + if (mnt_fs_get_option(fs, "subvolid", &vol, &volsz) == 0) { + char *target; + struct libmnt_fs *f; + char subvolidstr[sizeof(stringify_value(UINT64_MAX))]; + + DBG(BTRFS, ul_debug(" found subvolid=%s, checking", vol)); + + assert (volsz + 1 < sizeof(stringify_value(UINT64_MAX))); + memcpy(subvolidstr, vol, volsz); + subvolidstr[volsz] = '\0'; + + target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache); + if (!target) + goto err; + + DBG(BTRFS, ul_debug(" trying target=%s subvolid=%s", target, subvolidstr)); + f = mnt_table_find_target_with_option(tb, target, + "subvolid", subvolidstr, + MNT_ITER_BACKWARD); + if (!tb->cache) + free(target); + if (!f) + goto not_found; + + /* Instead of set of BACKREF queries constructing subvol path + * corresponding to a particular subvolid, use the one in + * mountinfo. Kernel keeps subvol path up to date. + */ + if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0) + goto not_found; + + } else if (mnt_fs_get_option(fs, "subvol", &vol, &volsz) != 0) { + /* If fstab entry does not contain "subvol", we have to + * check, whether btrfs has default subvolume defined. + */ + uint64_t default_id; + char *target; + struct libmnt_fs *f; + char default_id_str[sizeof(stringify_value(UINT64_MAX))]; + + DBG(BTRFS, ul_debug(" subvolid/subvol not found, checking default")); + + default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs)); + if (default_id == UINT64_MAX) + goto not_found; + + /* Volume has default subvolume. Check if it matches to + * the one in mountinfo. + * + * Only kernel >= 4.2 reports subvolid. On older + * kernels, there is no reasonable way to detect which + * subvolume was mounted. + */ + target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache); + if (!target) + goto err; + + snprintf(default_id_str, sizeof(default_id_str), "%llu", + (unsigned long long int) default_id); + + DBG(BTRFS, ul_debug(" trying target=%s default subvolid=%s", + target, default_id_str)); + + f = mnt_table_find_target_with_option(tb, target, + "subvolid", default_id_str, + MNT_ITER_BACKWARD); + if (!tb->cache) + free(target); + if (!f) + goto not_found; + + /* Instead of set of BACKREF queries constructing + * subvol path, use the one in mountinfo. Kernel does + * the evaluation for us. + */ + DBG(BTRFS, ul_debug("setting FS root: btrfs default subvolid = %s", + default_id_str)); + + if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0) + goto not_found; + } + + DBG(BTRFS, ul_debug(" using subvol=%s", vol)); + sz = volsz; + if (*vol != '/') + sz++; + *root = malloc(sz + 1); + if (!*root) + goto err; + p = *root; + if (*vol != '/') + *p++ = '/'; + memcpy(p, vol, volsz); + *(*root + sz) = '\0'; + return 0; + +not_found: + DBG(BTRFS, ul_debug(" not found btrfs volume setting")); + return 1; +err: + DBG(BTRFS, ul_debug(" error on btrfs volume setting evaluation")); + return errno ? -errno : -1; +} +#endif /* HAVE_BTRFS_SUPPORT */ + +static const char *get_cifs_unc_subdir_path (const char *unc) +{ + /* + * 1 or more slash: %*[/] + * 1 or more non-slash: %*[^/] + * number of byte read: %n + */ + int share_end = 0; + int r = sscanf(unc, "%*[/]%*[^/]%*[/]%*[^/]%n", &share_end); + if (r == EOF || share_end == 0) + return NULL; + return unc + share_end; +} + +/* + * tb: /proc/self/mountinfo + * fs: filesystem + * mountflags: MS_BIND or 0 + * fsroot: fs-root that will probably be used in the mountinfo file + * for @fs after mount(2) + * + * For btrfs subvolumes this function returns NULL, but @fsroot properly set. + * + * If @tb is NULL then defaults to '/'. + * + * Returns: entry from @tb that will be used as a source for @fs if the @fs is + * bindmount. + * + * Don't export to library API! + */ +struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb, + struct libmnt_fs *fs, + unsigned long mountflags, + char **fsroot) +{ + char *root = NULL; + const char *mnt = NULL; + struct libmnt_fs *src_fs = NULL; + + assert(fs); + assert(fsroot); + + DBG(TAB, ul_debug("lookup fs-root for '%s'", mnt_fs_get_source(fs))); + + if (tb && (mountflags & MS_BIND)) { + const char *src, *src_root; + char *xsrc = NULL; + + DBG(TAB, ul_debug("fs-root for bind")); + + src = xsrc = mnt_resolve_spec(mnt_fs_get_source(fs), tb->cache); + if (src) { + struct libmnt_fs *f = mnt_table_find_mountpoint(tb, + src, MNT_ITER_BACKWARD); + if (f) + mnt = mnt_fs_get_target(f); + } + if (mnt) + root = remove_mountpoint_from_path(src, mnt); + + if (xsrc && !tb->cache) { + free(xsrc); + src = NULL; + } + if (!mnt) + goto err; + + src_fs = mnt_table_find_target(tb, mnt, MNT_ITER_BACKWARD); + if (!src_fs) { + DBG(TAB, ul_debug("not found '%s' in mountinfo -- using default", mnt)); + goto dflt; + } + + /* It's possible that fstab_fs source is subdirectory on btrfs + * subvolume or another bind mount. For example: + * + * /dev/sdc /mnt/test btrfs subvol=/anydir + * /dev/sdc /mnt/test btrfs defaults + * /mnt/test/foo /mnt/test2 auto bind + * + * in this case, the root for /mnt/test2 will be /anydir/foo on + * /dev/sdc. It means we have to compose the final root from + * root and src_root. + */ + src_root = mnt_fs_get_root(src_fs); + + DBG(FS, ul_debugobj(fs, "source root: %s, source FS root: %s", root, src_root)); + + if (src_root && root && !startswith(root, src_root)) { + if (strcmp(root, "/") == 0) { + free(root); + root = strdup(src_root); + if (!root) + goto err; + } else { + char *tmp; + if (asprintf(&tmp, "%s%s", src_root, root) < 0) + goto err; + free(root); + root = tmp; + } + } + } + +#ifdef HAVE_BTRFS_SUPPORT + /* + * btrfs-subvolume mount -- get subvolume name and use it as a root-fs path + */ + else if (tb && fs->fstype && + (!strcmp(fs->fstype, "btrfs") || !strcmp(fs->fstype, "auto"))) { + if (get_btrfs_fs_root(tb, fs, &root) < 0) + goto err; + } +#endif /* HAVE_BTRFS_SUPPORT */ + +dflt: + if (!root) { + root = strdup("/"); + if (!root) + goto err; + } + *fsroot = root; + + DBG(TAB, ul_debug("FS root result: %s", root)); + + return src_fs; +err: + free(root); + return NULL; +} + + +int __mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs, + const char *tgt_prefix) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + + char *root = NULL; + char *src2 = NULL; + const char *src = NULL, *tgt = NULL; + char *xtgt = NULL, *tgt_buf = NULL; + int rc = 0; + dev_t devno = 0; + + DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: target=%s, source=%s", + mnt_fs_get_target(fstab_fs), + mnt_fs_get_source(fstab_fs))); + + if (mnt_fs_is_swaparea(fstab_fs) || mnt_table_is_empty(tb)) { + DBG(FS, ul_debugobj(fstab_fs, "- ignore (swap or no data)")); + return 0; + } + + if (is_mountinfo(tb)) { + /* @tb is mountinfo, so we can try to use fs-roots */ + struct libmnt_fs *rootfs; + int flags = 0; + + if (mnt_fs_get_option(fstab_fs, "bind", NULL, NULL) == 0 || + mnt_fs_get_option(fstab_fs, "rbind", NULL, NULL) == 0) + flags = MS_BIND; + + rootfs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root); + if (rootfs) { + const char *fstype = mnt_fs_get_fstype(rootfs); + + src = mnt_fs_get_srcpath(rootfs); + if (fstype && strncmp(fstype, "nfs", 3) == 0 && root) { + /* NFS stores the root at the end of the source */ + src = src2 = strconcat(src, root); + free(root); + root = NULL; + } + } + } + + if (!src) + src = mnt_fs_get_source(fstab_fs); + + if (src && tb->cache && !mnt_fs_is_pseudofs(fstab_fs)) + src = mnt_resolve_spec(src, tb->cache); + + if (src && root) { + struct stat st; + + devno = mnt_fs_get_devno(fstab_fs); + if (!devno && mnt_safe_stat(src, &st) == 0 && S_ISBLK(st.st_mode)) + devno = st.st_rdev; + } + + tgt = mnt_fs_get_target(fstab_fs); + + if (!tgt || !src) { + DBG(FS, ul_debugobj(fstab_fs, "- ignore (no source/target)")); + goto done; + } + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: src=%s, tgt=%s, root=%s", src, tgt, root)); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + + int eq = mnt_fs_streq_srcpath(fs, src); + + if (!eq && devno && mnt_fs_get_devno(fs) == devno) + eq = 1; + + if (!eq) { + /* The source does not match. Maybe the source is a loop + * device backing file. + */ + uint64_t offset = 0; + char *val; + size_t len; + int flags = 0; + + if (!mnt_fs_get_srcpath(fs) || + !startswith(mnt_fs_get_srcpath(fs), "/dev/loop")) + continue; /* does not look like loopdev */ + + if (mnt_fs_get_option(fstab_fs, "offset", &val, &len) == 0) { + if (mnt_parse_offset(val, len, &offset)) { + DBG(FS, ul_debugobj(fstab_fs, "failed to parse offset=")); + continue; + } + flags = LOOPDEV_FL_OFFSET; + } + + DBG(FS, ul_debugobj(fs, "checking for loop: src=%s", mnt_fs_get_srcpath(fs))); +#if __linux__ + if (!loopdev_is_used(mnt_fs_get_srcpath(fs), src, offset, 0, flags)) + continue; + + DBG(FS, ul_debugobj(fs, "used loop")); +#endif + } + + if (root) { + const char *fstype = mnt_fs_get_fstype(fs); + + if (fstype && (strcmp(fstype, "cifs") == 0 || + strcmp(fstype, "smb3") == 0)) { + + const char *sub = get_cifs_unc_subdir_path(src); + const char *r = mnt_fs_get_root(fs); + + if (!sub || !r || (!streq_paths(sub, r) && + !streq_paths("/", r))) + continue; + } else { + const char *r = mnt_fs_get_root(fs); + if (!r || strcmp(r, root) != 0) + continue; + } + } + + /* + * Compare target, try to minimize the number of situations when we + * need to canonicalize the path to avoid readlink() on + * mountpoints. + */ + if (!xtgt) { + if (tgt_prefix) { + const char *p = *tgt == '/' ? tgt + 1 : tgt; + if (!*p) + tgt = tgt_prefix; /* target is '/' */ + else { + if (asprintf(&tgt_buf, "%s/%s", tgt_prefix, p) <= 0) { + rc = -ENOMEM; + goto done; + } + tgt = tgt_buf; + } + } + + if (mnt_fs_streq_target(fs, tgt)) + break; + if (tb->cache) + xtgt = mnt_resolve_path(tgt, tb->cache); + } + if (xtgt && mnt_fs_streq_target(fs, xtgt)) + break; + } + + if (fs) + rc = 1; /* success */ +done: + free(root); + free(tgt_buf); + + DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc)); + free(src2); + return rc; +} + +/** + * mnt_table_is_fs_mounted: + * @tb: /proc/self/mountinfo file + * @fstab_fs: /etc/fstab entry + * + * Checks if the @fstab_fs entry is already in the @tb table. The "swap" is + * ignored. This function explicitly compares the source, target and root of the + * filesystems. + * + * Note that source and target are canonicalized only if a cache for @tb is + * defined (see mnt_table_set_cache()). The target canonicalization may + * trigger automount on autofs mountpoints! + * + * Don't use it if you want to know if a device is mounted, just use + * mnt_table_find_source() on the device. + * + * This function is designed mostly for "mount -a". + * + * Returns: 0 or 1 + */ +int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs) +{ + return __mnt_table_is_fs_mounted(tb, fstab_fs, NULL); +} + + +#ifdef TEST_PROGRAM +#include "pathnames.h" + +static int parser_errcb(struct libmnt_table *tb, const char *filename, int line) +{ + fprintf(stderr, "%s:%d: parse error\n", filename, line); + + return 1; /* all errors are recoverable -- this is the default */ +} + +static struct libmnt_table *create_table(const char *file, int comments) +{ + struct libmnt_table *tb; + + if (!file) + return NULL; + tb = mnt_new_table(); + if (!tb) + goto err; + + mnt_table_enable_comments(tb, comments); + mnt_table_set_parser_errcb(tb, parser_errcb); + + if (mnt_table_parse_file(tb, file) != 0) + goto err; + return tb; +err: + fprintf(stderr, "%s: parsing failed\n", file); + mnt_unref_table(tb); + return NULL; +} + +static int test_copy_fs(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + struct libmnt_fs *fs; + int rc = -1; + + tb = create_table(argv[1], FALSE); + if (!tb) + return -1; + + fs = mnt_table_find_target(tb, "/", MNT_ITER_FORWARD); + if (!fs) + goto done; + + printf("ORIGINAL:\n"); + mnt_fs_print_debug(fs, stdout); + + fs = mnt_copy_fs(NULL, fs); + if (!fs) + goto done; + + printf("COPY:\n"); + mnt_fs_print_debug(fs, stdout); + mnt_unref_fs(fs); + rc = 0; +done: + mnt_unref_table(tb); + return rc; +} + +static int test_parse(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb = NULL; + struct libmnt_iter *itr = NULL; + struct libmnt_fs *fs; + int rc = -1; + int parse_comments = FALSE; + + if (argc == 3 && !strcmp(argv[2], "--comments")) + parse_comments = TRUE; + + tb = create_table(argv[1], parse_comments); + if (!tb) + return -1; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto done; + + if (mnt_table_get_intro_comment(tb)) + fprintf(stdout, "Initial comment:\n\"%s\"\n", + mnt_table_get_intro_comment(tb)); + + while(mnt_table_next_fs(tb, itr, &fs) == 0) + mnt_fs_print_debug(fs, stdout); + + if (mnt_table_get_trailing_comment(tb)) + fprintf(stdout, "Trailing comment:\n\"%s\"\n", + mnt_table_get_trailing_comment(tb)); + rc = 0; +done: + mnt_free_iter(itr); + mnt_unref_table(tb); + return rc; +} + +static int test_find_idx(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + struct libmnt_fs *fs = NULL; + struct libmnt_cache *mpc = NULL; + const char *file, *what; + int rc = -1; + + if (argc != 3) { + fprintf(stderr, "try --help\n"); + return -EINVAL; + } + + file = argv[1], what = argv[2]; + + tb = create_table(file, FALSE); + if (!tb) + goto done; + + /* create a cache for canonicalized paths */ + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + fs = mnt_table_find_target(tb, what, MNT_ITER_BACKWARD); + + if (!fs) + fprintf(stderr, "%s: not found '%s'\n", file, what); + else { + int idx = mnt_table_find_fs(tb, fs); + + if (idx < 1) + fprintf(stderr, "%s: not found '%s' fs pointer", file, what); + else { + printf("%s index is %d\n", what, idx); + rc = 0; + } + } +done: + mnt_unref_table(tb); + return rc; +} + +static int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr) +{ + struct libmnt_table *tb; + struct libmnt_fs *fs = NULL; + struct libmnt_cache *mpc = NULL; + const char *file, *find, *what; + int rc = -1; + + if (argc != 4) { + fprintf(stderr, "try --help\n"); + return -EINVAL; + } + + file = argv[1], find = argv[2], what = argv[3]; + + tb = create_table(file, FALSE); + if (!tb) + goto done; + + /* create a cache for canonicalized paths */ + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + if (strcasecmp(find, "source") == 0) + fs = mnt_table_find_source(tb, what, dr); + else if (strcasecmp(find, "target") == 0) + fs = mnt_table_find_target(tb, what, dr); + + if (!fs) + fprintf(stderr, "%s: not found %s '%s'\n", file, find, what); + else { + mnt_fs_print_debug(fs, stdout); + rc = 0; + } +done: + mnt_unref_table(tb); + return rc; +} + +static int test_find_bw(struct libmnt_test *ts, int argc, char *argv[]) +{ + return test_find(ts, argc, argv, MNT_ITER_BACKWARD); +} + +static int test_find_fw(struct libmnt_test *ts, int argc, char *argv[]) +{ + return test_find(ts, argc, argv, MNT_ITER_FORWARD); +} + +static int test_find_pair(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + struct libmnt_fs *fs; + struct libmnt_cache *mpc = NULL; + int rc = -1; + + tb = create_table(argv[1], FALSE); + if (!tb) + return -1; + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + fs = mnt_table_find_pair(tb, argv[2], argv[3], MNT_ITER_FORWARD); + if (!fs) + goto done; + + mnt_fs_print_debug(fs, stdout); + rc = 0; +done: + mnt_unref_table(tb); + return rc; +} + +static int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + struct libmnt_fs *fs; + struct libmnt_cache *mpc = NULL; + int rc = -1; + + tb = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); + if (!tb) + return -1; + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + fs = mnt_table_find_mountpoint(tb, argv[1], MNT_ITER_BACKWARD); + if (!fs) + goto done; + + mnt_fs_print_debug(fs, stdout); + rc = 0; +done: + mnt_unref_table(tb); + return rc; +} + +static int test_is_mounted(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb = NULL, *fstab = NULL; + struct libmnt_fs *fs; + struct libmnt_iter *itr = NULL; + struct libmnt_cache *mpc = NULL; + + tb = mnt_new_table_from_file("/proc/self/mountinfo"); + if (!tb) { + fprintf(stderr, "failed to parse mountinfo\n"); + return -1; + } + + fstab = create_table(argv[1], FALSE); + if (!fstab) + goto done; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto done; + + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + while (mnt_table_next_fs(fstab, itr, &fs) == 0) { + if (mnt_table_is_fs_mounted(tb, fs)) + printf("%s already mounted on %s\n", + mnt_fs_get_source(fs), + mnt_fs_get_target(fs)); + else + printf("%s not mounted on %s\n", + mnt_fs_get_source(fs), + mnt_fs_get_target(fs)); + } + +done: + mnt_unref_table(tb); + mnt_unref_table(fstab); + mnt_free_iter(itr); + return 0; +} + +/* returns 0 if @a and @b targets are the same */ +static int test_uniq_cmp(struct libmnt_table *tb __attribute__((__unused__)), + struct libmnt_fs *a, + struct libmnt_fs *b) +{ + assert(a); + assert(b); + + return mnt_fs_streq_target(a, mnt_fs_get_target(b)) ? 0 : 1; +} + +static int test_uniq(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + int rc = -1; + + if (argc != 2) { + fprintf(stderr, "try --help\n"); + return -EINVAL; + } + + tb = create_table(argv[1], FALSE); + if (!tb) + goto done; + + if (mnt_table_uniq_fs(tb, 0, test_uniq_cmp) == 0) { + struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD); + struct libmnt_fs *fs; + if (!itr) + goto done; + while (mnt_table_next_fs(tb, itr, &fs) == 0) + mnt_fs_print_debug(fs, stdout); + mnt_free_iter(itr); + rc = 0; + } +done: + mnt_unref_table(tb); + return rc; +} + + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--parse", test_parse, "<file> [--comments] parse and print tab" }, + { "--find-forward", test_find_fw, "<file> <source|target> <string>" }, + { "--find-backward", test_find_bw, "<file> <source|target> <string>" }, + { "--uniq-target", test_uniq, "<file>" }, + { "--find-pair", test_find_pair, "<file> <source> <target>" }, + { "--find-fs", test_find_idx, "<file> <target>" }, + { "--find-mountpoint", test_find_mountpoint, "<path>" }, + { "--copy-fs", test_copy_fs, "<file> copy root FS from the file" }, + { "--is-mounted", test_is_mounted, "<fstab> check what from fstab is already mounted" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ |