]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - libmount/src/tab.c
misc: Fix various typos
[thirdparty/util-linux.git] / libmount / src / tab.c
index c79b9819c70a62488ca9ae3e13dc7e26448123f5..155c65ee36a6e2202c0a34a41048da149c3aaeca 100644 (file)
 #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:
@@ -65,8 +81,8 @@ struct libmnt_table *mnt_new_table(void)
        if (!tb)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "alloc"));
-
+       DBG(TAB, ul_debugobj(tb, "alloc"));
+       tb->refcount = 1;
        INIT_LIST_HEAD(&tb->ents);
        return tb;
 }
@@ -75,7 +91,8 @@ struct libmnt_table *mnt_new_table(void)
  * mnt_reset_table:
  * @tb: tab pointer
  *
- * Deallocates all entries (filesystems) from the table.
+ * 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.
  */
@@ -84,23 +101,59 @@ int mnt_reset_table(struct libmnt_table *tb)
        if (!tb)
                return -EINVAL;
 
-       DBG(TAB, mnt_debug_h(tb, "reset"));
+       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_free_fs(fs);
+               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 tab struct and all entries.
+ * Deallocates the table. This function does not care about reference count. Don't
+ * use this function directly -- it's better to use 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)
 {
@@ -108,8 +161,9 @@ void mnt_free_table(struct libmnt_table *tb)
                return;
 
        mnt_reset_table(tb);
+       DBG(TAB, ul_debugobj(tb, "free [refcount=%d]", tb->refcount));
 
-       DBG(TAB, mnt_debug_h(tb, "free"));
+       mnt_unref_cache(tb->cache);
        free(tb->comm_intro);
        free(tb->comm_tail);
        free(tb);
@@ -119,14 +173,53 @@ void mnt_free_table(struct libmnt_table *tb)
  * mnt_table_get_nents:
  * @tb: pointer to tab
  *
- * Returns: number of valid entries in tab.
+ * Returns: number of entries in table.
  */
 int mnt_table_get_nents(struct libmnt_table *tb)
 {
-       assert(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
@@ -149,14 +242,13 @@ int mnt_table_get_nents(struct libmnt_table *tb)
  *     # 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 
+ *     LABEL=bar /mnt/bar auto defaults 1 2
  *     # tailing comment
  *  </programlisting>
  * </informalexample>
  */
 void mnt_table_enable_comments(struct libmnt_table *tb, int enable)
 {
-       assert(tb);
        if (tb)
                tb->comms = enable;
 }
@@ -181,7 +273,6 @@ int mnt_table_with_comments(struct libmnt_table *tb)
  */
 const char *mnt_table_get_intro_comment(struct libmnt_table *tb)
 {
-       assert(tb);
        return tb ? tb->comm_intro : NULL;
 }
 
@@ -196,19 +287,7 @@ const char *mnt_table_get_intro_comment(struct libmnt_table *tb)
  */
 int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm)
 {
-       char *p = NULL;
-
-       assert(tb);
-       if (!tb)
-               return -EINVAL;
-       if (comm) {
-               p = strdup(comm);
-               if (!p)
-                       return -ENOMEM;
-       }
-       free(tb->comm_intro);
-       tb->comm_intro = p;
-       return 0;
+       return strdup_to_struct_member(tb, comm_intro, comm);
 }
 
 /**
@@ -222,7 +301,6 @@ int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm)
  */
 int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm)
 {
-       assert(tb);
        if (!tb)
                return -EINVAL;
        return append_string(&tb->comm_intro, comm);
@@ -236,7 +314,6 @@ int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm)
  */
 const char *mnt_table_get_trailing_comment(struct libmnt_table *tb)
 {
-       assert(tb);
        return tb ? tb->comm_tail : NULL;
 }
 
@@ -251,19 +328,7 @@ const char *mnt_table_get_trailing_comment(struct libmnt_table *tb)
  */
 int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm)
 {
-       char *p = NULL;
-
-       assert(tb);
-       if (!tb)
-               return -EINVAL;
-       if (comm) {
-               p = strdup(comm);
-               if (!p)
-                       return -ENOMEM;
-       }
-       free(tb->comm_tail);
-       tb->comm_tail = p;
-       return 0;
+       return strdup_to_struct_member(tb, comm_tail, comm);
 }
 
 /**
@@ -277,7 +342,6 @@ int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm)
  */
 int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm)
 {
-       assert(tb);
        if (!tb)
                return -EINVAL;
        return append_string(&tb->comm_tail, comm);
@@ -295,15 +359,21 @@ int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm)
  * 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)
 {
-       assert(tb);
        if (!tb)
                return -EINVAL;
+
+       mnt_ref_cache(mpc);                     /* new */
+       mnt_unref_cache(tb->cache);             /* old */
        tb->cache = mpc;
        return 0;
 }
