/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * This file is part of libmount from util-linux project.
 *
 * Copyright (C) 2022 Karel Zak <kzak@redhat.com>
 *
 * libmount is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 *
 * The "optlist" is container for parsed mount options.
 *
 */
#include "strutils.h"
#include "list.h"
#include "mountP.h"

#define MNT_OL_MAXMAPS	8

enum libmnt_optsrc {
	MNT_OPTSRC_STRING,
	MNT_OPTSRC_FLAG
};

struct optlist_cache {
	unsigned long flags;
	char *optstr;

	unsigned int flags_ready: 1,
		     optstr_ready : 1;
};

struct libmnt_opt {
	char *name;
	char *value;

	struct list_head opts;	/* libmnt_optlist->opts member */

	const struct libmnt_optmap	*map;
	const struct libmnt_optmap	*ent;	/* map entry */

	enum libmnt_optsrc	src;

	unsigned int external : 1,	/* visible for external helpers only */
		     recursive : 1,	/* recursive flag */
		     sepnodata : 1,	/* value separator, but without data ("name=") */
		     is_linux : 1,	/* defined in ls->linux_map (VFS attr) */
		     quoted : 1;	/* name="value" */
};

struct libmnt_optlist {
	int refcount;
	unsigned int age;			/* incremented after each change */

	const struct libmnt_optmap	*linux_map;	/* map with MS_ flags */
	const struct libmnt_optmap	*maps[MNT_OL_MAXMAPS];
	size_t nmaps;

	struct optlist_cache cache_mapped[MNT_OL_MAXMAPS];	/* cache by map */
	struct optlist_cache cache_all[__MNT_OL_FLTR_COUNT];	/* from all maps, unknown, external, ... */

	unsigned long		propagation;	/* propagation MS_ flags */
	struct list_head	opts;		/* parsed options */

	unsigned int	merged : 1,		/* don't care about MNT_OPTSRC_* */
			is_remount : 1,
			is_bind : 1,
			is_rbind : 1,
			is_rdonly : 1,
			is_move : 1,
			is_silent : 1,
			is_rpropagation : 1;	/* recursive propagation (rbind, rprivate, ...) */
};

struct libmnt_optlist *mnt_new_optlist(void)
{
	struct libmnt_optlist *ls = calloc(1, sizeof(*ls));

	if (!ls)
		return NULL;

	ls->refcount = 1;
	INIT_LIST_HEAD(&ls->opts);

	ls->linux_map = mnt_get_builtin_optmap(MNT_LINUX_MAP);

	DBG(OPTLIST, ul_debugobj(ls, "alloc"));
	return ls;
}

void mnt_ref_optlist(struct libmnt_optlist *ls)
{
	if (ls)
		ls->refcount++;
}

static void reset_cache(struct optlist_cache *cache)
{
	if (!cache || (cache->flags_ready == 0 && cache->optstr_ready == 0))
		return;
	free(cache->optstr);
	memset(cache, 0, sizeof(*cache));
}

void mnt_unref_optlist(struct libmnt_optlist *ls)
{
	size_t i;

	if (!ls)
		return;

	ls->refcount--;
	if (ls->refcount > 0)
		return;

	while (!list_empty(&ls->opts)) {
		struct libmnt_opt *opt = list_entry(ls->opts.next, struct libmnt_opt, opts);
		mnt_optlist_remove_opt(ls, opt);
	}

	for (i = 0; i < ls->nmaps; i++)
		reset_cache(&ls->cache_mapped[i]);

	for (i = 0; i < __MNT_OL_FLTR_COUNT; i++)
		reset_cache(&ls->cache_all[i]);

	free(ls);
}

int mnt_optlist_register_map(struct libmnt_optlist *ls, const struct libmnt_optmap *map)
{
	size_t i;

	if (!ls || !map)
		return -EINVAL;

	for (i = 0; i < ls->nmaps; i++) {
		if (ls->maps[i] == map)
			return 0;		/* already registered, ignore */
	}
	if (ls->nmaps + 1 >= MNT_OL_MAXMAPS)
		return -ERANGE;

	DBG(OPTLIST, ul_debugobj(ls, "register map %p", map));
	ls->maps[ls->nmaps++] = map;
	return 0;
}

static size_t optlist_get_mapidx(struct libmnt_optlist *ls, const struct libmnt_optmap *map)
{
	size_t i;

	assert(ls);
	assert(map);

	for (i = 0; i < ls->nmaps; i++)
		if (map == ls->maps[i])
			return i;

	return (size_t) -1;
}

static void optlist_cleanup_cache(struct libmnt_optlist *ls)
{
	size_t i;

	ls->age++;

	if (list_empty(&ls->opts))
		return;

	for (i = 0; i < ARRAY_SIZE(ls->cache_mapped); i++)
		reset_cache(&ls->cache_mapped[i]);

	for (i = 0; i < __MNT_OL_FLTR_COUNT; i++)
		reset_cache(&ls->cache_all[i]);
}

