/*
 * Copyright (C) 2008-2010 Karel Zak <kzak@redhat.com>
 *
 * This file may be redistributed under the terms of the
 * GNU Lesser General Public License.
 */

/**
 * 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 fstab with two entries:
 * <informalexample>
 *   <programlisting>
 *	LABEL=foo	/foo	auto   rw
 *	/dev/foo	/foo	auto   rw
 *  </programlisting>
 * </informalexample>
 *
 * where both lines are used for the *same* device, then
 * <informalexample>
 *  <programlisting>
 *	mnt_table_find_source(tb, "/dev/foo", &fs);
 *  </programlisting>
 * </informalexample>
 * will returns the second line, and
 * <informalexample>
 *  <programlisting>
 *	mnt_table_find_source(tb, "LABEL=foo", &fs);
 *  </programlisting>
 * </informalexample>
 * will returns the first entry, and
 * <informalexample>
 *  <programlisting>
 *	mnt_table_find_source(tb, "UUID=anyuuid", &fs);
 *  </programlisting>
 * </informalexample>
 * will returns the first entry (if UUID matches with the device).
 */
#include <blkid.h>

#include "mountP.h"
#include "strutils.h"
#include "loopdev.h"

/**
 * 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, mnt_debug_h(tb, "alloc"));

	INIT_LIST_HEAD(&tb->ents);
	return tb;
}

/**
 * mnt_reset_table:
 * @tb: tab pointer
 *
 * Dealocates all entries (filesystems) from the table
 *
 * 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, mnt_debug_h(tb, "reset"));

	while (!list_empty(&tb->ents)) {
		struct libmnt_fs *fs = list_entry(tb->ents.next,
				                  struct libmnt_fs, ents);
		mnt_free_fs(fs);
	}

	tb->nents = 0;
	return 0;
}

/**
 * mnt_free_table:
 * @tb: tab pointer
 *
 * Deallocates tab struct and all entries.
 */
void mnt_free_table(struct libmnt_table *tb)
{
	if (!tb)
		return;

	mnt_reset_table(tb);

	DBG(TAB, mnt_debug_h(tb, "free"));
	free(tb);
}

/**
 * mnt_table_get_nents:
 * @tb: pointer to tab
 *
 * Returns: number of valid entries in tab.
 */
int mnt_table_get_nents(struct libmnt_table *tb)
{
	assert(tb);
	return tb ? tb->nents : 0;
}