@@ -316,7 +386,6 @@ int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc)
  */
 struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb)
 {
-       assert(tb);
        return tb ? tb->cache : NULL;
 }
 
@@ -325,23 +394,23 @@ struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb)
  * @tb: tab pointer
  * @fs: new entry
  *
- * Adds a new entry to tab.
+ * 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)
 {
-       assert(tb);
-       assert(fs);
-
        if (!tb || !fs)
                return -EINVAL;
 
+       mnt_ref_fs(fs);
        list_add_tail(&fs->ents, &tb->ents);
+       tb->nents++;
 
-       DBG(TAB, mnt_debug_h(tb, "add entry: %s %s",
+       DBG(TAB, ul_debugobj(tb, "add entry: %s %s",
                        mnt_fs_get_source(fs), mnt_fs_get_target(fs)));
-       tb->nents++;
        return 0;
 }
 
@@ -350,16 +419,21 @@ int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *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)
 {
-       assert(tb);
-       assert(fs);
-
        if (!tb || !fs)
                return -EINVAL;
+
        list_del(&fs->ents);
+       INIT_LIST_HEAD(&fs->ents);      /* otherwise FS still points to the list */
+
+       mnt_unref_fs(fs);
        tb->nents--;
        return 0;
 }
@@ -388,19 +462,16 @@ int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root)
        struct libmnt_fs *fs;
        int root_id = 0;
 
-       assert(tb);
-       assert(root);
-
-       if (!tb || !root)
+       if (!tb || !root || !is_mountinfo(tb))
                return -EINVAL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup root fs"));
+       DBG(TAB, ul_debugobj(tb, "lookup root fs"));
+
+       *root = NULL;
 
        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 (!id)
-                       break;          /* @tab is not a mountinfo file? */
 
                if (!*root || id < root_id) {
                        *root = fs;
@@ -408,7 +479,7 @@ int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root)
                }
        }
 
-       return root_id ? 0 : -EINVAL;
+       return *root ? 0 : -EINVAL;
 }
 
 /**
@@ -429,15 +500,13 @@ int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
        struct libmnt_fs *fs;
        int parent_id, lastchld_id = 0, chld_id = 0;
 
-       if (!tb || !itr || !parent)
+       if (!tb || !itr || !parent || !is_mountinfo(tb))
                return -EINVAL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup next child of '%s'",
+       DBG(TAB, ul_debugobj(tb, "lookup next child of '%s'",
                                mnt_fs_get_target(parent)));
 
        parent_id = mnt_fs_get_id(parent);
-       if (!parent_id)
-               return -EINVAL;
 
        /* get ID of the previously returned child */
        if (itr->head && itr->p != itr->head) {
@@ -468,7 +537,7 @@ int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
                }
        }
 
-       if (!chld_id)
+       if (!*chld)
                return 1;       /* end of iterator */
 
        /* set the iterator to the @chld for the next call */
@@ -492,7 +561,6 @@ int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
  *             const char *dir = mnt_fs_get_target(fs);
  *             printf("mount point: %s\n", dir);
  *     }
- *     mnt_free_table(fi);
  *   </programlisting>
  * </informalexample>
  *
