/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * This file is part of libmount from util-linux project. * * Copyright (C) 2008-2018 Karel Zak * * 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: * * * LABEL=foo /foo auto rw * /dev/foo /foo auto rw * * * * where both lines are used for the *same* device, then * * * mnt_table_find_source(tb, "/dev/foo", &fs); * * * will returns the second line, and * * * mnt_table_find_source(tb, "LABEL=foo", &fs); * * * will returns the first entry, and * * * mnt_table_find_source(tb, "UUID=anyuuid", &fs); * * * will return the first entry (if UUID matches with the device). */ #include #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(). * * * * # * # 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 * * */ 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 append_string(&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 append_string(&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 inncrements 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: 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; int root_id = 0; if (!tb || !root || !is_mountinfo(tb)) return -EINVAL; DBG(TAB, ul_debugobj(tb, "lookup root fs")); *root = NULL; /* 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 || id < root_id) { *root = fs; root_id = id; } } /* go to the root node by "parent_id -> id" relation */ while (*root) { struct libmnt_fs *x = get_parent_fs(tb, *root); if (!x || x == *root) break; DBG(TAB, ul_debugobj(tb, " messy mountinfo, walk to %s", mnt_fs_get_target(x))); *root = x; } return *root ? 0 : -EINVAL; } /** * mnt_table_next_child_fs: * @tb: mountinfo file (/proc/self/mountinfo) * @itr: iterator * @parent: parental FS * @chld: returns the next child filesystem * * Note that filesystems are returned in the order of mounting (according to * IDs in /proc/self/mountinfo). * * 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; int parent_id, lastchld_id = 0, chld_id = 0; 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); /* get ID of the previously returned child */ if (itr->head && itr->p != itr->head) { MNT_ITER_ITERATE(itr, fs, struct libmnt_fs, ents); lastchld_id = mnt_fs_get_id(fs); } *chld = NULL; mnt_reset_iter(itr, MNT_ITER_FORWARD); 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 ((!lastchld_id || id > lastchld_id) && (!*chld || id < chld_id)) { *chld = fs; chld_id = id; } } if (!*chld) return 1; /* end of iterator */ /* set the iterator to the @chld for the next call */ mnt_table_set_iter(tb, itr, *chld); return 0; } /** * mnt_table_next_fs: * @tb: tab pointer * @itr: iterator * @fs: returns the next tab entry * * Returns: 0 on success, negative number in case of error or 1 at the end of list. * * Example: * * * while(mnt_table_next_fs(tb, itr, &fs) == 0) { * const char *dir = mnt_fs_get_target(fs); * printf("mount point: %s\n", dir); * } * * * * 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 || !fs) return -EINVAL; *fs = NULL; if (!itr->head) MNT_ITER_INIT(itr, &tb->ents); if (itr->p != itr->head) { MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents); rc = 0; } return rc; } /** * mnt_table_first_fs: * @tb: tab pointer * @fs: 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 || !fs) return -EINVAL; if (list_empty(&tb->ents)) return 1; *fs = list_first_entry(&tb->ents, struct libmnt_fs, ents); return 0; } /** * mnt_table_last_fs: * @tb: tab pointer * @fs: 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 || !fs) return -EINVAL; if (list_empty(&tb->ents)) return 1; *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: 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) { if (!tb || !itr || !fs || !match_func) return -EINVAL; DBG(TAB, ul_debugobj(tb, "lookup next fs")); if (!itr->head) MNT_ITER_INIT(itr, &tb->ents); do { if (itr->p != itr->head) MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents); else break; /* end */ if (match_func(*fs, userdata)) return 0; } while(1); *fs = NULL; return 1; } 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; struct stat st; 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_stat_mountpoint(path, &st)) 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 && !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; } /** * 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) { struct libmnt_iter itr; struct libmnt_fs *fs; char *root = NULL; char *src2 = NULL; const char *src = NULL, *tgt = NULL; char *xtgt = 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 = strappend(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 && 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) { const char *unc_subdir = get_cifs_unc_subdir_path(src); const char *path_on_fs = mnt_fs_get_root(fs); if (!unc_subdir || !path_on_fs || !streq_paths(unc_subdir, path_on_fs)) 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 (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); DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc)); free(src2); return rc; } #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; int writable = 0; const char *path = NULL; if (mnt_has_regular_mtab(&path, &writable) == 1 && writable == 0) tb = mnt_new_table_from_file(path); else 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, " [--comments] parse and print tab" }, { "--find-forward", test_find_fw, " " }, { "--find-backward", test_find_bw, " " }, { "--uniq-target", test_uniq, "" }, { "--find-pair", test_find_pair, " " }, { "--find-fs", test_find_idx, " " }, { "--find-mountpoint", test_find_mountpoint, "" }, { "--copy-fs", test_copy_fs, " copy root FS from the file" }, { "--is-mounted", test_is_mounted, " check what from fstab is already mounted" }, { NULL } }; return mnt_run_test(tss, argc, argv); } #endif /* TEST_PROGRAM */