int mnt_optlist_remove_opt(struct libmnt_optlist *ls, struct libmnt_opt *opt)
{
	if (!opt)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, " remove %s", opt->name));

	if (opt->map && opt->ent && opt->map == ls->linux_map) {
		if (opt->ent->id & MS_PROPAGATION)
			ls->propagation &= ~opt->ent->id;
		else if (opt->ent->id == MS_REMOUNT)
			ls->is_remount = 0;
		else if (opt->ent->id == (MS_BIND|MS_REC))
			ls->is_rbind = 0;
		else if (opt->ent->id == MS_BIND)
			ls->is_bind = 0;
		else if (opt->ent->id == MS_RDONLY)
			ls->is_rdonly = 0;
		else if (opt->ent->id == MS_MOVE)
			ls->is_move = 0;
		else if (opt->ent->id == MS_SILENT)
			ls->is_silent = 0;

		if (opt->ent->id & MS_REC)
			ls->is_rpropagation = 0;
	}

	optlist_cleanup_cache(ls);

	list_del_init(&opt->opts);
	free(opt->value);
	free(opt->name);
	free(opt);

	return 0;
}

int mnt_optlist_remove_named(struct libmnt_optlist *ls, const char *name,
			     const struct libmnt_optmap *map)
{
	struct libmnt_opt *opt = mnt_optlist_get_named(ls, name, map);

	return opt ? mnt_optlist_remove_opt(ls, opt) : 0;
}

int mnt_optlist_next_opt(struct libmnt_optlist *ls,
			struct libmnt_iter *itr, struct libmnt_opt **opt)
{
	int rc = 1;

	if (!ls || !itr)
		return -EINVAL;
	if (opt)
		*opt = NULL;

	if (!itr->head)
		MNT_ITER_INIT(itr, &ls->opts);
	if (itr->p != itr->head) {
		if (opt)
			*opt = MNT_ITER_GET_ENTRY(itr, struct libmnt_opt, opts);
		MNT_ITER_ITERATE(itr);
		rc = 0;
	}

	return rc;
}

struct libmnt_opt *mnt_optlist_get_opt(struct libmnt_optlist *ls,
			unsigned long id, const struct libmnt_optmap *map)
{
	struct libmnt_iter itr;
	struct libmnt_opt *opt;

	if (!ls || !map)
		return NULL;

	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
		if (opt->external)
			continue;
		if (map && opt->map != map)
			continue;
		if (opt->ent && (unsigned long) opt->ent->id == id)
			return opt;
	}

	return NULL;
}

struct libmnt_opt *mnt_optlist_get_named(struct libmnt_optlist *ls,
			const char *name, const struct libmnt_optmap *map)
{
	struct libmnt_iter itr;
	struct libmnt_opt *opt;

	if (!ls || !name)
		return NULL;

	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
		if (opt->external)
			continue;
		if (map && map != opt->map)
			continue;
		if (opt->name && strcmp(opt->name, name) == 0)
			return opt;
	}

	return NULL;
}

static int is_equal_opts(struct libmnt_opt *a, struct libmnt_opt *b)
{
	if (a->map != b->map)
		return 0;
	if (a->ent && b->ent && a->ent != b->ent)
		return 0;
	if ((a->value && !b->value) || (!a->value && b->value))
		return 0;
	if (strcmp(a->name, b->name) != 0)
		return 0;
	if (a->value && b->value && strcmp(a->value, b->value) != 0)
		return 0;

	return 1;
}

int mnt_optlist_merge_opts(struct libmnt_optlist *ls)
{
	struct libmnt_iter itr;
	struct libmnt_opt *opt;

	if (!ls)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "merging"));
	ls->merged = 1;

	/* deduplicate, keep last instance of the option only */
	mnt_reset_iter(&itr, MNT_ITER_BACKWARD);

	while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
		struct libmnt_iter xtr;
		struct libmnt_opt *x;

		mnt_reset_iter(&xtr, MNT_ITER_FORWARD);
		while (mnt_optlist_next_opt(ls, &xtr, &x) == 0) {
			int rem = 0;

			if (opt == x)
				break;	/* no another instance */

			/* remove duplicate option */
			if (is_equal_opts(opt, x))
				rem = 1;

			/* remove inverted option */
			else if (opt->ent && x->ent
			    && opt->map == x->map
			    && opt->ent->id == x->ent->id
			    && (opt->ent->mask & MNT_INVERT
				    || x->ent->mask & MNT_INVERT))
				rem = 1;

			if (rem) {
				/* me sure @itr does not point to removed item */
				if (itr.p == &x->opts)
					itr.p = x->opts.prev;
				mnt_optlist_remove_opt(ls, x);
			}

		}
	}

	return 0;
}

#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
static inline int flag_to_attr(unsigned long flag, uint64_t *attr)
{
	uint64_t a = 0;

	switch (flag) {
	case MS_RDONLY:
		a = MOUNT_ATTR_RDONLY;
		break;
	case MS_NOSUID:
		a = MOUNT_ATTR_NOSUID;
		break;
	case MS_NODEV:
		a = MOUNT_ATTR_NODEV;
		break;
	case MS_NOEXEC:
		a = MOUNT_ATTR_NOEXEC;
		break;
	case MS_NODIRATIME:
		a = MOUNT_ATTR_NODIRATIME;
		break;
	case MS_RELATIME:
		a = MOUNT_ATTR_RELATIME;
		break;
	case MS_NOATIME:
		a =  MOUNT_ATTR_NOATIME;
		break;
	case MS_STRICTATIME:
		a = MOUNT_ATTR_STRICTATIME;
		break;
	case MS_NOSYMFOLLOW:
		a = MOUNT_ATTR_NOSYMFOLLOW;
		break;
	default:
		return -1;
	}

	if (attr)
		*attr = a;
	return 0;
}

/*
 * Is the @opt relevant for mount_setattr() ?
 */