@@ -502,10 +570,6 @@ int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct l
 {
        int rc = 1;
 
-       assert(tb);
-       assert(itr);
-       assert(fs);
-
        if (!tb || !itr || !fs)
                return -EINVAL;
        *fs = NULL;
@@ -520,6 +584,40 @@ int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct l
        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
@@ -539,7 +637,7 @@ int mnt_table_find_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
        if (!tb || !itr || !fs || !match_func)
                return -EINVAL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup next fs"));
+       DBG(TAB, ul_debugobj(tb, "lookup next fs"));
 
        if (!itr->head)
                MNT_ITER_INIT(itr, &tb->ents);
@@ -558,6 +656,93 @@ int mnt_table_find_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
        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
@@ -570,10 +755,6 @@ int mnt_table_find_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
  */
 int mnt_table_set_iter(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs *fs)
 {
-       assert(tb);
-       assert(itr);
-       assert(fs);
-
        if (!tb || !itr || !fs)
                return -EINVAL;
 
@@ -605,7 +786,7 @@ struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup MOUNTPOINT: '%s'", path));
+       DBG(TAB, ul_debugobj(tb, "lookup MOUNTPOINT: '%s'", path));
 
        mnt = strdup(path);
        if (!mnt)
@@ -622,7 +803,7 @@ struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
                }
 
                p = stripoff_last_component(mnt);
-               if (!p || !*p)
+               if (!p)
                        break;
        } while (mnt && *(mnt + 1) != '\0');
 
@@ -638,8 +819,10 @@ struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
  *
  * 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()).
+ * 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.
  */
@@ -649,15 +832,12 @@ struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *pat
        struct libmnt_fs *fs = NULL;
        char *cn;
 
-       assert(tb);
-       assert(path);
-
        if (!tb || !path || !*path)
                return NULL;
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup TARGET: '%s'", path));
+       DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s'", path));
 
        /* native @target */
        mnt_reset_iter(&itr, direction);
@@ -665,10 +845,24 @@ struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *pat
                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, mnt_debug_h(tb, "lookup canonical TARGET: '%s'", cn));
+       DBG(TAB, ul_debugobj(tb, "lookup canonical TARGET: '%s'", cn));
 
        /* canonicalized paths in struct libmnt_table */
        mnt_reset_iter(&itr, direction);
@@ -677,7 +871,7 @@ struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *pat
                        return fs;
        }
 
-       /* non-canonicaled path in struct libmnt_table
+       /* non-canonical path in struct libmnt_table
         * -- note that mountpoint in /proc/self/mountinfo is already
         *    canonicalized by the kernel
         */
@@ -691,7 +885,7 @@ struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *pat
                    || (*fs->target == '/' && *(fs->target + 1) == '\0'))
                       continue;
 
-               p = mnt_resolve_path(fs->target, tb->cache);
+               p = mnt_resolve_target(fs->target, tb->cache);
                /* both canonicalized, strcmp() is fine here */
                if (p && strcmp(cn, p) == 0)
                        return fs;
