From: Karel Zak Date: Fri, 24 Jun 2022 16:15:25 +0000 (+0200) Subject: libmount: add libmnt_optlist X-Git-Tag: v2.39-rc1~327 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=add9d4582179eaad69d92ee224cbb4c1c3d96228;p=thirdparty%2Futil-linux.git libmount: add libmnt_optlist Now libmount internally keeps mount options as strings or flags. This commit adds a new container to keep parsed mount options in memory. We need: * the same mount options could be mapped to the different mount options maps (for example "nosuid" to classic MS_NOSUID or new MOUNT_ATTR_NOSUID) * easy to sync flags and strings (e.g. conversion from MS_NOSUID to "nosuid" and vice-versa). * double linked list is easy to use in while() when browse all options * easy to filter options by specific map (MS_*, userspace, etc.) * don't hardcode options maps in options parser, support 3th-party maps (for example for extensions like dm-verity, etc.) Signed-off-by: Karel Zak --- diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index 2881ae5762..7a220f8fd1 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -16,6 +16,7 @@ libmount_la_SOURCES = \ libmount/src/iter.c \ libmount/src/lock.c \ libmount/src/optmap.c \ + libmount/src/optlist.c \ libmount/src/optstr.c \ libmount/src/tab.c \ libmount/src/tab_diff.c \ diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index 49bcdd0b72..0fe7bd4adf 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -49,6 +49,7 @@ #define MNT_DEBUG_LOOP (1 << 13) #define MNT_DEBUG_VERITY (1 << 14) #define MNT_DEBUG_HOOK (1 << 15) +#define MNT_DEBUG_OPTLIST (1 << 16) #define MNT_DEBUG_ALL 0xFFFF @@ -475,6 +476,44 @@ extern int mnt_buffer_append_option(struct ul_buffer *buf, const char *name, size_t namesz, const char *val, size_t valsz); +/* optlist.h */ +struct libmnt_opt; +struct libmnt_optlist; + +extern struct libmnt_optlist *mnt_new_optlist(void); +extern void mnt_ref_optlist(struct libmnt_optlist *ls); +extern void mnt_unref_optlist(struct libmnt_optlist *ls); +extern int mnt_optlist_register_map(struct libmnt_optlist *ls, const struct libmnt_optmap *map); +extern int mnt_optlist_remove_opt(struct libmnt_optlist *ls, struct libmnt_opt *opt); +extern int mnt_optlist_next_opt(struct libmnt_optlist *ls, + struct libmnt_iter *itr, struct libmnt_opt **opt); +extern struct libmnt_opt *mnt_optlist_get_opt(struct libmnt_optlist *ls, + unsigned long id, const struct libmnt_optmap *map); +extern struct libmnt_opt *mnt_optlist_get_named(struct libmnt_optlist *ls, + char *name, const struct libmnt_optmap *map); +extern int mnt_optlist_set_optstr(struct libmnt_optlist *ls, const char *optstr, + const struct libmnt_optmap *map); +extern int mnt_optlist_append_optstr(struct libmnt_optlist *ls, const char *optstr, + const struct libmnt_optmap *map); +extern int mnt_optlist_prepend_optstr(struct libmnt_optlist *ls, const char *optstr, + const struct libmnt_optmap *map); +extern int mnt_optlist_append_flags(struct libmnt_optlist *ls, unsigned long flags, + const struct libmnt_optmap *map); +extern int mnt_optlist_set_flags(struct libmnt_optlist *ls, unsigned long flags, + const struct libmnt_optmap *map); +extern 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); +extern int mnt_optlist_get_flags(struct libmnt_optlist *ls, unsigned long *flags, + const struct libmnt_optmap *map); +extern int mnt_optlist_get_optstr(struct libmnt_optlist *ol, char **optstr, + const struct libmnt_optmap *map); +extern int mnt_optlist_get_propagation(struct libmnt_optlist *ls); +extern int mnt_optlist_is_propagation_only(struct libmnt_optlist *ls); +extern int mnt_opt_has_value(struct libmnt_opt *opt); + + /* fs.c */ extern struct libmnt_fs *mnt_copy_mtab_fs(const struct libmnt_fs *fs); extern int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source) diff --git a/libmount/src/optlist.c b/libmount/src/optlist.c new file mode 100644 index 0000000000..dd34c893bb --- /dev/null +++ b/libmount/src/optlist.c @@ -0,0 +1,478 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2022 Karel Zak + * + * 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_OPTLIST_MAXMAPS 64 + +enum libmnt_optsrc { + MNT_OPTSRC_STRING, + MNT_OPTSRC_FLAG +}; + +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 */ +}; + +struct libmnt_optlist { + int refcount; + + const struct libmnt_optmap *linux_map; /* map with MS_ flags */ + const struct libmnt_optmap *maps[MNT_OPTLIST_MAXMAPS]; + size_t nmaps; + + unsigned long propagation; /* propagation MS_ flags */ + struct list_head opts; /* parsed options */ +}; + +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++; + /*DBG(OPTLIST, ul_debugobj(ls, "ref=%d", ls->refcount));*/ + } +} + +void mnt_unref_optlist(struct libmnt_optlist *ls) +{ + if (!ls) + return; + + ls->refcount--; + if (ls->refcount > 0) + return; + + /*DBG(OPTLIST, ul_debugobj(ls, "unref=%d", ls->refcount));*/ + while (!list_empty(&ls->opts)) { + struct libmnt_opt *opt = list_entry(ls->opts.next, struct libmnt_opt, opts); + mnt_optlist_remove_opt(ls, opt); + } + + 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_OPTLIST_MAXMAPS) + return -ERANGE; + + DBG(OPTLIST, ul_debugobj(ls, "registr map %p", map)); + ls->maps[ls->nmaps++] = map; + return 0; +} + +int mnt_optlist_remove_opt(struct libmnt_optlist *ls, struct libmnt_opt *opt) +{ + if (!opt) + return -EINVAL; + + if (opt->map && opt->ent + && opt->map == ls->linux_map && opt->ent->id & MS_PROPAGATION) + ls->propagation &= ~opt->ent->id; + + list_del_init(&opt->opts); + free(opt->value); + free(opt->name); + free(opt); + + return 0; +} + +int mnt_optlist_next_opt(struct libmnt_optlist *ls, + struct libmnt_iter *itr, struct libmnt_opt **opt) +{ + int rc = 1; + + if (!ls || !itr || !opt) + return -EINVAL; + *opt = NULL; + + if (!itr->head) + MNT_ITER_INIT(itr, &ls->opts); + if (itr->p != itr->head) { + MNT_ITER_ITERATE(itr, *opt, struct libmnt_opt, opts); + 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 && opt->map == map + && opt->ent && opt->ent->id == (int) id) + return opt; + } + + return NULL; +} + +struct libmnt_opt *mnt_optlist_get_named(struct libmnt_optlist *ls, + 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 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) { + 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); + + if (map && ent && map == ls->linux_map && ent->id & MS_PROPAGATION) + ls->propagation |= ent->id; + + 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 || !optstr) + return -EINVAL; + if (map && (rc = mnt_optlist_register_map(ls, map))) + return rc; + + 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; + } + + return 0; +} + +/* replaces all existing options with options from @optstr */ +int mnt_optlist_set_optstr(struct libmnt_optlist *ls, const char *optstr, + const struct libmnt_optmap *map) +{ + struct list_head *p, *next; + + DBG(OPTLIST, ul_debugobj(ls, "set %s", optstr)); + + /* remove all previous options set by optstr */ + list_for_each_safe(p, next, &ls->opts) { + struct libmnt_opt *opt = list_entry(p, struct libmnt_opt, opts); + + if (opt->external) + continue; + if (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) +{ + 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) +{ + 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->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= */ + sz = p - ent->name; + } else + 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; + } + + return 0; +} + +int mnt_optlist_append_flags(struct libmnt_optlist *ls, unsigned long flags, + const struct libmnt_optmap *map) +{ + DBG(OPTLIST, ul_debugobj(ls, "append %lx", 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 %lx", flags)); + + /* remove all previous options defined by flag */ + list_for_each_safe(p, next, &ls->opts) { + struct libmnt_opt *opt = list_entry(p, struct libmnt_opt, opts); + + if (opt->external) + continue; + if (opt->src == MNT_OPTSRC_FLAG) + mnt_optlist_remove_opt(ls, opt); + } + + return mnt_optlist_append_flags(ls, flags, map); +} + +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; + + DBG(OPTLIST, ul_debugobj(ls, "insert %lx (after %s)", + flags, opt->ent ? opt->ent->name : "???")); + + opt = mnt_optlist_get_opt(ls, after, after_map); + return optlist_add_flags(ls, flags, map, &opt->opts); +} + +/* like mnt_optstr_get_flags() */ +int mnt_optlist_get_flags(struct libmnt_optlist *ls, unsigned long *flags, + const struct libmnt_optmap *map) +{ + struct libmnt_iter itr; + struct libmnt_opt *opt; + + if (!ls || !map) + return -EINVAL; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) { + if (opt->map != map || !opt->ent || !opt->ent->id) + continue; + if (opt->external) + continue; + if (opt->ent->mask & MNT_INVERT) + *flags &= ~opt->ent->id; + else + *flags |= opt->ent->id; + } + + DBG(OPTLIST, ul_debugobj(ls, "return flags %lx [map=%p]", *flags, map)); + return 0; +} + +int mnt_optlist_get_optstr(struct libmnt_optlist *ls, char **optstr, + const struct libmnt_optmap *map) +{ + struct libmnt_iter itr; + struct libmnt_opt *opt; + struct ul_buffer buf = UL_INIT_BUFFER; + + if (!ls || !optstr) + return -EINVAL; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ls, &itr, &opt) == 0) { + int rc; + + if (map && opt->map != map) + continue; + if (!opt->name) + continue; + + rc = mnt_buffer_append_option(&buf, + opt->name, strlen(opt->name), + opt->value, + opt->value ? strlen(opt->value) : 0); + if (rc) { + ul_buffer_free_data(&buf); + return rc; + } + } + + *optstr = ul_buffer_get_data(&buf, NULL, NULL); + DBG(OPTLIST, ul_debugobj(ls, "return optstr %s", *optstr)); + return 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) + return 0; + + rest = flags & ~MS_PROPAGATION; + return (rest == 0 || rest == MS_SILENT); +} + +int mnt_opt_has_value(struct libmnt_opt *opt) +{ + return opt && opt->value; +}