static inline int is_vfs_opt(struct libmnt_opt *opt)
{
	if (!opt->map || !opt->ent || !opt->ent->id || !opt->is_linux)
		return 0;

	return flag_to_attr(opt->ent->id, NULL) < 0 ? 0 : 1;
}
#endif

static struct libmnt_opt *optlist_new_opt(struct libmnt_optlist *ls,
			const char *name, size_t namesz,
			const char *value, size_t valsz,
			const struct libmnt_optmap *map,
			const struct libmnt_optmap *ent,
			struct list_head *where)

{
	struct libmnt_opt *opt;

	opt = calloc(1, sizeof(*opt));
	if (!opt)
		return NULL;

	INIT_LIST_HEAD(&opt->opts);
	opt->map = map;
	opt->ent = ent;

	if (valsz) {
		if (*value == '"' && *(value + valsz - 1) == '"') {
			opt->quoted = 1;
			value++;
			valsz -= 2;
		}
		opt->value = strndup(value, valsz);
		if (!opt->value)
			goto fail;

	} else if (value) {
		/* separator specified, but empty value ("name=") */
		opt->sepnodata = 1;
	}
	if (namesz) {
		opt->name = strndup(name, namesz);
		if (!opt->name)
			goto fail;
	}

	if (where)
		list_add(&opt->opts, where);
	else
		list_add_tail(&opt->opts, &ls->opts);

	/* shortcuts */
	if (map && ent && map == ls->linux_map) {
		opt->is_linux = 1;

		if (ent->id & MS_PROPAGATION)
			ls->propagation |= ent->id;
		else if (opt->ent->id == MS_REMOUNT)
			ls->is_remount = 1;
		else if (opt->ent->id == (MS_REC|MS_BIND))
			ls->is_rbind = 1;
		else if (opt->ent->id == MS_BIND)
			ls->is_bind = 1;
		else if (opt->ent->id == MS_RDONLY)
			ls->is_rdonly = opt->ent->mask & MNT_INVERT ? 0 : 1;
		else if (opt->ent->id == MS_MOVE)
			ls->is_move = 1;
		else if (opt->ent->id == MS_SILENT)
			ls->is_silent = 1;

		if (opt->ent->id & MS_REC) {
			ls->is_rpropagation = 1;
			opt->recursive = 1;
		}
	}
#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
	if (!opt->recursive && opt->value
	    && is_vfs_opt(opt) && mnt_opt_value_with(opt, "recursive"))
		opt->recursive = 1;
#endif
	if (ent && map) {
		DBG(OPTLIST, ul_debugobj(ls, " added %s [id=0x%08x map=%p]",
				opt->name, ent->id, map));
	} else {
		DBG(OPTLIST, ul_debugobj(ls, " added %s", opt->name));
	}
	return opt;
fail:
	mnt_optlist_remove_opt(ls, opt);
	return NULL;
}

static int optlist_add_optstr(struct libmnt_optlist *ls, const char *optstr,
			const struct libmnt_optmap *map, struct list_head *where)
{
	char *p = (char *) optstr, *name, *val;
	size_t namesz, valsz;
	int rc;

	if (!ls)
		return -EINVAL;
	if (map && (rc =  mnt_optlist_register_map(ls, map)))
		return rc;
	if (!optstr)
		return 0;

	while (ul_optstr_next(&p, &name, &namesz, &val, &valsz) == 0) {

		struct libmnt_opt *opt;
		const struct libmnt_optmap *e = NULL, *m = NULL;

		if (map)
			m = mnt_optmap_get_entry(&map, 1, name, namesz, &e);
		if (!m && ls->nmaps)
			m = mnt_optmap_get_entry(ls->maps, ls->nmaps, name, namesz, &e);

		/* TODO: add the option more than once if belongs to the more maps */

		opt = optlist_new_opt(ls, name, namesz, val, valsz, m, e, where);
		if (!opt)
			return -ENOMEM;
		opt->src = MNT_OPTSRC_STRING;
		if (where)
			where = &opt->opts;
	}

	optlist_cleanup_cache(ls);

	return 0;
}

/*
 * The library differentiate between options specified by flags and strings by
 * default.  In this case mnt_optlist_set_optstr() replaces all options
 * specified by strings for the @map or for all maps if @map is NULL
  *
  * If optlist is marked as merged by mnt_optlist_set_merged() than this
  * function replaced all options for the @map or for all maps if @map is NULL.
 */
int mnt_optlist_set_optstr(struct libmnt_optlist *ls, const char *optstr,
			  const struct libmnt_optmap *map)
{
	struct list_head *p, *next;

	if (!ls)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "set %s", optstr));

	/* remove all already set options */
	list_for_each_safe(p, next, &ls->opts) {
		struct libmnt_opt *opt = list_entry(p, struct libmnt_opt, opts);

		if (opt->external)
			continue;
		if (map && opt->map != map)
			continue;
		if (ls->merged || opt->src == MNT_OPTSRC_STRING)
			mnt_optlist_remove_opt(ls, opt);
	}

	return optlist_add_optstr(ls, optstr, map, NULL);
}

int mnt_optlist_append_optstr(struct libmnt_optlist *ls, const char *optstr,
			const struct libmnt_optmap *map)
{
	if (!ls)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "append %s", optstr));
	return optlist_add_optstr(ls, optstr, map, NULL);
}

int mnt_optlist_prepend_optstr(struct libmnt_optlist *ls, const char *optstr,
			const struct libmnt_optmap *map)
{
	if (!ls)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "prepend %s", optstr));
	return optlist_add_optstr(ls, optstr, map, &ls->opts);
}