@@ -721,17 +915,16 @@ struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, const char *pa
 {
        struct libmnt_iter itr;
        struct libmnt_fs *fs = NULL;
-       int ntags = 0;
+       int ntags = 0, nents;
        char *cn;
        const char *p;
 
-       assert(tb);
        if (!tb || !path || !*path)
                return NULL;
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup SRCPATH: '%s'", path));
+       DBG(TAB, ul_debugobj(tb, "lookup SRCPATH: '%s'", path));
 
        /* native paths */
        mnt_reset_iter(&itr, direction);
@@ -745,10 +938,12 @@ struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, const char *pa
        if (!path || !tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup canonical SRCPATH: '%s'", cn));
+       DBG(TAB, ul_debugobj(tb, "lookup canonical SRCPATH: '%s'", cn));
+
+       nents = mnt_table_get_nents(tb);
 
        /* canonicalized paths in struct libmnt_table */
-       if (ntags < mnt_table_get_nents(tb)) {
+       if (ntags < nents) {
                mnt_reset_iter(&itr, direction);
                while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
                        if (mnt_fs_streq_srcpath(fs, cn))
@@ -791,7 +986,7 @@ struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, const char *pa
        }
 
        /* non-canonicalized paths in struct libmnt_table */
-       if (ntags <= mnt_table_get_nents(tb)) {
+       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))
@@ -830,16 +1025,12 @@ struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
        struct libmnt_iter itr;
        struct libmnt_fs *fs = NULL;
 
-       assert(tb);
-       assert(tag);
-       assert(val);
-
        if (!tb || !tag || !*tag || !val)
                return NULL;
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup by TAG: %s %s", tag, val));
+       DBG(TAB, ul_debugobj(tb, "lookup by TAG: %s %s", tag, val));
 
        /* look up by TAG */
        mnt_reset_iter(&itr, direction);
@@ -859,6 +1050,50 @@ struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
        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
@@ -877,14 +1112,12 @@ struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb,
        struct libmnt_fs *fs;
        char *t = NULL, *v = NULL;
 
-       assert(tb);
-
        if (!tb)
                return NULL;
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup SOURCE: '%s'", source));
+       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);
@@ -916,15 +1149,12 @@ struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb, const char *sourc
        struct libmnt_fs *fs = NULL;
        struct libmnt_iter itr;
 
-       assert(tb);
-       assert(target);
-
        if (!tb || !target || !*target || !source || !*source)
                return NULL;
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup SOURCE: %s TARGET: %s", source, target));
+       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) {
@@ -954,14 +1184,12 @@ struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb,
        struct libmnt_fs *fs = NULL;
        struct libmnt_iter itr;
 
-       assert(tb);
-
        if (!tb)
                return NULL;
        if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
                return NULL;
 
-       DBG(TAB, mnt_debug_h(tb, "lookup DEVNO: %d", (int) devno));
+       DBG(TAB, ul_debugobj(tb, "lookup DEVNO: %d", (int) devno));
 
        mnt_reset_iter(&itr, direction);
 
@@ -973,6 +1201,134 @@ struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb,
        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 */
+
 /*
  * tb: /proc/self/mountinfo
  * fs: filesystem
@@ -982,6 +1338,8 @@ struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb,
  *
  * 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.
  *
@@ -992,14 +1350,15 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
                                        unsigned long mountflags,
                                        char **fsroot)
 {
-       char *root = NULL, *mnt = NULL;
+       char *root = NULL;
+       const char *mnt = NULL;
        const char *fstype;
        struct libmnt_fs *src_fs = NULL;
 
        assert(fs);
        assert(fsroot);
 
-       DBG(TAB, mnt_debug("lookup fs-root for '%s'", mnt_fs_get_source(fs)));
+       DBG(TAB, ul_debug("lookup fs-root for '%s'", mnt_fs_get_source(fs)));
 
        fstype = mnt_fs_get_fstype(fs);
 
@@ -1007,13 +1366,17 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
                const char *src, *src_root;
                char *xsrc = NULL;
 
-               DBG(TAB, mnt_debug("fs-root for bind"));
+               DBG(TAB, ul_debug("fs-root for bind"));
 
                src = xsrc = mnt_resolve_spec(mnt_fs_get_source(fs), tb->cache);
-               if (src)
-                       mnt = mnt_get_mountpoint(src);
+               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 = mnt_get_fs_root(src, mnt);
+                       root = remove_mountpoint_from_path(src, mnt);
 
                if (xsrc && !tb->cache) {
                        free(xsrc);
@@ -1024,52 +1387,51 @@ struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
 
                src_fs = mnt_table_find_target(tb, mnt, MNT_ITER_BACKWARD);
                if (!src_fs)  {
-                       DBG(TAB, mnt_debug("not found '%s' in mountinfo -- using default", mnt));
+                       DBG(TAB, ul_debug("not found '%s' in mountinfo -- using default", mnt));
                        goto dflt;
                }
 
-               /* on btrfs the subvolume is used as fs-root in
-                * /proc/self/mountinfo, so we have to get the original subvolume
-                * name from src_fs and prepend the subvolume name to the
-                * fs-root path
+               /* 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)) {
-                       size_t sz = strlen(root) + strlen(src_root) + 1;
-                       char *tmp = malloc(sz);
-
-                       if (!tmp)
-                               goto err;
-                       snprintf(tmp, sz, "%s%s", src_root, root);
-                       free(root);
-                       root = tmp;
+                       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 (fstype && !strcmp(fstype, "btrfs")) {
-               char *vol = NULL, *p;
-               size_t sz, volsz = 0;
-
-               if (mnt_fs_get_option(fs, "subvol", &vol, &volsz))
-                       goto dflt;
-
-               DBG(TAB, mnt_debug("setting FS root: btrfs subvol"));
-
-               sz = volsz;
-               if (*vol != '/')
-                       sz++;
-               root = malloc(sz + 1);
-               if (!root)
+       else if (tb && fstype && (!strcmp(fstype, "btrfs") || !strcmp(fstype, "auto"))) {
+               if (get_btrfs_fs_root(tb, fs, &root) < 0)
                        goto err;
-               p = root;
-               if (*vol != '/')
-                       *p++ = '/';
-               memcpy(p, vol, volsz);
-               *(root + sz) = '\0';
        }
+#endif /* HAVE_BTRFS_SUPPORT */
+
 dflt:
        if (!root) {
                root = strdup("/");
@@ -1078,32 +1440,16 @@ dflt:
        }
        *fsroot = root;
 
-       DBG(TAB, mnt_debug("FS root result: %s", root));
+       DBG(TAB, ul_debug("FS root result: %s", root));
 
-       free(mnt);
        return src_fs;
 err:
        free(root);
-       free(mnt);
        return NULL;
 }
 