/**
 * mnt_table_set_cache:
 * @tb: pointer to tab
 * @mpc: pointer to struct libmnt_cache instance
 *
 * Setups 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.
 *
 * 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;
	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)
{
	assert(tb);
	return tb ? tb->cache : NULL;
}

/**
 * mnt_table_add_fs:
 * @tb: tab pointer
 * @fs: new entry
 *
 * Adds a new entry to tab.
 *
 * 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;

	list_add_tail(&fs->ents, &tb->ents);

	DBG(TAB, mnt_debug_h(tb, "add entry: %s %s",
			mnt_fs_get_source(fs), mnt_fs_get_target(fs)));
	tb->nents++;
	return 0;
}

/**
 * mnt_table_remove_fs:
 * @tb: tab pointer
 * @fs: new entry
 *
 * 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);
	tb->nents--;
	return 0;
}

/**
 * mnt_table_get_root_fs:
 * @tb: mountinfo file (/proc/self/mountinfo)
 * @root: returns pointer to the root filesystem (/)
 *
 * The function uses parent ID from mountinfo file to determine root filesystem
 * (the filesystem with the smallest ID). The function is designed mostly for
 * applications where is necessary to sort mountpoints by IDs to get the tree
 * of the mountpoints (e.g. findmnt default output).
 *
 * If you're not sure than use
 *
 *	mnt_table_find_target(tb, "/", MNT_ITER_BACKWARD);
 *
 * this is more robust and usable for arbitrary tab file (including fstab).
 *
 * Returns: 0 on success or less then zero 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;

	assert(tb);
	assert(root);

	if (!tb || !root)
		return -EINVAL;

	DBG(TAB, mnt_debug_h(tb, "lookup root fs"));

	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 mountinfo file? */

		if (!*root || id < root_id) {
			*root = fs;
			root_id = id;
		}
	}

	return root_id ? 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 how was mounted (according to
 * IDs in /proc/self/mountinfo).
 *
 * Returns: 0 on success, negative number in case of error or 1 at 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)
		return -EINVAL;

	DBG(TAB, mnt_debug_h(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) {
		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 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_id)
		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 end of list.
 *
 * Example:
 * <informalexample>
 *   <programlisting>
 *	while(mnt_table_next_fs(tb, itr, &fs) == 0) {
 *		const char *dir = mnt_fs_get_target(fs);
 *		printf("mount point: %s\n", dir);
 *	}
 *	mnt_free_table(fi);
 *   </programlisting>
 * </informalexample>
 *
 * lists all mountpoints from fstab in backward order.
 */
int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs **fs)
{
	int rc = 1;

	assert(tb);
	assert(itr);
	assert(fs);

	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_find_next_fs:
 * @tb: table
 * @itr: iterator
 * @match_func: function returns 1 or 0
 * @userdata: extra data for match_func
 * @fs: returns pointer to the next matching table entry
 *
 * This function allows search 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, mnt_debug_h(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;
}

/**
 * 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)
{
	assert(tb);
	assert(itr);
	assert(fs);

	if (!tb || !itr || !fs)
		return -EINVAL;

	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 like mnt_get_mountpoint(), but this function does not rely on
 * st_dev numbers.
 *
 * Returns: a tab entry or NULL.
 */
struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
					    const char *path,
					    int direction)
{
	char *mnt;

	if (!tb || !path || !*path)
		return NULL;
	if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
		return NULL;

	DBG(TAB, mnt_debug_h(tb, "lookup MOUNTPOINT: '%s'", path));

	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 || !*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 given tab, possible are three iterations, first
 * with @path, second with realpath(@path) and third with realpath(@path)
 * against realpath(fs->target). The 2nd and 3rd iterations are 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_target(struct libmnt_table *tb, const char *path, int direction)
{
	struct libmnt_iter itr;
	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));

	/* 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;
	}
	if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
		return NULL;

	DBG(TAB, mnt_debug_h(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-canonicaled path in struct libmnt_table
	 * -- note that mountpoint in /proc/self/mountinfo is already
	 *    canonicalized by 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_path(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 given tab, possible are four iterations, first
 * with @path, second with realpath(@path), third with tags (LABEL, UUID, ..)
 * from @path and fourth with realpath(@path) against realpath(entry->srcpath).
 *
 * The 2nd, 3rd and 4th iterations are not performed when @tb cache is not
 * set (see mnt_table_set_cache()).
 *
 * 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;
	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));

	/* native paths */
	mnt_reset_iter(&itr, direction);
	while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
		if (mnt_fs_streq_srcpath(fs, path))
			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, mnt_debug_h(tb, "lookup canonical SRCPATH: '%s'", cn));

	/* canonicalized paths in struct libmnt_table */
	if (ntags < mnt_table_get_nents(tb)) {
		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 unaccessible, try evaluate all TAGs in @tb
			 * by udev symlinks -- this could be expensive on systems
			 * with 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 <= mnt_table_get_nents(tb)) {
		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 given tab, first attempt is lookup by @tag and
 * @val, for the second attempt the tag is evaluated (converted to the device
 * name) and mnt_table_find_srcpath() is preformed. 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;

	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));

	/* 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_source:
 * @tb: tab pointer
 * @source: TAG or path
 * @direction: MNT_ITER_{FORWARD,BACKWARD}
 *
 * This is high-level API for mnt_table_find_{srcpath,tag}. You needn't to care
 * about @source format (device, LABEL, UUID, ...). This function parses @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 = 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));

	if (source && *source && strchr(source, '=')) {
		char *tag, *val;

		if (blkid_parse_tag_string(source, &tag, &val) == 0) {

			fs = mnt_table_find_tag(tb, tag, val, direction);

			free(tag);
			free(val);
		}
	} else
		fs = mnt_table_find_srcpath(tb, source, direction);

	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 that
 * 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;

	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));

	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 valid device number for 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;

	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));

	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;
}

/*
 * tb: /proc/self/mountinfo
 * fs: filesystem
 * mountflags: MS_BIND or 0
 * fsroot: fs-root that will be probably used in the mountinfo file
 *          for @fs after mount(2)
 *
 * For btrfs subvolumes this function returns NULL, but @fsroot properly set.
 *
 * 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, *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)));

	fstype = mnt_fs_get_fstype(fs);

	if (tb && (mountflags & MS_BIND)) {
		const char *src, *src_root;
		char *xsrc = NULL;

		DBG(TAB, mnt_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 (mnt)
			root = mnt_get_fs_root(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, mnt_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
		 */
		src_root = mnt_fs_get_root(src_fs);
		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;
		}
	}

	/*
	 * 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)
			goto err;
		p = root;
		if (*vol != '/')
			*p++ = '/';
		memcpy(p, vol, volsz);
		*(root + sz) = '\0';
	}
dflt:
	if (!root) {
		root = strdup("/");
		if (!root)
			goto err;
	}
	*fsroot = root;

	DBG(TAB, mnt_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_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 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
 * triggers automount on autofs mountpoints!
 *
 * Don't use it if you want to know if a device is mounted, just use
 * mnt_table_find_source() for 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;
	const char *src = NULL, *tgt = NULL;
	char *xtgt = NULL;
	int rc = 0;

	assert(tb);
	assert(fstab_fs);

	DBG(FS, mnt_debug_h(fstab_fs, "is FS mounted? [target=%s]",
				mnt_fs_get_target(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)"));
		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)
			flags = MS_BIND;

		rootfs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root);
		if (rootfs)
			src = mnt_fs_get_srcpath(rootfs);
	}

	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);

	tgt = mnt_fs_get_target(fstab_fs);

	if (!tgt || !src) {
		DBG(FS, mnt_debug_h(fstab_fs, "- ignore (no source/target)"));
		goto done;
	}
	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_table_next_fs(tb, &itr, &fs) == 0) {

		if (!mnt_fs_streq_srcpath(fs, src)) {
			/* 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;

			if (!mnt_fs_is_kernel(fs) ||
			    !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
				flags = LOOPDEV_FL_OFFSET;

			if (loopdev_is_used(mnt_fs_get_srcpath(fs), src, offset, flags))
				break;
		}

		if (root) {
			const char *r = mnt_fs_get_root(fs);
			if (!r || strcmp(r, root) != 0)
				continue;
		}

		/*
		 * Compare target, try to minimize 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, mnt_debug_h(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc));
	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 default */
}

