#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:
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;
}
* 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.
*/
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)
{
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);
* 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
* # 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;
}
*/
const char *mnt_table_get_intro_comment(struct libmnt_table *tb)
{
- assert(tb);
return tb ? tb->comm_intro : NULL;
}
*/
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);
}
/**
*/
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);
*/
const char *mnt_table_get_trailing_comment(struct libmnt_table *tb)
{
- assert(tb);
return tb ? tb->comm_tail : NULL;
}
*/
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);
}
/**
*/
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);
* 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;
}
*/
struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb)
{
- assert(tb);
return tb ? tb->cache : NULL;
}
* @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;
}
* @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;
}
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;
}
}
- return root_id ? 0 : -EINVAL;
+ return *root ? 0 : -EINVAL;
}
/**
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) {
}
}
- if (!chld_id)
+ if (!*chld)
return 1; /* end of iterator */
/* set the iterator to the @chld for the next call */
* const char *dir = mnt_fs_get_target(fs);
* printf("mount point: %s\n", dir);
* }
- * mnt_free_table(fi);
* </programlisting>
* </informalexample>
*
{
int rc = 1;
- assert(tb);
- assert(itr);
- assert(fs);
-
if (!tb || !itr || !fs)
return -EINVAL;
*fs = NULL;
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
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);
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
*/
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;
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)
}
p = stripoff_last_component(mnt);
- if (!p || !*p)
+ if (!p)
break;
} while (mnt && *(mnt + 1) != '\0');
*
* 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.
*/
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);
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);
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
*/
|| (*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;
{
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);
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))
}
/* 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))
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);
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
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);
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) {
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);
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
*
* 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.
*
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);
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);
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("/");
}
*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
*
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;
}
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);
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) {
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;
}
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;
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;
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;
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;
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);
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;
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)
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;
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)
mnt_fs_print_debug(fs, stdout);
rc = 0;
done:
- mnt_free_table(tb);
- mnt_free_cache(mpc);
+ mnt_unref_table(tb);
return rc;
}
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;
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),
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 }
};