-static 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_table_is_fs__mounted:
+ * mnt_table_is_fs_mounted:
  * @tb: /proc/self/mountinfo file
  * @fstab_fs: /etc/fstab entry
  *
@@ -1133,14 +1479,12 @@ int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs)
        int rc = 0;
        dev_t devno = 0;
 
-       assert(tb);
-       assert(fstab_fs);
-
-       DBG(FS, mnt_debug_h(fstab_fs, "is FS mounted? [target=%s]",
-                               mnt_fs_get_target(fstab_fs)));
+       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_get_nents(tb) == 0) {
-               DBG(FS, mnt_debug_h(fstab_fs, "- ignore (swap or no data)"));
+       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;
        }
 
@@ -1174,11 +1518,13 @@ int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs)
        tgt = mnt_fs_get_target(fstab_fs);
 
        if (!tgt || !src) {
-               DBG(FS, mnt_debug_h(fstab_fs, "- ignore (no source/target)"));
+               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);
@@ -1193,22 +1539,27 @@ int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs)
                        uint64_t offset = 0;
                        char *val;
                        size_t len;
-                       int flags;
+                       int flags = 0;
 
-                       if (!mnt_fs_is_kernel(fs) ||
-                           !mnt_fs_get_srcpath(fs) ||
+                       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 &&
-                           mnt_parse_offset(val, len, &offset)) {
-                               DBG(FS, mnt_debug_h(fstab_fs, "failed to parse offset="));
-                               continue;
-                       } else
+                       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;
+                       }
 
-                       if (loopdev_is_used(mnt_fs_get_srcpath(fs), src, offset, flags))
-                               break;
+                       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, flags))
+                               continue;
+
+                       DBG(FS, ul_debugobj(fs, "used loop"));
+#endif
                }
 
                if (root) {
@@ -1237,7 +1588,7 @@ int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs)
 done:
        free(root);
 
-       DBG(TAB, mnt_debug_h(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc));
+       DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc));
        return rc;
 }
 
@@ -1251,7 +1602,7 @@ static int parser_errcb(struct libmnt_table *tb, const char *filename, int line)
        return 1;       /* all errors are recoverable -- this is the default */
 }
 
-struct libmnt_table *create_table(const char *file, int comments)
+static struct libmnt_table *create_table(const char *file, int comments)
 {
        struct libmnt_table *tb;
 
@@ -1269,11 +1620,11 @@ struct libmnt_table *create_table(const char *file, int comments)
        return tb;
 err:
        fprintf(stderr, "%s: parsing failed\n", file);
-       mnt_free_table(tb);
+       mnt_unref_table(tb);
        return NULL;
 }
 
-int test_copy_fs(struct libmnt_test *ts, int argc, char *argv[])
+static int test_copy_fs(struct libmnt_test *ts, int argc, char *argv[])
 {
        struct libmnt_table *tb;
        struct libmnt_fs *fs;
@@ -1296,14 +1647,14 @@ int test_copy_fs(struct libmnt_test *ts, int argc, char *argv[])
 
        printf("COPY:\n");
        mnt_fs_print_debug(fs, stdout);
-       mnt_free_fs(fs);
+       mnt_unref_fs(fs);
        rc = 0;
 done:
-       mnt_free_table(tb);
+       mnt_unref_table(tb);
        return rc;
 }
 