struct libmnt_table *create_table(const char *file)
{
	struct libmnt_table *tb;

	if (!file)
		return NULL;
	tb = mnt_new_table();
	if (!tb)
		goto err;

	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_free_table(tb);
	return NULL;
}

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]);
	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_free_fs(fs);
	rc = 0;
done:
	mnt_free_table(tb);
	return rc;
}

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;

	tb = create_table(argv[1]);
	if (!tb)
		return -1;

	itr = mnt_new_iter(MNT_ITER_FORWARD);
	if (!itr)
		goto done;

	while(mnt_table_next_fs(tb, itr, &fs) == 0)
		mnt_fs_print_debug(fs, stdout);
	rc = 0;
done:
	mnt_free_iter(itr);
	mnt_free_table(tb);
	return rc;
}

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);
	if (!tb)
		goto done;

	/* create a cache for canonicalized paths */
	mpc = mnt_new_cache();
	if (!mpc)
		goto done;
	mnt_table_set_cache(tb, 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_free_table(tb);
	mnt_free_cache(mpc);
	return rc;
}

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[])
{
	return test_find(ts, argc, argv, MNT_ITER_FORWARD);
}

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]);
	if (!tb)
		return -1;
	mpc = mnt_new_cache();
	if (!mpc)
		goto done;
	mnt_table_set_cache(tb, 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_free_table(tb);
	mnt_free_cache(mpc);
	return rc;
}

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);

	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_free_table(tb);
	mnt_free_cache(mpc);
	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 rc;

	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]);
	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);

	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));
	}

	rc = 0;
done:
	mnt_free_table(tb);
	mnt_free_table(fstab);
	mnt_free_cache(mpc);
	mnt_free_iter(itr);
	return rc;
}

int main(int argc, char *argv[])
{
	struct libmnt_test tss[] = {
	{ "--parse",    test_parse,        "<file>  parse and print tab" },
	{ "--find-forward",  test_find_fw, "<file> <source|target> <string>" },
	{ "--find-backward", test_find_bw, "<file> <source|target> <string>" },
	{ "--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" },
	{ NULL }
	};

	return mnt_run_test(tss, argc, argv);
}

#endif /* TEST_PROGRAM */