static int optlist_add_flags(struct libmnt_optlist *ls, unsigned long flags,
			const struct libmnt_optmap *map, struct list_head *where)
{
	const struct libmnt_optmap *ent;
	int rc;

	if (!ls || !map)
		return -EINVAL;

	if (map && (rc = mnt_optlist_register_map(ls, map)))
		return rc;

	for (ent = map; ent && ent->name; ent++) {

		const char *p;
		size_t sz;
		struct libmnt_opt *opt;

		if ((ent->mask & MNT_INVERT)
		    || ent->name == NULL
		    || ent->id == 0
		    || (flags & ent->id) != (unsigned long) ent->id)
			continue;

		/* don't add options which require values (e.g. offset=%d) */
		p = strchr(ent->name, '=');
		if (p) {
			if (p > ent->name && *(p - 1) == '[')
				p--;		 /* name[=] */
			else
				continue;	/* name=<value> */
			sz = p - ent->name;
			p -= sz;
		} else {
			p = ent->name;
			sz = strlen(ent->name); /* alone "name" */
		}

		opt = optlist_new_opt(ls, p, sz, NULL, 0, map, ent, where);
		if (!opt)
			return -ENOMEM;
		opt->src = MNT_OPTSRC_FLAG;
		if (where)
			where = &opt->opts;
	}

	optlist_cleanup_cache(ls);

	return 0;
}

int mnt_optlist_append_flags(struct libmnt_optlist *ls, unsigned long flags,
			  const struct libmnt_optmap *map)
{
	if (!ls || !map)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "append 0x%08lx", flags));
	return optlist_add_flags(ls, flags, map, NULL);
}


int mnt_optlist_set_flags(struct libmnt_optlist *ls, unsigned long flags,
			  const struct libmnt_optmap *map)
{
	struct list_head *p, *next;

	if (!ls || !map)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "set 0x%08lx", flags));

	/* remove all already set options */
	list_for_each_safe(p, next, &ls->opts) {
		struct libmnt_opt *opt = list_entry(p, struct libmnt_opt, opts);

		if (opt->external)
			continue;
		if (map && opt->map != map)
			continue;
		if (ls->merged || opt->src == MNT_OPTSRC_FLAG)
			mnt_optlist_remove_opt(ls, opt);
	}

	return mnt_optlist_append_flags(ls, flags, map);
}

int mnt_optlist_remove_flags(struct libmnt_optlist *ls, unsigned long flags,
			const struct libmnt_optmap *map)
{
	struct list_head *p, *next;

	if (!ls || !map)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "remove 0x%08lx", flags));

	list_for_each_safe(p, next, &ls->opts) {
		struct libmnt_opt *opt = list_entry(p, struct libmnt_opt, opts);

		if (opt->external || !opt->ent)
			continue;
		if (map && opt->map != map)
			continue;
		if (opt->ent->id & flags)
			mnt_optlist_remove_opt(ls, opt);
	}
	return 0;
}

int mnt_optlist_insert_flags(struct libmnt_optlist *ls, unsigned long flags,
			const struct libmnt_optmap *map,
			unsigned long after,
			const struct libmnt_optmap *after_map)
{
	struct libmnt_opt *opt;

	if (!ls || !map || !after || !after_map)
		return -EINVAL;

	opt = mnt_optlist_get_opt(ls, after, after_map);
	if (!opt)
		return -EINVAL;

	DBG(OPTLIST, ul_debugobj(ls, "insert 0x%08lx (after %s)",
				flags, opt->ent ? opt->ent->name : "???"));

	return optlist_add_flags(ls, flags, map, &opt->opts);
}

static int is_wanted_opt(struct libmnt_opt *opt, const struct libmnt_optmap *map,
		unsigned int what)
{
	switch (what) {
	case MNT_OL_FLTR_DFLT:
		if (opt->external)
			return 0;
		if (map && opt->map != map)
			return 0;
		break;
	case MNT_OL_FLTR_ALL:
		break;
	case MNT_OL_FLTR_UNKNOWN:
		if (opt->map || opt->external)
			return 0;
		break;
	case MNT_OL_FLTR_HELPERS:
		if (opt->ent && opt->ent->mask & MNT_NOHLPS)
			return 0;
		break;
	case MNT_OL_FLTR_MTAB:
		if (opt->ent && opt->ent->mask & MNT_NOMTAB)
			return 0;
		break;
	}

	return 1;
}

static struct optlist_cache *get_cache(	struct libmnt_optlist *ls,
					const struct libmnt_optmap *map,
					unsigned int what)
{
	switch (what) {
	case MNT_OL_FLTR_DFLT:
		if (map) {
			const size_t idx = optlist_get_mapidx(ls, map);
			if (idx == (size_t) -1)
				return NULL;
			return &ls->cache_mapped[idx];
		}
		return &ls->cache_all[MNT_OL_FLTR_DFLT];

	case MNT_OL_FLTR_ALL:
	case MNT_OL_FLTR_UNKNOWN:
	case MNT_OL_FLTR_HELPERS:
	case MNT_OL_FLTR_MTAB:
		return &ls->cache_all[what];

	default:
		break;
	}

	return NULL;
}

/*
 * Returns flags (bit mask from options map entries).
 */