-int test_parse(struct libmnt_test *ts, int argc, char *argv[])
+static int test_parse(struct libmnt_test *ts, int argc, char *argv[])
 {
        struct libmnt_table *tb = NULL;
        struct libmnt_iter *itr = NULL;
@@ -1335,11 +1686,11 @@ int test_parse(struct libmnt_test *ts, int argc, char *argv[])
        rc = 0;
 done:
        mnt_free_iter(itr);
-       mnt_free_table(tb);
+       mnt_unref_table(tb);
        return rc;
 }
 
-int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr)
+static int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr)
 {
        struct libmnt_table *tb;
        struct libmnt_fs *fs = NULL;
@@ -1363,6 +1714,7 @@ int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr)
        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);
@@ -1376,22 +1728,21 @@ int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr)
                rc = 0;
        }
 done:
-       mnt_free_table(tb);
-       mnt_free_cache(mpc);
+       mnt_unref_table(tb);
        return rc;
 }
 
-int test_find_bw(struct libmnt_test *ts, int argc, char *argv[])
+static int test_find_bw(struct libmnt_test *ts, int argc, char *argv[])
 {
        return test_find(ts, argc, argv, MNT_ITER_BACKWARD);
 }
 
-int test_find_fw(struct libmnt_test *ts, int argc, char *argv[])
+static int test_find_fw(struct libmnt_test *ts, int argc, char *argv[])
 {
        return test_find(ts, argc, argv, MNT_ITER_FORWARD);
 }
 
-int test_find_pair(struct libmnt_test *ts, int argc, char *argv[])
+static int test_find_pair(struct libmnt_test *ts, int argc, char *argv[])
 {
        struct libmnt_table *tb;
        struct libmnt_fs *fs;
@@ -1405,6 +1756,7 @@ int test_find_pair(struct libmnt_test *ts, int argc, char *argv[])
        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)
@@ -1413,12 +1765,11 @@ int test_find_pair(struct libmnt_test *ts, int argc, char *argv[])
        mnt_fs_print_debug(fs, stdout);
        rc = 0;
 done:
-       mnt_free_table(tb);
-       mnt_free_cache(mpc);
+       mnt_unref_table(tb);
        return rc;
 }
 
-int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
+static int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
 {
        struct libmnt_table *tb;
        struct libmnt_fs *fs;
@@ -1432,6 +1783,7 @@ int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
        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)
@@ -1440,8 +1792,7 @@ int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
        mnt_fs_print_debug(fs, stdout);
        rc = 0;
 done:
-       mnt_free_table(tb);
-       mnt_free_cache(mpc);
+       mnt_unref_table(tb);
        return rc;
 }
 
@@ -1451,9 +1802,14 @@ static int test_is_mounted(struct libmnt_test *ts, int argc, char *argv[])
        struct libmnt_fs *fs;
        struct libmnt_iter *itr = NULL;
        struct libmnt_cache *mpc = NULL;
-       int rc;
+       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");
 
-       tb = mnt_new_table_from_file("/proc/self/mountinfo");
        if (!tb) {
                fprintf(stderr, "failed to parse mountinfo\n");
                return -1;
@@ -1471,8 +1827,9 @@ static int test_is_mounted(struct libmnt_test *ts, int argc, char *argv[])
        if (!mpc)
                goto done;
        mnt_table_set_cache(tb, mpc);
+       mnt_unref_cache(mpc);
 
-       while(mnt_table_next_fs(fstab, itr, &fs) == 0) {
+       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),
@@ -1483,25 +1840,65 @@ static int test_is_mounted(struct libmnt_test *ts, int argc, char *argv[])
                                        mnt_fs_get_target(fs));
        }
 
-       rc = 0;
 done:
-       mnt_free_table(tb);
-       mnt_free_table(fstab);
-       mnt_free_cache(mpc);
+       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-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 <file> are already mounted" },
+       { "--is-mounted",    test_is_mounted, "<fstab> check what from fstab is already mounted" },
        { NULL }
        };