summaryrefslogtreecommitdiffstats
path: root/libmount/src/optlist.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libmount/src/optlist.c1415
1 files changed, 1415 insertions, 0 deletions
diff --git a/libmount/src/optlist.c b/libmount/src/optlist.c
new file mode 100644
index 0000000..0702ada
--- /dev/null
+++ b/libmount/src/optlist.c
@@ -0,0 +1,1415 @@
+/* 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"
+#include "mount-api-utils.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 */
+ 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_recursive : 1;
+};
+
+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 registred, ignore */
+ }
+ if (ls->nmaps + 1 >= MNT_OL_MAXMAPS)
+ return -ERANGE;
+
+ DBG(OPTLIST, ul_debugobj(ls, "registr 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_recursive = 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;
+ }
+ 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_recursive = 1;
+ opt->recursive = 1;
+ }
+ }
+#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
+ if (!opt->recursive && opt->value
+ && is_vfs_opt(opt) && strcmp(opt->value, "recursive") == 0)
+ 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++) {
+
+ 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 = (char *) 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;
+
+ 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
+ * internally in kernel reseted.
+ */
+ 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 && remount_reset)
+ remount_reset &= ~x;
+
+ if (opt->ent->mask & MNT_INVERT) {
+ DBG(OPTLIST, ul_debugobj(ls, " clr: %s", opt->ent->name));
+ /*
+ * All atime settings are mutually exclusive so *clr must
+ * have MOUNT_ATTR__ATIME set.
+ *
+ * See the function fs/namespace.c:build_mount_kattr()
+ * in the linux kernel source.
+ */
+ if (x == MOUNT_ATTR_RELATIME || x == MOUNT_ATTR_NOATIME ||
+ x == MOUNT_ATTR_STRICTATIME)
+ *clr |= MOUNT_ATTR__ATIME;
+ else
+ *clr |= x;
+ } else {
+ DBG(OPTLIST, ul_debugobj(ls, " set: %s", opt->ent->name));
+ *set |= x;
+
+ if (x == MOUNT_ATTR_RELATIME || x == MOUNT_ATTR_NOATIME ||
+ x == MOUNT_ATTR_STRICTATIME)
+ *clr |= MOUNT_ATTR__ATIME;
+ }
+ }
+
+ 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 srings ro/rw is expected at the begining */
+ 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) {
+ 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 ? 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;
+ }
+ }
+
+ 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_recursive(struct libmnt_optlist *ls)
+{
+ return ls && ls->is_recursive;
+}
+
+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;
+}
+
+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 && str && strcmp(str, "recursive") == 0)
+ 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;
+}
+
+
+#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, "connt convert string to flags");
+}
+
+static int test_append_str(struct libmnt_test *ts, 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, 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, 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, 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, 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, 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, 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;
+}
+
+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" },
+
+ { NULL }
+ };
+ return mnt_run_test(tss, argc, argv);
+}
+#endif /* TEST_PROGRAM */