int mnt_optlist_get_flags(struct libmnt_optlist *ls, unsigned long *flags,
			  const struct libmnt_optmap *map, unsigned int what)
{
	struct optlist_cache *cache;

	if (!ls || !map || !flags)
		return -EINVAL;

	cache = get_cache(ls, map, what);
	if (!cache)
		return -EINVAL;

	if (!cache->flags_ready) {
		struct libmnt_iter itr;
		struct libmnt_opt *opt;
		unsigned long fl = 0;

		mnt_reset_iter(&itr, MNT_ITER_FORWARD);

		while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
			if (map != opt->map)
				continue;
			if (!opt->ent || !opt->ent->id)
				continue;
			if (!is_wanted_opt(opt, map, what))
				continue;

			if (opt->ent->mask & MNT_INVERT)
				fl &= ~opt->ent->id;
			else
				fl |= opt->ent->id;
		}

		cache->flags = fl;
		cache->flags_ready = 1;
	}

	*flags = cache->flags;

	DBG(OPTLIST, ul_debugobj(ls, "return flags 0x%08lx [map=%p]", *flags, map));
	return 0;
}


/*
 * Like mnt_optlist_get_flags() for VFS flags, but converts classic MS_* flags to
 * new MOUNT_ATTR_*
 */
#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT

#define MNT_RESETABLE_ATTRS	(MOUNT_ATTR_RDONLY| MOUNT_ATTR_NOSUID| \
				 MOUNT_ATTR_NODEV | MOUNT_ATTR_NOEXEC| \
				 MOUNT_ATTR_NOATIME|  MOUNT_ATTR_NODIRATIME | \
				 MOUNT_ATTR_NOSYMFOLLOW)

int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *clr, int rec)
{
	struct libmnt_iter itr;
	struct libmnt_opt *opt;
	uint64_t remount_reset = 0;
	uint64_t atime_set = 0;

	if (!ls || !ls->linux_map || !set || !clr)
		return -EINVAL;

	*set = 0, *clr = 0;
	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	/* The classic mount(2) MS_REMOUNT resets all flags which are not
	 * specified (except atime stuff). For backward compatibility we need
	 * to emulate this semantic by mount_setattr(). The new
	 * mount_setattr() has simple set/unset sematinc and nothing is
	 * reset internally in kernel.
	 */
	if (mnt_optlist_is_remount(ls)
	    && !mnt_optlist_is_bind(ls)
	    && rec == MNT_OL_NOREC)
		remount_reset = (MOUNT_ATTR_RDONLY| MOUNT_ATTR_NOSUID| \
				 MOUNT_ATTR_NODEV | MOUNT_ATTR_NOEXEC| \
				 MOUNT_ATTR_NOSYMFOLLOW);

	while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
		uint64_t x = 0;

		if (ls->linux_map != opt->map)
			continue;
		if (!opt->ent || !opt->ent->id)
			continue;

		if (rec == MNT_OL_REC && !opt->recursive)
			continue;
		if (rec == MNT_OL_NOREC && opt->recursive)
			continue;

		if (!is_wanted_opt(opt, ls->linux_map, MNT_OL_FLTR_DFLT))
			continue;
		if (flag_to_attr( opt->ent->id, &x) < 0)
			continue;

		if (x == MOUNT_ATTR_RDONLY && !mnt_opt_value_with(opt, "vfs")
				           && mnt_opt_value_with(opt, "fs"))
			continue;

		if (x && remount_reset)
			remount_reset &= ~x;

		if (opt->ent->mask & MNT_INVERT) {
			DBG(OPTLIST, ul_debugobj(ls, " clr: %s 0x%08" PRIx64,
						opt->ent->name, x));

			if (x == MOUNT_ATTR_RELATIME || x == MOUNT_ATTR_NOATIME ||
			    x == MOUNT_ATTR_STRICTATIME)
				*clr |= MOUNT_ATTR__ATIME;
			else
				*clr |= x;
		} else {
			if (x == MOUNT_ATTR_RELATIME || x == MOUNT_ATTR_NOATIME ||
			    x == MOUNT_ATTR_STRICTATIME) {
				/* All atime settings are mutually exclusive,
				 * the last option wins and MOUNT_ATTR__ATIME
				 * is required in clr mask.
				 */
				DBG(OPTLIST, ul_debugobj(ls, " atime: %s 0x%08" PRIx64,
							opt->ent->name, x));
				*clr |= MOUNT_ATTR__ATIME;
				atime_set = x;
			} else {
				DBG(OPTLIST, ul_debugobj(ls, " set: %s 0x%08" PRIx64,
							opt->ent->name, x));
				*set |= x;
			}
		}
	}

	if (atime_set) {
		DBG(OPTLIST, ul_debugobj(ls, " set atime 0x%08" PRIx64, atime_set));
		*set |= atime_set;
	}
	if (remount_reset)
		*clr |= remount_reset;

	DBG(OPTLIST, ul_debugobj(ls, "return attrs set=0x%08" PRIx64
				      ", clr=0x%08" PRIx64 " %s",
				*set, *clr,
				rec == MNT_OL_REC ? "[rec]" :
				rec == MNT_OL_NOREC ? "[norec]" : ""));
	return 0;
}

#else
int mnt_optlist_get_attrs(struct libmnt_optlist *ls __attribute__((__unused__)),
			  uint64_t *set __attribute__((__unused__)),
			  uint64_t *clr __attribute__((__unused__)),
			  int mask __attribute__((__unused__)))
{
	return 0;
}
#endif /* USE_LIBMOUNT_MOUNTFD_SUPPORT */

int mnt_optlist_strdup_optstr(struct libmnt_optlist *ls, char **optstr,
			const struct libmnt_optmap *map, unsigned int what)
{
	struct libmnt_iter itr;
	struct libmnt_opt *opt;
	struct ul_buffer buf = UL_INIT_BUFFER;
	char *str = NULL;
	int rc = 0, is_rdonly = 0, xx_wanted = 0;

	if (!ls || !optstr)
		return -EINVAL;

	*optstr = NULL;

	/* For generic options strings ro/rw are expected at the beginning */
	if ((!map || map == ls->linux_map)
	     && (what == MNT_OL_FLTR_DFLT ||
		 what == MNT_OL_FLTR_ALL ||
		 what == MNT_OL_FLTR_HELPERS)) {

		rc = mnt_buffer_append_option(&buf, "rw", 2, NULL, 0, 0);
		if (rc)
			goto fail;
		xx_wanted = 1;
	}

	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
		if (!opt->name)
			continue;
		if (opt->map == ls->linux_map && opt->ent->id == MS_RDONLY) {
			/* "ro=fs" -- ignore for VFS (linux map wanted) */
			if (map == ls->linux_map
			    && !mnt_opt_value_with(opt, "vfs")
			    && mnt_opt_value_with(opt, "fs"))
				continue;
			/* "ro=vfs" -- ignore for FS */
			if (what == MNT_OL_FLTR_UNKNOWN
			    && !mnt_opt_value_with(opt, "fs")
			    && mnt_opt_value_with(opt, "vfs"))
				continue;

			is_rdonly = opt->ent->mask & MNT_INVERT ? 0 : 1;
			continue;
		}
		if (!is_wanted_opt(opt, map, what))
			continue;
		rc = mnt_buffer_append_option(&buf,
					opt->name, strlen(opt->name),
					opt->value ? opt->value :
						     opt->sepnodata ? "" : NULL,
					opt->value ? strlen(opt->value) : 0,
					opt->quoted);
		if (rc)
			goto fail;
	}

	str = ul_buffer_get_data(&buf, NULL, NULL);

	/* convert 'rw' at the beginning to 'ro' if necessary */
	if (str && is_rdonly && xx_wanted
	    && (what == MNT_OL_FLTR_DFLT ||
		what == MNT_OL_FLTR_ALL ||
		what == MNT_OL_FLTR_HELPERS)) {

		str[0] = 'r';
		str[1] = 'o';
	}

	if (optstr)
		*optstr = str;
	return 0;
fail:
	ul_buffer_free_data(&buf);
	return rc;
}

int mnt_optlist_get_optstr(struct libmnt_optlist *ls, const char **optstr,
			const struct libmnt_optmap *map, unsigned int what)
{
	struct optlist_cache *cache;

	if (!ls || !optstr)
		return -EINVAL;

	*optstr = NULL;

	cache = get_cache(ls, map, what);
	if (!cache)
		return -EINVAL;

	if (!cache->optstr_ready) {
		char *str = NULL;
		int rc = mnt_optlist_strdup_optstr(ls, &str, map, what);

		if (rc)
			return rc;

		cache->optstr = str;
		cache->optstr_ready = 1;
	}

	*optstr = cache->optstr;
	return 0;
}

struct libmnt_optlist *mnt_copy_optlist(struct libmnt_optlist *ls)
{
	struct libmnt_optlist *n = mnt_new_optlist();
	struct libmnt_iter itr;
	struct libmnt_opt *opt;
	size_t i;

	if (!n)
		return NULL;

	n->age = ls->age;
	n->linux_map = ls->linux_map;

	for (i = 0; i < ls->nmaps; i++)
		n->maps[i] = ls->maps[i];
	n->nmaps = ls->nmaps;

	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) {
		struct libmnt_opt *no;

		no = optlist_new_opt(n,
			opt->name, opt->name ? strlen(opt->name) : 0,
			opt->value, opt->value ? strlen(opt->value) : 0,
			opt->map, opt->ent, NULL);
		if (no) {
			no->src = opt->src;
			no->external = opt->external;
			no->quoted = opt->quoted;
			no->sepnodata = opt->sepnodata;
		}
	}

	n->merged = ls->merged;
	return n;
}


int mnt_optlist_is_empty(struct libmnt_optlist *ls)
{
	return ls == NULL || list_empty(&ls->opts);
}

unsigned int mnt_optlist_get_age(struct libmnt_optlist *ls)
{
	return ls ? ls->age : 0;
}

int mnt_optlist_get_propagation(struct libmnt_optlist *ls)
{
	return ls ? ls->propagation : 0;
}

int mnt_optlist_is_propagation_only(struct libmnt_optlist *ls)
{
	unsigned long flags = 0, rest;

	if (!ls || !ls->propagation || !ls->nmaps)
		return 0;

	if (mnt_optlist_get_flags(ls, &flags, ls->linux_map, 0) != 0)
		return 0;

	rest = flags & ~MS_PROPAGATION;
	DBG(OPTLIST, ul_debugobj(ls, " propagation-only: %s",
		(rest == 0 || (rest & (MS_SILENT | MS_REC)) ? "y" : "n")));

	return (rest == 0 || (rest & (MS_SILENT | MS_REC)));
}

int mnt_optlist_is_remount(struct libmnt_optlist *ls)
{
	return ls && ls->is_remount;
}

int mnt_optlist_is_rpropagation(struct libmnt_optlist *ls)
{
	return ls && ls->is_rpropagation;
}

int mnt_optlist_is_move(struct libmnt_optlist *ls)
{
	return ls && ls->is_move;
}

int mnt_optlist_is_bind(struct libmnt_optlist *ls)
{
	return ls && (ls->is_bind || ls->is_rbind);
}

int mnt_optlist_is_rbind(struct libmnt_optlist *ls)
{
	return ls && ls->is_rbind;
}

int mnt_optlist_is_rdonly(struct libmnt_optlist *ls)
{
	return ls && ls->is_rdonly;
}

int mnt_optlist_is_silent(struct libmnt_optlist *ls)
{
	return ls && ls->is_silent;
}


int mnt_opt_has_value(struct libmnt_opt *opt)
{
	return opt && opt->value;
}

const char *mnt_opt_get_value(struct libmnt_opt *opt)
{
	return opt->value;
}

/* check if option value is @str or comma separated @str */
int mnt_opt_value_with(struct libmnt_opt *opt, const char *str)
{
	const char *p, *start = opt->value;
	size_t len;

	if (!str || !opt->value || !*opt->value)
		return 0;

	len = strlen(str);

	while (start && *start) {
		p = strstr(start, str);
		if (!p)
			return 0;
		start = p + len;

		if (p > opt->value && *(p - 1) != ',')
			continue;
		len = strlen(str);
		if (*(p + len) != '\0' && *(p + len) != ',')
			continue;
		return 1;
	}
	return 0;
}

const char *mnt_opt_get_name(struct libmnt_opt *opt)
{
	return opt->name;
}

const struct libmnt_optmap *mnt_opt_get_map(struct libmnt_opt *opt)
{
	return opt->map;
}

const struct libmnt_optmap *mnt_opt_get_mapent(struct libmnt_opt *opt)
{
	return opt->ent;
}

int mnt_opt_set_value(struct libmnt_opt *opt, const char *str)
{
	int rc;

	opt->recursive = 0;
	rc = strdup_to_struct_member(opt, value, str);

	if (rc == 0 && mnt_opt_value_with(opt, "recursive"))
		opt->recursive = 1;
	return rc;
}

int mnt_opt_set_u64value(struct libmnt_opt *opt, uint64_t num)
{
	char buf[ sizeof(stringify_value(UINT64_MAX)) ];

        snprintf(buf, sizeof(buf), "%"PRIu64, num);

	return mnt_opt_set_value(opt, buf);
}

int mnt_opt_set_quoted_value(struct libmnt_opt *opt, const char *str)
{
	opt->quoted = 1;
	return mnt_opt_set_value(opt, str);
}

int mnt_opt_set_external(struct libmnt_opt *opt, int enable)
{
	if (!opt)
		return -EINVAL;
	opt->external = enable ? 1 : 0;
	return 0;
}

int mnt_opt_is_external(struct libmnt_opt *opt)
{
	return opt && opt->external ? 1 : 0;
}

int mnt_opt_is_sepnodata(struct libmnt_opt *opt)
{
	return opt->sepnodata;
}


#ifdef TEST_PROGRAM

static int mk_optlist(struct libmnt_optlist **ol, const char *optstr)
{
	int rc = 0;

	*ol = mnt_new_optlist();
	if (!*ol)
		rc = -ENOMEM;

	if (!rc)
		rc = mnt_optlist_register_map(*ol, mnt_get_builtin_optmap(MNT_LINUX_MAP));
	if (!rc)
		rc = mnt_optlist_register_map(*ol, mnt_get_builtin_optmap(MNT_USERSPACE_MAP));
	if (!rc && optstr)
		rc = mnt_optlist_append_optstr(*ol, optstr, NULL);
	if (rc) {
		mnt_unref_optlist(*ol);
		*ol = NULL;
	}
	return rc;
}

static void dump_optlist(struct libmnt_optlist *ol)
{
	struct libmnt_iter itr;
	struct libmnt_opt *opt;
	int i = 0;

	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
		if (opt->ent)
			printf("#%02d [%p:0x%08x] name:'%s',\tvalue:'%s'\n",
				++i, opt->map, opt->ent->id, opt->name, opt->value);
		else
			printf("#%02d [         unknown         ] name:'%s',\tvalue:'%s'\n",
				++i, opt->name, opt->value);

	}
}

static const struct libmnt_optmap *get_map(const char *name)
{
	if (name && strcmp(name, "linux") == 0)
		return mnt_get_builtin_optmap(MNT_LINUX_MAP);
	if (name && strcmp(name, "user") == 0)
		return mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
	return NULL;
}

static inline unsigned long str2flg(const char *str)
{
	return (unsigned long) strtox64_or_err(str, "can't convert string to flags");
}

static int test_append_str(struct libmnt_test *ts __attribute__((unused)),
			   int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	int rc;

	if (argc < 3)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (!rc)
		rc = mnt_optlist_append_optstr(ol, argv[2], get_map(argv[3]));
	if (!rc)
		dump_optlist(ol);
	mnt_unref_optlist(ol);
	return rc;
}

static int test_prepend_str(struct libmnt_test *ts __attribute__((unused)),
			    int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	int rc;

	if (argc < 3)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (!rc)
		rc = mnt_optlist_prepend_optstr(ol, argv[2], get_map(argv[3]));
	if (!rc)
		dump_optlist(ol);
	mnt_unref_optlist(ol);
	return rc;
}

static int test_set_str(struct libmnt_test *ts __attribute__((unused)),
			int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	int rc;

	if (argc < 3)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (!rc)
		rc = mnt_optlist_set_optstr(ol, argv[2], get_map(argv[3]));
	if (!rc)
		dump_optlist(ol);
	mnt_unref_optlist(ol);
	return rc;
}

static int test_append_flg(struct libmnt_test *ts __attribute__((unused)),
			   int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	int rc;

	if (argc < 4)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (!rc)
		rc = mnt_optlist_append_flags(ol, str2flg(argv[2]), get_map(argv[3]));
	if (!rc)
		dump_optlist(ol);
	mnt_unref_optlist(ol);
	return rc;
}

static int test_set_flg(struct libmnt_test *ts __attribute__((unused)),
			int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	int rc;

	if (argc < 4)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (!rc)
		rc = mnt_optlist_set_flags(ol, str2flg(argv[2]), get_map(argv[3]));
	if (!rc)
		dump_optlist(ol);
	mnt_unref_optlist(ol);
	return rc;
}

static int test_get_str(struct libmnt_test *ts __attribute__((unused)),
			int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	const struct libmnt_optmap *map;
	const char *str = NULL;
	int rc;
	unsigned long flags = 0;

	if (argc < 2)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (rc)
		goto done;

	map = get_map(argv[2]);
	mnt_optlist_merge_opts(ol);

	/* We always call mnt_optlist_get_optstr() two times to test the cache */
	if (map) {
		rc = mnt_optlist_get_optstr(ol, &str, map, MNT_OL_FLTR_DFLT);
		if (!rc)
			rc = mnt_optlist_get_optstr(ol, &str, map, MNT_OL_FLTR_DFLT);
		if (!rc)
			rc = mnt_optlist_get_flags(ol, &flags, map, MNT_OL_FLTR_DFLT);
		if (!rc)
			rc = mnt_optlist_get_flags(ol, &flags, map, MNT_OL_FLTR_DFLT);
		if (!rc)
			printf("Default: %s [0x%08lx] (in %s map)\n", str, flags, argv[2]);
	}

	rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_DFLT);
	if (!rc)
		rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_DFLT);
	if (!rc)
		printf("Default: %s\n", str);


	rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_ALL);
	if (!rc)
		rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_ALL);
	if (!rc)
		printf("All:     %s\n", str);

	rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_UNKNOWN);
	if (!rc)
		rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_UNKNOWN);
	if (!rc)
		printf("Unknown: %s\n", str);

	rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_HELPERS);
	if (!rc)
		rc = mnt_optlist_get_optstr(ol, &str, NULL, MNT_OL_FLTR_HELPERS);
	if (!rc)
		printf("Helpers: %s\n", str);
done:
	mnt_unref_optlist(ol);
	return rc;
}

static int test_get_flg(struct libmnt_test *ts __attribute__((unused)),
			int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	unsigned long flags = 0;
	int rc;

	if (argc < 3)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (!rc)
		rc = mnt_optlist_get_flags(ol, &flags, get_map(argv[2]), 0);
	if (!rc)
		printf("0x%08lx\n", flags);
	mnt_unref_optlist(ol);
	return rc;
}

static int test_split(struct libmnt_test *ts __attribute__((unused)),
		      int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	int rc;
	struct libmnt_iter itr;
	struct libmnt_opt *opt;
	const char *name, *value;

	if (argc != 2)
		return -EINVAL;
	rc = mk_optlist(&ol, argv[1]);
	if (rc)
		goto done;

	mnt_reset_iter(&itr, MNT_ITER_FORWARD);

	while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
		name = mnt_opt_get_name(opt);
		value = mnt_opt_get_value(opt);

		printf("%s = %s\n", name, value ?: "(null)");
	}

done:
	mnt_unref_optlist(ol);
	return rc;
}

static int test_value_with(struct libmnt_test *ts __attribute__((unused)),
		int argc, char *argv[])
{
	struct libmnt_optlist *ol;
	struct libmnt_opt *opt;
	int rc;

	if (argc != 4)
		return -EINVAL;

	rc = mk_optlist(&ol, argv[1]);
	if (rc)
		goto done;

	opt = mnt_optlist_get_named(ol, argv[2], NULL);
	if (!opt)
		warnx("not found '%s' option", argv[2]);
	else if (mnt_opt_value_with(opt, argv[3]))
		printf("found\n");
	else
		printf("'%s' not found within '%s'\n", argv[3], mnt_opt_get_value(opt));

done:
	mnt_unref_optlist(ol);
	return rc;
}

int main(int argc, char *argv[])
{
	struct libmnt_test tss[] = {
		{ "--append-str",  test_append_str,  "<list> <str> [linux|user]  append to the list" },
		{ "--prepend-str", test_prepend_str, "<list> <str> [linux|user]  prepend to the list" },
		{ "--set-str",     test_set_str,     "<list> <str> [linux|user]  set to the list" },
		{ "--append-flg",  test_append_flg,  "<list> <flg>  linux|user   append to the list" },
		{ "--set-flg",     test_set_flg,     "<list> <flg>  linux|user   set to the list" },
		{ "--get-str",     test_get_str,     "<list> [linux|user]        all options in string" },
		{ "--get-flg",     test_get_flg,     "<list>  linux|user         all options by flags" },
		{ "--split",       test_split,       "<list>                     split options into key-value pairs"},
		{ "--value-with",  test_value_with,  "<list> <opt> <str>        check if option value contains string"},

		{ NULL }
	};
	return  mnt_run_test(tss, argc, argv);
}
#endif /* TEST_PROGRAM */
