diff options
Diffstat (limited to 'libmount/src')
35 files changed, 27199 insertions, 0 deletions
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am new file mode 100644 index 0000000..367bc46 --- /dev/null +++ b/libmount/src/Makemodule.am @@ -0,0 +1,217 @@ + +# libmount.h is generated, so it's store in builddir! +mountincdir = $(includedir)/libmount +nodist_mountinc_HEADERS = libmount/src/libmount.h + +usrlib_exec_LTLIBRARIES += libmount.la +libmount_la_SOURCES = \ + include/list.h \ + lib/monotonic.c \ + \ + libmount/src/mountP.h \ + libmount/src/cache.c \ + libmount/src/fs.c \ + libmount/src/init.c \ + 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 \ + libmount/src/tab_parse.c \ + libmount/src/tab_update.c \ + libmount/src/test.c \ + libmount/src/utils.c \ + libmount/src/version.c + +if LINUX +libmount_la_SOURCES += \ + libmount/src/context.c \ + libmount/src/context_mount.c \ + libmount/src/context_umount.c \ + libmount/src/hooks.c \ + libmount/src/hook_mount.c \ + libmount/src/hook_mount_legacy.c \ + libmount/src/hook_mkdir.c \ + libmount/src/hook_selinux.c \ + libmount/src/hook_subdir.c \ + libmount/src/hook_owner.c \ + libmount/src/hook_idmap.c \ + libmount/src/hook_loopdev.c \ + libmount/src/hook_veritydev.c \ + libmount/src/monitor.c + +if HAVE_BTRFS +libmount_la_SOURCES += libmount/src/btrfs.c +endif + +endif # LINUX + + +libmount_la_LIBADD = \ + libcommon.la \ + libblkid.la \ + $(SELINUX_LIBS) \ + $(REALTIME_LIBS) + +if HAVE_CRYPTSETUP +if CRYPTSETUP_VIA_DLOPEN +libmount_la_LIBADD += -ldl +else +libmount_la_LIBADD += $(CRYPTSETUP_LIBS) +endif +endif + +libmount_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + $(CRYPTSETUP_CFLAGS) \ + -I$(ul_libblkid_incdir) \ + -I$(ul_libmount_incdir) \ + -I$(top_srcdir)/libmount/src + +EXTRA_libmount_la_DEPENDENCIES = \ + libmount/src/libmount.sym + +libmount_la_LDFLAGS = $(SOLIB_LDFLAGS) +if HAVE_VSCRIPT +libmount_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libmount/src/libmount.sym +endif +libmount_la_LDFLAGS += -version-info $(LIBMOUNT_VERSION_INFO) + + +EXTRA_DIST += \ + libmount/src/libmount.sym + +if BUILD_LIBMOUNT_TESTS +check_PROGRAMS += \ + test_mount_cache \ + test_mount_lock \ + test_mount_optstr \ + test_mount_optlist \ + test_mount_tab \ + test_mount_tab_diff \ + test_mount_tab_update \ + test_mount_utils \ + test_mount_version \ + test_mount_debug +if LINUX +check_PROGRAMS += test_mount_context test_mount_context_mount +check_PROGRAMS += test_mount_monitor +endif + +libmount_tests_cflags = -DTEST_PROGRAM $(libmount_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS) +libmount_tests_ldflags = -static +libmount_tests_ldadd = libmount.la libblkid.la $(LDADD) $(REALTIME_LIBS) + +if HAVE_SELINUX +libmount_tests_ldadd += $(SELINUX_LIBS) +endif + +if HAVE_CRYPTSETUP +if CRYPTSETUP_VIA_DLOPEN +libmount_tests_ldadd += -ldl +else +libmount_tests_ldadd += $(CRYPTSETUP_LIBS) +endif +endif + +test_mount_cache_SOURCES = libmount/src/cache.c +test_mount_cache_CFLAGS = $(libmount_tests_cflags) +test_mount_cache_LDFLAGS = $(libmount_tests_ldflags) +test_mount_cache_LDADD = $(libmount_tests_ldadd) + +test_mount_context_SOURCES = libmount/src/context.c +test_mount_context_CFLAGS = $(libmount_tests_cflags) +test_mount_context_LDFLAGS = $(libmount_tests_ldflags) +test_mount_context_LDADD = $(libmount_tests_ldadd) + +test_mount_context_mount_SOURCES = libmount/src/context_mount.c +test_mount_context_mount_CFLAGS = $(libmount_tests_cflags) +test_mount_context_mount_LDFLAGS = $(libmount_tests_ldflags) +test_mount_context_mount_LDADD = $(libmount_tests_ldadd) + +test_mount_lock_SOURCES = libmount/src/lock.c +test_mount_lock_CFLAGS = $(libmount_tests_cflags) +test_mount_lock_LDFLAGS = $(libmount_tests_ldflags) +test_mount_lock_LDADD = $(libmount_tests_ldadd) + +test_mount_optstr_SOURCES = libmount/src/optstr.c +test_mount_optstr_CFLAGS = $(libmount_tests_cflags) +test_mount_optstr_LDFLAGS = $(libmount_tests_ldflags) +test_mount_optstr_LDADD = $(libmount_tests_ldadd) + +test_mount_optlist_SOURCES = libmount/src/optlist.c +test_mount_optlist_CFLAGS = $(libmount_tests_cflags) +test_mount_optlist_LDFLAGS = $(libmount_tests_ldflags) +test_mount_optlist_LDADD = $(libmount_tests_ldadd) + +test_mount_tab_SOURCES = libmount/src/tab.c +test_mount_tab_CFLAGS = $(libmount_tests_cflags) +test_mount_tab_LDFLAGS = $(libmount_tests_ldflags) +test_mount_tab_LDADD = $(libmount_tests_ldadd) + +test_mount_tab_diff_SOURCES = libmount/src/tab_diff.c +test_mount_tab_diff_CFLAGS = $(libmount_tests_cflags) +test_mount_tab_diff_LDFLAGS = $(libmount_tests_ldflags) +test_mount_tab_diff_LDADD = $(libmount_tests_ldadd) + +test_mount_monitor_SOURCES = libmount/src/monitor.c +test_mount_monitor_CFLAGS = $(libmount_tests_cflags) +test_mount_monitor_LDFLAGS = $(libmount_tests_ldflags) +test_mount_monitor_LDADD = $(libmount_tests_ldadd) + +test_mount_tab_update_SOURCES = libmount/src/tab_update.c +test_mount_tab_update_CFLAGS = $(libmount_tests_cflags) +test_mount_tab_update_LDFLAGS = $(libmount_tests_ldflags) +test_mount_tab_update_LDADD = $(libmount_tests_ldadd) + +test_mount_utils_SOURCES = libmount/src/utils.c +test_mount_utils_CFLAGS = $(libmount_tests_cflags) +test_mount_utils_LDFLAGS = $(libmount_tests_ldflags) +test_mount_utils_LDADD = $(libmount_tests_ldadd) + +test_mount_version_SOURCES = libmount/src/version.c +test_mount_version_CFLAGS = $(libmount_tests_cflags) +test_mount_version_LDFLAGS = $(libmount_tests_ldflags) +test_mount_version_LDADD = $(libmount_tests_ldadd) + +test_mount_debug_SOURCES = libmount/src/init.c +test_mount_debug_CFLAGS = $(libmount_tests_cflags) +test_mount_debug_LDFLAGS = $(libmount_tests_ldflags) +test_mount_debug_LDADD = $(libmount_tests_ldadd) + +if FUZZING_ENGINE +check_PROGRAMS += test_mount_fuzz + +test_mount_fuzz_SOURCES = libmount/src/fuzz.c + +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements +nodist_EXTRA_test_mount_fuzz_SOURCES = dummy.cxx + +test_mount_fuzz_CFLAGS = $(libmount_tests_cflags) +test_mount_fuzz_LDFLAGS = $(libmount_tests_ldflags) -lpthread +test_mount_fuzz_LDADD = $(libmount_tests_ldadd) $(LIB_FUZZING_ENGINE) +endif + +endif # BUILD_LIBMOUNT_TESTS + + +# move lib from $(usrlib_execdir) to $(libdir) if needed +install-exec-hook-libmount: + if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libmount.so"; then \ + $(MKDIR_P) $(DESTDIR)$(libdir); \ + mv $(DESTDIR)$(usrlib_execdir)/libmount.so.* $(DESTDIR)$(libdir); \ + so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libmount.so); \ + so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \ + (cd $(DESTDIR)$(usrlib_execdir) && \ + rm -f libmount.so && \ + $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libmount.so); \ + fi + +uninstall-hook-libmount: + rm -f $(DESTDIR)$(libdir)/libmount.so* + +INSTALL_EXEC_HOOKS += install-exec-hook-libmount +UNINSTALL_HOOKS += uninstall-hook-libmount diff --git a/libmount/src/btrfs.c b/libmount/src/btrfs.c new file mode 100644 index 0000000..a831ce8 --- /dev/null +++ b/libmount/src/btrfs.c @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2016 David Sterba <dsterba@suse.cz> + * Copyright (C) 2016 Stanislav Brabec <sbrabec@suse.cz> + * + * 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. + * + * Based on kernel ctree.h, rbtree.h and btrfs-progs. + */ +#include <dirent.h> +#include <sys/ioctl.h> +#include <stdlib.h> +#include <stdint.h> +#include <linux/btrfs.h> + +#include "mountP.h" +#include "bitops.h" + + +/* linux/btrfs.h lacks large parts of stuff needed for getting default + * sub-volume. Suppose that if BTRFS_DIR_ITEM_KEY is not defined, all + * declarations are still missing. + */ +#ifndef BTRFS_DIR_ITEM_KEY + +/* + * dir items are the name -> inode pointers in a directory. There is one + * for every name in a directory. + */ +#define BTRFS_DIR_ITEM_KEY 84 + +/* holds pointers to all of the tree roots */ +#define BTRFS_ROOT_TREE_OBJECTID 1ULL + +/* directory objectid inside the root tree */ +#define BTRFS_ROOT_TREE_DIR_OBJECTID 6ULL + +/* + * the key defines the order in the tree, and so it also defines (optimal) + * block layout. objectid corresponds with the inode number. The flags + * tells us things about the object, and is a kind of stream selector. + * so for a given inode, keys with flags of 1 might refer to the inode + * data, flags of 2 may point to file data in the btree and flags == 3 + * may point to extents. + * + * offset is the starting byte offset for this key in the stream. + * + * btrfs_disk_key is in disk byte order. struct btrfs_key is always + * in cpu native order. Otherwise they are identical and their sizes + * should be the same (ie both packed) + */ +struct btrfs_disk_key { + uint64_t objectid; /* little endian */ + uint8_t type; + uint64_t offset; /* little endian */ +} __attribute__ ((__packed__)); + +struct btrfs_dir_item { + struct btrfs_disk_key location; + uint64_t transid; /* little endian */ + uint16_t data_len; /* little endian */ + uint16_t name_len; /* little endian */ + uint8_t type; +} __attribute__ ((__packed__)); + +#define BTRFS_SETGET_STACK_FUNCS(name, type, member, bits) \ +static inline uint##bits##_t btrfs_##name(const type *s) \ +{ \ + return le##bits##_to_cpu(s->member); \ +} + +/* struct btrfs_disk_key */ +BTRFS_SETGET_STACK_FUNCS(disk_key_objectid, struct btrfs_disk_key, + objectid, 64) + +BTRFS_SETGET_STACK_FUNCS(stack_dir_name_len, struct btrfs_dir_item, name_len, 16) + +/* + Red Black Trees +*/ +struct rb_node { + unsigned long __rb_parent_color; + struct rb_node *rb_right; + struct rb_node *rb_left; +} __attribute__((aligned(sizeof(long)))); + /* The alignment might seem pointless, but allegedly CRIS needs it */ + +#endif /* BTRFS_DIR_ITEM_KEY */ + +/* + * btrfs_get_default_subvol_id: + * @path: Path to mounted btrfs volume + * + * Searches for the btrfs default subvolume id. + * + * Returns: default subvolume id or UINT64_MAX (-1) in case of no + * default subvolume or error. In case of error, errno is set + * properly. + */ +uint64_t btrfs_get_default_subvol_id(const char *path) +{ + int iocret; + int fd; + DIR *dirstream; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + struct btrfs_ioctl_search_header *sh; + uint64_t found = UINT64_MAX; + + dirstream = opendir(path); + if (!dirstream) { + DBG(BTRFS, ul_debug("opendir() failed for \"%s\" [errno=%d %m]", path, errno)); + return UINT64_MAX; + } + fd = dirfd(dirstream); + if (fd < 0) { + DBG(BTRFS, ul_debug("dirfd(opendir()) failed for \"%s\" [errno=%d %m]", path, errno)); + goto out; + } + + memset(&args, 0, sizeof(args)); + sk->tree_id = BTRFS_ROOT_TREE_OBJECTID; + sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID; + sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID; + sk->min_type = BTRFS_DIR_ITEM_KEY; + sk->max_type = BTRFS_DIR_ITEM_KEY; + sk->max_offset = UINT64_MAX; + sk->max_transid = UINT64_MAX; + sk->nr_items = 1; + + iocret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args); + if (iocret < 0) { + DBG(BTRFS, ul_debug("ioctl() failed for \"%s\" [errno=%d %m]", path, errno)); + goto out; + } + + /* the ioctl returns the number of items it found in nr_items */ + if (sk->nr_items == 0) { + DBG(BTRFS, ul_debug("root tree dir object id not found")); + goto out; + } + DBG(BTRFS, ul_debug("found %d root tree dir object id items", sk->nr_items)); + + sh = (struct btrfs_ioctl_search_header *)args.buf; + + if (sh->type == BTRFS_DIR_ITEM_KEY) { + struct btrfs_dir_item *di; + int name_len; + char *name; + + di = (struct btrfs_dir_item *)(sh + 1); + name_len = btrfs_stack_dir_name_len(di); + name = (char *)(di + 1); + + if (!strncmp("default", name, name_len)) { + found = btrfs_disk_key_objectid(&di->location); + DBG(BTRFS, ul_debug("\"default\" id is %llu", (unsigned long long)found)); + } else { + DBG(BTRFS, ul_debug("\"default\" id not found in tree root")); + goto out; + } + } else { + DBG(BTRFS, ul_debug("unexpected type found: %d", (int)sh->type)); + goto out; + } + +out: + closedir(dirstream); + return found; +} diff --git a/libmount/src/cache.c b/libmount/src/cache.c new file mode 100644 index 0000000..6286aeb --- /dev/null +++ b/libmount/src/cache.c @@ -0,0 +1,861 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2009-2018 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. + */ + +/** + * SECTION: cache + * @title: Cache + * @short_description: paths and tags (UUID/LABEL) caching + * + * The cache is a very simple API for working with tags (LABEL, UUID, ...) and + * paths. The cache uses libblkid as a backend for TAGs resolution. + * + * All returned paths are always canonicalized. + */ +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <limits.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <blkid.h> + +#include "canonicalize.h" +#include "mountP.h" +#include "loopdev.h" +#include "strutils.h" + +/* + * Canonicalized (resolved) paths & tags cache + */ +#define MNT_CACHE_CHUNKSZ 128 + +#define MNT_CACHE_ISTAG (1 << 1) /* entry is TAG */ +#define MNT_CACHE_ISPATH (1 << 2) /* entry is path */ +#define MNT_CACHE_TAGREAD (1 << 3) /* tag read by mnt_cache_read_tags() */ + +/* path cache entry */ +struct mnt_cache_entry { + char *key; /* search key (e.g. uncanonicalized path) */ + char *value; /* value (e.g. canonicalized path) */ + int flag; +}; + +struct libmnt_cache { + struct mnt_cache_entry *ents; + size_t nents; + size_t nallocs; + int refcount; + int probe_sb_extra; /* extra BLKID_SUBLKS_* flags */ + + /* blkid_evaluate_tag() works in two ways: + * + * 1/ all tags are evaluated by udev /dev/disk/by-* symlinks, + * then the blkid_cache is NULL. + * + * 2/ all tags are read from blkid.tab and verified by /dev + * scanning, then the blkid_cache is not NULL and then it's + * better to reuse the blkid_cache. + */ + blkid_cache bc; + + struct libmnt_table *mountinfo; +}; + +/** + * mnt_new_cache: + * + * Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error. + */ +struct libmnt_cache *mnt_new_cache(void) +{ + struct libmnt_cache *cache = calloc(1, sizeof(*cache)); + if (!cache) + return NULL; + DBG(CACHE, ul_debugobj(cache, "alloc")); + cache->refcount = 1; + return cache; +} + +/** + * mnt_free_cache: + * @cache: pointer to struct libmnt_cache instance + * + * Deallocates the cache. This function does not care about reference count. Don't + * use this function directly -- it's better to use mnt_unref_cache(). + */ +void mnt_free_cache(struct libmnt_cache *cache) +{ + size_t i; + + if (!cache) + return; + + DBG(CACHE, ul_debugobj(cache, "free [refcount=%d]", cache->refcount)); + + for (i = 0; i < cache->nents; i++) { + struct mnt_cache_entry *e = &cache->ents[i]; + if (e->value != e->key) + free(e->value); + free(e->key); + } + free(cache->ents); + if (cache->bc) + blkid_put_cache(cache->bc); + free(cache); +} + +/** + * mnt_ref_cache: + * @cache: cache pointer + * + * Increments reference counter. + */ +void mnt_ref_cache(struct libmnt_cache *cache) +{ + if (cache) { + cache->refcount++; + /*DBG(CACHE, ul_debugobj(cache, "ref=%d", cache->refcount));*/ + } +} + +/** + * mnt_unref_cache: + * @cache: cache pointer + * + * De-increments reference counter, on zero the cache is automatically + * deallocated by mnt_free_cache(). + */ +void mnt_unref_cache(struct libmnt_cache *cache) +{ + if (cache) { + cache->refcount--; + /*DBG(CACHE, ul_debugobj(cache, "unref=%d", cache->refcount));*/ + if (cache->refcount <= 0) { + mnt_unref_table(cache->mountinfo); + + mnt_free_cache(cache); + } + } +} + +/** + * mnt_cache_set_targets: + * @cache: cache pointer + * @mountinfo: table with already canonicalized mountpoints + * + * Add to @cache reference to @mountinfo. This can be used to avoid unnecessary paths + * canonicalization in mnt_resolve_target(). + * + * Returns: negative number in case of error, or 0 o success. + */ +int mnt_cache_set_targets(struct libmnt_cache *cache, + struct libmnt_table *mountinfo) +{ + if (!cache) + return -EINVAL; + + mnt_ref_table(mountinfo); + mnt_unref_table(cache->mountinfo); + cache->mountinfo = mountinfo; + return 0; +} + +/** + * mnt_cache_set_sbprobe: + * @cache: cache pointer + * @flags: BLKID_SUBLKS_* flags + * + * Add extra flags to the libblkid prober. Don't use if not sure. + * + * Returns: negative number in case of error, or 0 o success. + */ +int mnt_cache_set_sbprobe(struct libmnt_cache *cache, int flags) +{ + if (!cache) + return -EINVAL; + + cache->probe_sb_extra = flags; + return 0; +} + +/* note that the @key could be the same pointer as @value */ +static int cache_add_entry(struct libmnt_cache *cache, char *key, + char *value, int flag) +{ + struct mnt_cache_entry *e; + + assert(cache); + assert(value); + assert(key); + + if (cache->nents == cache->nallocs) { + size_t sz = cache->nallocs + MNT_CACHE_CHUNKSZ; + + e = realloc(cache->ents, sz * sizeof(struct mnt_cache_entry)); + if (!e) + return -ENOMEM; + cache->ents = e; + cache->nallocs = sz; + } + + e = &cache->ents[cache->nents]; + e->key = key; + e->value = value; + e->flag = flag; + cache->nents++; + + DBG(CACHE, ul_debugobj(cache, "add entry [%2zd] (%s): %s: %s", + cache->nents, + (flag & MNT_CACHE_ISPATH) ? "path" : "tag", + value, key)); + return 0; +} + +/* add tag to the cache, @devname has to be an allocated string */ +static int cache_add_tag(struct libmnt_cache *cache, const char *tagname, + const char *tagval, char *devname, int flag) +{ + size_t tksz, vlsz; + char *key; + int rc; + + assert(cache); + assert(devname); + assert(tagname); + assert(tagval); + + /* add into cache -- cache format for TAGs is + * key = "TAG_NAME\0TAG_VALUE\0" + * value = "/dev/foo" + */ + tksz = strlen(tagname); + vlsz = strlen(tagval); + + key = malloc(tksz + vlsz + 2); + if (!key) + return -ENOMEM; + + memcpy(key, tagname, tksz + 1); /* include '\0' */ + memcpy(key + tksz + 1, tagval, vlsz + 1); + + rc = cache_add_entry(cache, key, devname, flag | MNT_CACHE_ISTAG); + if (!rc) + return 0; + + free(key); + return rc; +} + + +/* + * Returns cached canonicalized path or NULL. + */ +static const char *cache_find_path(struct libmnt_cache *cache, const char *path) +{ + size_t i; + + if (!cache || !path) + return NULL; + + for (i = 0; i < cache->nents; i++) { + struct mnt_cache_entry *e = &cache->ents[i]; + if (!(e->flag & MNT_CACHE_ISPATH)) + continue; + if (streq_paths(path, e->key)) + return e->value; + } + return NULL; +} + +/* + * Returns cached path or NULL. + */ +static const char *cache_find_tag(struct libmnt_cache *cache, + const char *token, const char *value) +{ + size_t i; + size_t tksz; + + if (!cache || !token || !value) + return NULL; + + tksz = strlen(token); + + for (i = 0; i < cache->nents; i++) { + struct mnt_cache_entry *e = &cache->ents[i]; + if (!(e->flag & MNT_CACHE_ISTAG)) + continue; + if (strcmp(token, e->key) == 0 && + strcmp(value, e->key + tksz + 1) == 0) + return e->value; + } + return NULL; +} + +static char *cache_find_tag_value(struct libmnt_cache *cache, + const char *devname, const char *token) +{ + size_t i; + + assert(cache); + assert(devname); + assert(token); + + for (i = 0; i < cache->nents; i++) { + struct mnt_cache_entry *e = &cache->ents[i]; + if (!(e->flag & MNT_CACHE_ISTAG)) + continue; + if (strcmp(e->value, devname) == 0 && /* dev name */ + strcmp(token, e->key) == 0) /* tag name */ + return e->key + strlen(token) + 1; /* tag value */ + } + + return NULL; +} + +/** + * mnt_cache_read_tags + * @cache: pointer to struct libmnt_cache instance + * @devname: path device + * + * Reads @devname LABEL and UUID to the @cache. + * + * Returns: 0 if at least one tag was added, 1 if no tag was added or + * negative number in case of error. + */ +int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname) +{ + blkid_probe pr; + size_t i, ntags = 0; + int rc; + const char *tags[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" }; + const char *blktags[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" }; + + if (!cache || !devname) + return -EINVAL; + + DBG(CACHE, ul_debugobj(cache, "tags for %s requested", devname)); + + /* check if device is already cached */ + for (i = 0; i < cache->nents; i++) { + struct mnt_cache_entry *e = &cache->ents[i]; + if (!(e->flag & MNT_CACHE_TAGREAD)) + continue; + if (strcmp(e->value, devname) == 0) + /* tags have already been read */ + return 0; + } + + pr = blkid_new_probe_from_filename(devname); + if (!pr) + return -1; + + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_set_superblocks_flags(pr, + BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | + BLKID_SUBLKS_TYPE | cache->probe_sb_extra); + + blkid_probe_enable_partitions(pr, 1); + blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); + + rc = blkid_do_safeprobe(pr); + if (rc) + goto error; + + DBG(CACHE, ul_debugobj(cache, "reading tags for: %s", devname)); + + for (i = 0; i < ARRAY_SIZE(tags); i++) { + const char *data; + char *dev; + + if (cache_find_tag_value(cache, devname, tags[i])) { + DBG(CACHE, ul_debugobj(cache, + "\ntag %s already cached", tags[i])); + continue; + } + if (blkid_probe_lookup_value(pr, blktags[i], &data, NULL)) + continue; + dev = strdup(devname); + if (!dev) + goto error; + if (cache_add_tag(cache, tags[i], data, dev, + MNT_CACHE_TAGREAD)) { + free(dev); + goto error; + } + ntags++; + } + + DBG(CACHE, ul_debugobj(cache, "\tread %zd tags", ntags)); + blkid_free_probe(pr); + return ntags ? 0 : 1; +error: + blkid_free_probe(pr); + return rc < 0 ? rc : -1; +} + +/** + * mnt_cache_device_has_tag: + * @cache: paths cache + * @devname: path to the device + * @token: tag name (e.g "LABEL") + * @value: tag value + * + * Look up @cache to check if @tag+@value are associated with @devname. + * + * Returns: 1 on success or 0. + */ +int mnt_cache_device_has_tag(struct libmnt_cache *cache, const char *devname, + const char *token, const char *value) +{ + const char *path = cache_find_tag(cache, token, value); + + if (path && devname && strcmp(path, devname) == 0) + return 1; + return 0; +} + +static int __mnt_cache_find_tag_value(struct libmnt_cache *cache, + const char *devname, const char *token, char **data) +{ + int rc = 0; + + if (!cache || !devname || !token || !data) + return -EINVAL; + + rc = mnt_cache_read_tags(cache, devname); + if (rc) + return rc; + + *data = cache_find_tag_value(cache, devname, token); + return *data ? 0 : -1; +} + +/** + * mnt_cache_find_tag_value: + * @cache: cache for results + * @devname: device name + * @token: tag name ("LABEL" or "UUID") + * + * Returns: LABEL or UUID for the @devname or NULL in case of error. + */ +char *mnt_cache_find_tag_value(struct libmnt_cache *cache, + const char *devname, const char *token) +{ + char *data = NULL; + + if (__mnt_cache_find_tag_value(cache, devname, token, &data) == 0) + return data; + return NULL; +} + +/** + * mnt_get_fstype: + * @devname: device name + * @ambi: returns TRUE if probing result is ambivalent (optional argument) + * @cache: cache for results or NULL + * + * Returns: filesystem type or NULL in case of error. The result has to be + * deallocated by free() if @cache is NULL. + */ +char *mnt_get_fstype(const char *devname, int *ambi, struct libmnt_cache *cache) +{ + blkid_probe pr; + const char *data; + char *type = NULL; + int rc; + + DBG(CACHE, ul_debugobj(cache, "get %s FS type", devname)); + + if (cache) { + char *val = NULL; + rc = __mnt_cache_find_tag_value(cache, devname, "TYPE", &val); + if (ambi) + *ambi = rc == -2 ? TRUE : FALSE; + return rc ? NULL : val; + } + + /* + * no cache, probe directly + */ + pr = blkid_new_probe_from_filename(devname); + if (!pr) + return NULL; + + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE); + + rc = blkid_do_safeprobe(pr); + + DBG(CACHE, ul_debugobj(cache, "libblkid rc=%d", rc)); + + if (!rc && !blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) + type = strdup(data); + + if (ambi) + *ambi = rc == -2 ? TRUE : FALSE; + + blkid_free_probe(pr); + return type; +} + +static char *canonicalize_path_and_cache(const char *path, + struct libmnt_cache *cache) +{ + char *p; + char *key; + char *value; + + DBG(CACHE, ul_debugobj(cache, "canonicalize path %s", path)); + p = canonicalize_path(path); + + if (p && cache) { + value = p; + key = strcmp(path, p) == 0 ? value : strdup(path); + + if (!key || !value) + goto error; + + if (cache_add_entry(cache, key, value, + MNT_CACHE_ISPATH)) + goto error; + } + + return p; +error: + if (value != key) + free(value); + free(key); + return NULL; +} + +/** + * mnt_resolve_path: + * @path: "native" path + * @cache: cache for results or NULL + * + * Converts path: + * - to the absolute path + * - /dev/dm-N to /dev/mapper/name + * + * Returns: absolute path or NULL in case of error. The result has to be + * deallocated by free() if @cache is NULL. + */ +char *mnt_resolve_path(const char *path, struct libmnt_cache *cache) +{ + char *p = NULL; + + /*DBG(CACHE, ul_debugobj(cache, "resolving path %s", path));*/ + + if (!path) + return NULL; + if (cache) + p = (char *) cache_find_path(cache, path); + if (!p) + p = canonicalize_path_and_cache(path, cache); + + return p; +} + +/** + * mnt_resolve_target: + * @path: "native" path, a potential mount point + * @cache: cache for results or NULL. + * + * Like mnt_resolve_path(), unless @cache is not NULL and + * mnt_cache_set_targets(cache, mountinfo) was called: if @path is found in the + * cached @mountinfo and the matching entry was provided by the kernel, assume that + * @path is already canonicalized. By avoiding a call to realpath(2) on + * known mount points, there is a lower risk of stepping on a stale mount + * point, which can result in an application freeze. This is also faster in + * general, as stat(2) on a mount point is slower than on a regular file. + * + * Returns: absolute path or NULL in case of error. The result has to be + * deallocated by free() if @cache is NULL. + */ +char *mnt_resolve_target(const char *path, struct libmnt_cache *cache) +{ + char *p = NULL; + + if (!path) + return NULL; + + /*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/ + + if (!cache || !cache->mountinfo) + return mnt_resolve_path(path, cache); + + p = (char *) cache_find_path(cache, path); + if (p) + return p; + + { + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + while (mnt_table_next_fs(cache->mountinfo, &itr, &fs) == 0) { + + if (!mnt_fs_is_kernel(fs) + || mnt_fs_is_swaparea(fs) + || !mnt_fs_streq_target(fs, path)) + continue; + + p = strdup(path); + if (!p) + return NULL; /* ENOMEM */ + + if (cache_add_entry(cache, p, p, MNT_CACHE_ISPATH)) { + free(p); + return NULL; /* ENOMEM */ + } + break; + } + } + + if (!p) + p = canonicalize_path_and_cache(path, cache); + return p; +} + +/** + * mnt_pretty_path: + * @path: any path + * @cache: NULL or pointer to the cache + * + * Converts path: + * - to the absolute path + * - /dev/dm-N to /dev/mapper/name + * - /dev/loopN to the loop backing filename + * - empty path (NULL) to 'none' + * + * Returns: newly allocated string with path, result always has to be deallocated + * by free(). + */ +char *mnt_pretty_path(const char *path, struct libmnt_cache *cache) +{ + char *pretty = mnt_resolve_path(path, cache); + + if (!pretty) + return strdup("none"); + +#ifdef __linux__ + /* users assume backing file name rather than /dev/loopN in + * output if the device has been initialized by mount(8). + */ + if (strncmp(pretty, "/dev/loop", 9) == 0) { + struct loopdev_cxt lc; + + if (loopcxt_init(&lc, 0) || loopcxt_set_device(&lc, pretty)) + goto done; + + if (loopcxt_is_autoclear(&lc)) { + char *tmp = loopcxt_get_backing_file(&lc); + if (tmp) { + loopcxt_deinit(&lc); + if (!cache) + free(pretty); /* not cached, deallocate */ + return tmp; /* return backing file */ + } + } + loopcxt_deinit(&lc); + + } +#endif + +done: + /* don't return pointer to the cache, allocate a new string */ + return cache ? strdup(pretty) : pretty; +} + +/** + * mnt_resolve_tag: + * @token: tag name + * @value: tag value + * @cache: for results or NULL + * + * Returns: device name or NULL in case of error. The result has to be + * deallocated by free() if @cache is NULL. + */ +char *mnt_resolve_tag(const char *token, const char *value, + struct libmnt_cache *cache) +{ + char *p = NULL; + + /*DBG(CACHE, ul_debugobj(cache, "resolving tag token=%s value=%s", + token, value));*/ + + if (!token || !value) + return NULL; + + if (cache) + p = (char *) cache_find_tag(cache, token, value); + + if (!p) { + /* returns newly allocated string */ + p = blkid_evaluate_tag(token, value, cache ? &cache->bc : NULL); + + if (p && cache && + cache_add_tag(cache, token, value, p, 0)) + goto error; + } + + return p; +error: + free(p); + return NULL; +} + + + +/** + * mnt_resolve_spec: + * @spec: path or tag + * @cache: paths cache + * + * Returns: canonicalized path or NULL. The result has to be + * deallocated by free() if @cache is NULL. + */ +char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache) +{ + char *cn = NULL; + char *t = NULL, *v = NULL; + + if (!spec) + return NULL; + + if (blkid_parse_tag_string(spec, &t, &v) == 0 && mnt_valid_tagname(t)) + cn = mnt_resolve_tag(t, v, cache); + else + cn = mnt_resolve_path(spec, cache); + + free(t); + free(v); + return cn; +} + + +#ifdef TEST_PROGRAM + +static int test_resolve_path(struct libmnt_test *ts, int argc, char *argv[]) +{ + char line[BUFSIZ]; + struct libmnt_cache *cache; + + cache = mnt_new_cache(); + if (!cache) + return -ENOMEM; + + while(fgets(line, sizeof(line), stdin)) { + size_t sz = strlen(line); + char *p; + + if (sz > 0 && line[sz - 1] == '\n') + line[sz - 1] = '\0'; + + p = mnt_resolve_path(line, cache); + printf("%s : %s\n", line, p); + } + mnt_unref_cache(cache); + return 0; +} + +static int test_resolve_spec(struct libmnt_test *ts, int argc, char *argv[]) +{ + char line[BUFSIZ]; + struct libmnt_cache *cache; + + cache = mnt_new_cache(); + if (!cache) + return -ENOMEM; + + while(fgets(line, sizeof(line), stdin)) { + size_t sz = strlen(line); + char *p; + + if (sz > 0 && line[sz - 1] == '\n') + line[sz - 1] = '\0'; + + p = mnt_resolve_spec(line, cache); + printf("%s : %s\n", line, p); + } + mnt_unref_cache(cache); + return 0; +} + +static int test_read_tags(struct libmnt_test *ts, int argc, char *argv[]) +{ + char line[BUFSIZ]; + struct libmnt_cache *cache; + size_t i; + + cache = mnt_new_cache(); + if (!cache) + return -ENOMEM; + + while(fgets(line, sizeof(line), stdin)) { + size_t sz = strlen(line); + char *t = NULL, *v = NULL; + + if (sz > 0 && line[sz - 1] == '\n') + line[sz - 1] = '\0'; + + if (!strcmp(line, "quit")) + break; + + if (*line == '/') { + if (mnt_cache_read_tags(cache, line) < 0) + fprintf(stderr, "%s: read tags failed\n", line); + + } else if (blkid_parse_tag_string(line, &t, &v) == 0) { + const char *cn = NULL; + + if (mnt_valid_tagname(t)) + cn = cache_find_tag(cache, t, v); + free(t); + free(v); + + if (cn) + printf("%s: %s\n", line, cn); + else + printf("%s: not cached\n", line); + } + } + + for (i = 0; i < cache->nents; i++) { + struct mnt_cache_entry *e = &cache->ents[i]; + if (!(e->flag & MNT_CACHE_ISTAG)) + continue; + + printf("%15s : %5s : %s\n", e->value, e->key, + e->key + strlen(e->key) + 1); + } + + mnt_unref_cache(cache); + return 0; + +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test ts[] = { + { "--resolve-path", test_resolve_path, " resolve paths from stdin" }, + { "--resolve-spec", test_resolve_spec, " evaluate specs from stdin" }, + { "--read-tags", test_read_tags, " read devname or TAG from stdin (\"quit\" to exit)" }, + { NULL } + }; + + return mnt_run_test(ts, argc, argv); +} +#endif diff --git a/libmount/src/context.c b/libmount/src/context.c new file mode 100644 index 0000000..0cd3201 --- /dev/null +++ b/libmount/src/context.c @@ -0,0 +1,3455 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2010-2018 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. + */ + +/** + * SECTION: context + * @title: Library high-level context + * @short_description: high-level API to mount/umount devices. + * + * <informalexample> + * <programlisting> + * struct libmnt_context *cxt = mnt_new_context(); + * + * mnt_context_set_options(cxt, "aaa,bbb,ccc=CCC"); + * mnt_context_set_mflags(cxt, MS_NOATIME|MS_NOEXEC); + * mnt_context_set_target(cxt, "/mnt/foo"); + * + * if (!mnt_context_mount(cxt)) + * printf("successfully mounted\n"); + * mnt_free_context(cxt); + * + * </programlisting> + * </informalexample> + * + * This code is similar to: + * + * mount -o aaa,bbb,ccc=CCC,noatime,noexec /mnt/foo + * + */ + +#include "mountP.h" +#include "strutils.h" +#include "namespace.h" +#include "match.h" + +#include <sys/wait.h> + +/** + * mnt_new_context: + * + * Returns: newly allocated mount context + */ +struct libmnt_context *mnt_new_context(void) +{ + struct libmnt_context *cxt; + uid_t ruid, euid; + + cxt = calloc(1, sizeof(*cxt)); + if (!cxt) + return NULL; + + ruid = getuid(); + euid = geteuid(); + + mnt_context_reset_status(cxt); + + cxt->ns_orig.fd = -1; + cxt->ns_tgt.fd = -1; + cxt->ns_cur = &cxt->ns_orig; + + cxt->map_linux = mnt_get_builtin_optmap(MNT_LINUX_MAP); + cxt->map_userspace = mnt_get_builtin_optmap(MNT_USERSPACE_MAP); + + INIT_LIST_HEAD(&cxt->hooksets_hooks); + INIT_LIST_HEAD(&cxt->hooksets_datas); + + /* if we're really root and aren't running setuid */ + cxt->restricted = (uid_t) 0 == ruid && ruid == euid ? 0 : 1; + + cxt->noautofs = 0; + + DBG(CXT, ul_debugobj(cxt, "----> allocate %s", + cxt->restricted ? "[RESTRICTED]" : "")); + + return cxt; +} + +/** + * mnt_free_context: + * @cxt: mount context + * + * Deallocates context struct. + */ +void mnt_free_context(struct libmnt_context *cxt) +{ + if (!cxt) + return; + + mnt_reset_context(cxt); + + free(cxt->fstype_pattern); + free(cxt->optstr_pattern); + free(cxt->tgt_prefix); + + mnt_unref_table(cxt->fstab); + mnt_unref_cache(cxt->cache); + mnt_unref_fs(cxt->fs); + + mnt_unref_optlist(cxt->optlist_saved); + mnt_unref_optlist(cxt->optlist); + + mnt_free_lock(cxt->lock); + mnt_free_update(cxt->update); + + mnt_context_set_target_ns(cxt, NULL); + + free(cxt->children); + + DBG(CXT, ul_debugobj(cxt, "free")); + free(cxt); +} + +/** + * mnt_reset_context: + * @cxt: mount context + * + * Resets all information in the context that is directly related to + * the latest mount (spec, source, target, mount options, ...). + * + * The match patterns, target namespace, prefix, cached fstab, cached canonicalized + * paths and tags and [e]uid are not reset. You have to use + * + * mnt_context_set_fstab(cxt, NULL); + * mnt_context_set_cache(cxt, NULL); + * mnt_context_set_fstype_pattern(cxt, NULL); + * mnt_context_set_options_pattern(cxt, NULL); + * mnt_context_set_target_ns(cxt, NULL); + * + * to reset this stuff. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_reset_context(struct libmnt_context *cxt) +{ + int fl; + + if (!cxt) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "<---- reset [status=%d] ---->", + mnt_context_get_status(cxt))); + + fl = cxt->flags; + + mnt_unref_fs(cxt->fs); + mnt_unref_table(cxt->mountinfo); + mnt_unref_table(cxt->utab); + mnt_unref_optlist(cxt->optlist); + + free(cxt->helper); + + cxt->fs = NULL; + cxt->mountinfo = NULL; + cxt->optlist = NULL; + cxt->utab = NULL; + cxt->helper = NULL; + cxt->mountdata = NULL; + cxt->flags = MNT_FL_DEFAULT; + cxt->noautofs = 0; + cxt->has_selinux_opt = 0; + + cxt->map_linux = mnt_get_builtin_optmap(MNT_LINUX_MAP); + cxt->map_userspace = mnt_get_builtin_optmap(MNT_USERSPACE_MAP); + + mnt_context_reset_status(cxt); + mnt_context_deinit_hooksets(cxt); + + if (cxt->table_fltrcb) + mnt_context_set_tabfilter(cxt, NULL, NULL); + + /* restore non-resettable flags */ + cxt->flags |= (fl & MNT_FL_NOMTAB); + cxt->flags |= (fl & MNT_FL_FAKE); + cxt->flags |= (fl & MNT_FL_SLOPPY); + cxt->flags |= (fl & MNT_FL_VERBOSE); + cxt->flags |= (fl & MNT_FL_NOHELPERS); + cxt->flags |= (fl & MNT_FL_LOOPDEL); + cxt->flags |= (fl & MNT_FL_LAZY); + cxt->flags |= (fl & MNT_FL_FORK); + cxt->flags |= (fl & MNT_FL_FORCE); + cxt->flags |= (fl & MNT_FL_NOCANONICALIZE); + cxt->flags |= (fl & MNT_FL_RDONLY_UMOUNT); + cxt->flags |= (fl & MNT_FL_RWONLY_MOUNT); + cxt->flags |= (fl & MNT_FL_NOSWAPMATCH); + cxt->flags |= (fl & MNT_FL_TABPATHS_CHECKED); + + mnt_context_apply_template(cxt); + + return 0; +} + +/* + * Saves the current context setting (mount options, etc) to make it usable after + * mnt_reset_context() or by mnt_context_apply_template(). This is usable for + * example for mnt_context_next_mount() where for the next mount operation we + * need to restore to the original context setting. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_save_template(struct libmnt_context *cxt) +{ + if (!cxt) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "saving template")); + + /* reset old saved data */ + mnt_unref_optlist(cxt->optlist_saved); + cxt->optlist_saved = NULL; + + if (cxt->optlist) + cxt->optlist_saved = mnt_copy_optlist(cxt->optlist); + + return 0; +} + +/* + * Restores context FS setting from previously saved template (see + * mnt_context_save_template()). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_apply_template(struct libmnt_context *cxt) +{ + if (!cxt) + return -EINVAL; + + if (cxt->optlist) { + mnt_unref_optlist(cxt->optlist); + cxt->optlist = NULL; + } + + if (cxt->optlist_saved) { + DBG(CXT, ul_debugobj(cxt, "restoring template")); + cxt->optlist = mnt_copy_optlist(cxt->optlist_saved); + } + + return 0; +} + +int mnt_context_has_template(struct libmnt_context *cxt) +{ + return cxt && cxt->optlist_saved ? 1 : 0; +} + +struct libmnt_context *mnt_copy_context(struct libmnt_context *o) +{ + struct libmnt_context *n; + + n = mnt_new_context(); + if (!n) + return NULL; + + DBG(CXT, ul_debugobj(n, "<---- clone ---->")); + + n->flags = o->flags; + + if (o->fs) { + n->fs = mnt_copy_fs(NULL, o->fs); + if (!n->fs) + goto failed; + } + + n->mountinfo = o->mountinfo; + mnt_ref_table(n->mountinfo); + + n->utab = o->utab; + mnt_ref_table(n->utab); + + if (strdup_between_structs(n, o, tgt_prefix)) + goto failed; + if (strdup_between_structs(n, o, helper)) + goto failed; + + n->map_linux = o->map_linux; + n->map_userspace = o->map_userspace; + + mnt_context_reset_status(n); + + n->table_fltrcb = o->table_fltrcb; + n->table_fltrcb_data = o->table_fltrcb_data; + + n->noautofs = o->noautofs; + n->has_selinux_opt = o->has_selinux_opt; + + return n; +failed: + mnt_free_context(n); + return NULL; +} + +/** + * mnt_context_reset_status: + * @cxt: context + * + * Resets mount(2) and mount.type statuses, so mnt_context_do_mount() or + * mnt_context_do_umount() could be again called with the same settings. + * + * BE CAREFUL -- after this soft reset the libmount will NOT parse mount + * options, evaluate permissions or apply stuff from fstab. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_reset_status(struct libmnt_context *cxt) +{ + if (!cxt) + return -EINVAL; + + cxt->syscall_status = 1; /* means not called yet */ + cxt->helper_exec_status = 1; + cxt->helper_status = 0; + return 0; +} + +static int context_init_paths(struct libmnt_context *cxt, int writable) +{ + struct libmnt_ns *ns_old; + + assert(cxt); + + if (!cxt->utab_path) { + cxt->utab_path = mnt_get_utab_path(); + DBG(CXT, ul_debugobj(cxt, "utab path initialized to: %s", cxt->utab_path)); + } + + if (!writable) + return 0; /* only paths wanted */ + if (mnt_context_is_nomtab(cxt)) + return 0; /* write mode overridden by mount -n */ + if (cxt->flags & MNT_FL_TABPATHS_CHECKED) + return 0; + + DBG(CXT, ul_debugobj(cxt, "checking for writable tab files")); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + mnt_has_regular_utab(&cxt->utab_path, &cxt->utab_writable); + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + cxt->flags |= MNT_FL_TABPATHS_CHECKED; + return 0; +} + +int mnt_context_utab_writable(struct libmnt_context *cxt) +{ + assert(cxt); + + context_init_paths(cxt, 1); + return cxt->utab_writable == 1; +} + +const char *mnt_context_get_writable_tabpath(struct libmnt_context *cxt) +{ + assert(cxt); + + context_init_paths(cxt, 1); + return cxt->utab_path; +} + + +static int set_flag(struct libmnt_context *cxt, int flag, int enable) +{ + if (!cxt) + return -EINVAL; + if (enable) { + DBG(CXT, ul_debugobj(cxt, "enabling flag %04x", flag)); + cxt->flags |= flag; + } else { + DBG(CXT, ul_debugobj(cxt, "disabling flag %04x", flag)); + cxt->flags &= ~flag; + } + return 0; +} + +/** + * mnt_context_is_restricted: + * @cxt: mount context + * + * Returns: 0 for an unrestricted mount (user is root), or 1 for non-root mounts + */ +int mnt_context_is_restricted(struct libmnt_context *cxt) +{ + return cxt->restricted; +} + +/** + * mnt_context_force_unrestricted: + * @cxt: mount context + * + * This function is DANGEROURS as it disables all security policies in libmount. + * Don't use if not sure. It removes "restricted" flag from the context, so + * libmount will use the current context as for root user. + * + * This function is designed for case you have no any suid permissions, so you + * can depend on kernel. + * + * Returns: 0 on success, negative number in case of error. + * + * Since: 2.35 + */ +int mnt_context_force_unrestricted(struct libmnt_context *cxt) +{ + if (mnt_context_is_restricted(cxt)) { + DBG(CXT, ul_debugobj(cxt, "force UNRESTRICTED")); + cxt->restricted = 0; + } + + return 0; +} + +/** + * mnt_context_set_optsmode + * @cxt: mount context + * @mode: MNT_OMODE_* flags + * + * Controls how to use mount optionssource and target paths from fstab/mountinfo. + * + * @MNT_OMODE_IGNORE: ignore fstab options + * + * @MNT_OMODE_APPEND: append fstab options to existing options + * + * @MNT_OMODE_PREPEND: prepend fstab options to existing options + * + * @MNT_OMODE_REPLACE: replace existing options with options from fstab + * + * @MNT_OMODE_FORCE: always read fstab (although source and target are defined) + * + * @MNT_OMODE_FSTAB: read from fstab + * + * @MNT_OMODE_MTAB: read from mountinfo if fstab not enabled or failed + * + * @MNT_OMODE_NOTAB: do not read fstab/mountinfoat all + * + * @MNT_OMODE_AUTO: default mode (MNT_OMODE_PREPEND | MNT_OMODE_FSTAB | MNT_OMODE_MTAB) + * + * @MNT_OMODE_USER: default for non-root users (MNT_OMODE_REPLACE | MNT_OMODE_FORCE | MNT_OMODE_FSTAB) + * + * Notes: + * + * - MNT_OMODE_USER is always used if mount context is in restricted mode + * - MNT_OMODE_AUTO is used if nothing else is defined + * - the flags are evaluated in this order: MNT_OMODE_NOTAB, MNT_OMODE_FORCE, + * MNT_OMODE_FSTAB, MNT_OMODE_MTAB and then the mount options from fstab/mountinfo + * are set according to MNT_OMODE_{IGNORE,APPEND,PREPEND,REPLACE} + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_optsmode(struct libmnt_context *cxt, int mode) +{ + if (!cxt) + return -EINVAL; + cxt->optsmode = mode; + return 0; +} + +/** + * mnt_context_get_optsmode + * @cxt: mount context + * + * Returns: MNT_OMODE_* mask or zero. + */ + +int mnt_context_get_optsmode(struct libmnt_context *cxt) +{ + return cxt->optsmode; +} + +/** + * mnt_context_disable_canonicalize: + * @cxt: mount context + * @disable: TRUE or FALSE + * + * Enable/disable paths canonicalization and tags evaluation. The libmount context + * canonicalizes paths when searching in fstab and when preparing source and target paths + * for mount(2) syscall. + * + * This function has an effect on the private (within context) fstab instance only + * (see mnt_context_set_fstab()). If you want to use an external fstab then you + * need to manage your private struct libmnt_cache (see mnt_table_set_cache(fstab, + * NULL). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_disable_canonicalize(struct libmnt_context *cxt, int disable) +{ + return set_flag(cxt, MNT_FL_NOCANONICALIZE, disable); +} + +/** + * mnt_context_is_nocanonicalize: + * @cxt: mount context + * + * Returns: 1 if no-canonicalize mode is enabled or 0. + */ +int mnt_context_is_nocanonicalize(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_NOCANONICALIZE ? 1 : 0; +} + +/** + * mnt_context_enable_lazy: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable lazy umount (see umount(8) man page, option -l). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_lazy(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_LAZY, enable); +} + +/** + * mnt_context_is_lazy: + * @cxt: mount context + * + * Returns: 1 if lazy umount is enabled or 0 + */ +int mnt_context_is_lazy(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_LAZY ? 1 : 0; +} + +/** + * mnt_context_enable_onlyonce: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable only-once mount (check if FS is not already mounted). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_onlyonce(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_ONLYONCE, enable); +} + +/** + * mnt_context_is_lazy: + * @cxt: mount context + * + * Returns: 1 if lazy umount is enabled or 0 + */ +int mnt_context_is_onlyonce(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_ONLYONCE ? 1 : 0; +} + +/** + * mnt_context_enable_fork: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable fork(2) call in mnt_context_next_mount() (see mount(8) man + * page, option -F). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_fork(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_FORK, enable); +} + +/** + * mnt_context_is_fork: + * @cxt: mount context + * + * Returns: 1 if fork (mount -F) is enabled or 0 + */ +int mnt_context_is_fork(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_FORK ? 1 : 0; +} + +/** + * mnt_context_is_parent: + * @cxt: mount context + * + * Return: 1 if mount -F enabled and the current context is parent, or 0 + */ +int mnt_context_is_parent(struct libmnt_context *cxt) +{ + return mnt_context_is_fork(cxt) && cxt->pid == 0; +} + +/** + * mnt_context_is_child: + * @cxt: mount context + * + * Return: 1 f the current context is child, or 0 + */ +int mnt_context_is_child(struct libmnt_context *cxt) +{ + /* See mnt_fork_context(), the for fork flag is always disabled + * for children to avoid recursive forking. + */ + return !mnt_context_is_fork(cxt) && cxt->pid; +} + +/** + * mnt_context_enable_rdonly_umount: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable read-only remount on failed umount(2) + * (see umount(8) man page, option -r). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_rdonly_umount(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_RDONLY_UMOUNT, enable); +} + +/** + * mnt_context_is_rdonly_umount + * @cxt: mount context + * + * See also mnt_context_enable_rdonly_umount() and umount(8) man page, + * option -r. + * + * Returns: 1 if read-only remount failed umount(2) is enables or 0 + */ +int mnt_context_is_rdonly_umount(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_RDONLY_UMOUNT ? 1 : 0; +} + +/** + * mnt_context_enable_rwonly_mount: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Force read-write mount; if enabled libmount will never try MS_RDONLY + * after failed mount(2) EROFS. (See mount(8) man page, option -w). + * + * Since: 2.30 + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_rwonly_mount(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_RWONLY_MOUNT, enable); +} + +/** + * mnt_context_is_rwonly_mount + * @cxt: mount context + * + * See also mnt_context_enable_rwonly_mount() and mount(8) man page, + * option -w. + * + * Since: 2.30 + * + * Returns: 1 if only read-write mount is allowed. + */ +int mnt_context_is_rwonly_mount(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_RWONLY_MOUNT ? 1 : 0; +} + +/** + * mnt_context_forced_rdonly: + * @cxt: mount context + * + * See also mnt_context_enable_rwonly_mount(). + * + * Since: 2.30 + * + * Returns: 1 if mounted read-only on write-protected device. + */ +int mnt_context_forced_rdonly(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_FORCED_RDONLY ? 1 : 0; +} + +/** + * mnt_context_disable_helpers: + * @cxt: mount context + * @disable: TRUE or FALSE + * + * Enable/disable /sbin/[u]mount.* helpers (see mount(8) man page, option -i). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_disable_helpers(struct libmnt_context *cxt, int disable) +{ + return set_flag(cxt, MNT_FL_NOHELPERS, disable); +} + +/** + * mnt_context_is_nohelpers + * @cxt: mount context + * + * Returns: 1 if helpers are disabled (mount -i) or 0 + */ +int mnt_context_is_nohelpers(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_NOHELPERS ? 1 : 0; +} + + +/** + * mnt_context_enable_sloppy: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Set/unset sloppy mounting (see mount(8) man page, option -s). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_sloppy(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_SLOPPY, enable); +} + +/** + * mnt_context_is_sloppy: + * @cxt: mount context + * + * Returns: 1 if sloppy flag is enabled or 0 + */ +int mnt_context_is_sloppy(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_SLOPPY ? 1 : 0; +} + +/** + * mnt_context_enable_fake: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable fake mounting (see mount(8) man page, option -f). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_fake(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_FAKE, enable); +} + +/** + * mnt_context_is_fake: + * @cxt: mount context + * + * Returns: 1 if fake flag is enabled or 0 + */ +int mnt_context_is_fake(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_FAKE ? 1 : 0; +} + +/** + * mnt_context_disable_mtab: + * @cxt: mount context + * @disable: TRUE or FALSE + * + * Disable/enable userspace mount table update (see mount(8) man page, + * option -n). Originally /etc/mtab, now /run/mount/utab. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_disable_mtab(struct libmnt_context *cxt, int disable) +{ + return set_flag(cxt, MNT_FL_NOMTAB, disable); +} + +/** + * mnt_context_is_nomtab: + * @cxt: mount context + * + * Returns: 1 if no-mtab is enabled or 0 + */ +int mnt_context_is_nomtab(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_NOMTAB ? 1 : 0; +} + +/** + * mnt_context_disable_swapmatch: + * @cxt: mount context + * @disable: TRUE or FALSE + * + * Disable/enable swap between source and target for mount(8) if only one path + * is specified. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_disable_swapmatch(struct libmnt_context *cxt, int disable) +{ + return set_flag(cxt, MNT_FL_NOSWAPMATCH, disable); +} + +/** + * mnt_context_is_swapmatch: + * @cxt: mount context + * + * Returns: 1 if swap between source and target is allowed (default is 1) or 0. + */ +int mnt_context_is_swapmatch(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_NOSWAPMATCH ? 0 : 1; +} + +/** + * mnt_context_enable_force: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable force umounting (see umount(8) man page, option -f). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_force(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_FORCE, enable); +} + +/** + * mnt_context_is_force + * @cxt: mount context + * + * Returns: 1 if force umounting flag is enabled or 0 + */ +int mnt_context_is_force(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_FORCE ? 1 : 0; +} + +/** + * mnt_context_enable_verbose: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable verbose output (TODO: not implemented yet) + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_verbose(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_VERBOSE, enable); +} + +/** + * mnt_context_is_verbose + * @cxt: mount context + * + * Returns: 1 if verbose flag is enabled or 0 + */ +int mnt_context_is_verbose(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_VERBOSE ? 1 : 0; +} + +/** + * mnt_context_enable_loopdel: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable the loop delete (destroy) after umount (see umount(8), option -d) + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_enable_loopdel(struct libmnt_context *cxt, int enable) +{ + return set_flag(cxt, MNT_FL_LOOPDEL, enable); +} + +/** + * mnt_context_is_loopdel: + * @cxt: mount context + * + * Returns: 1 if loop device should be deleted after umount (umount -d) or 0. + */ +int mnt_context_is_loopdel(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_LOOPDEL ? 1 : 0; +} + +/** + * mnt_context_set_fs: + * @cxt: mount context + * @fs: filesystem description + * + * The mount context uses private @fs by default. This function can be used to + * overwrite the private @fs with an external instance. This function + * increments @fs reference counter (and decrement reference counter of the + * old fs). + * + * The @fs will be modified by mnt_context_set_{source,target,options,fstype} + * functions, If the @fs is NULL, then all current FS specific settings (source, + * target, etc., exclude spec) are reset. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_fs(struct libmnt_context *cxt, struct libmnt_fs *fs) +{ + if (!cxt) + return -EINVAL; + + if (cxt->fs == fs) + return 0; + + DBG(CXT, ul_debugobj(cxt, "setting new FS")); + + /* new */ + if (fs) { + struct libmnt_optlist *ol = mnt_context_get_optlist(cxt); + + if (!ol) + return -ENOMEM; + + mnt_ref_fs(fs); + + mnt_optlist_set_optstr(ol, mnt_fs_get_options(fs), NULL); + mnt_fs_follow_optlist(fs, ol); + } + + /* old */ + if (cxt->fs) + mnt_fs_follow_optlist(cxt->fs, NULL); + mnt_unref_fs(cxt->fs); + + cxt->fs = fs; + return 0; +} + +/** + * mnt_context_get_fs: + * @cxt: mount context + * + * The FS contains the basic description of mountpoint, fs type and so on. + * Note that the FS is modified by mnt_context_set_{source,target,options,fstype} + * functions. + * + * Returns: pointer to FS description or NULL in case of a calloc() error. + */ +struct libmnt_fs *mnt_context_get_fs(struct libmnt_context *cxt) +{ + if (!cxt) + return NULL; + if (!cxt->fs) { + struct libmnt_optlist *ol = mnt_context_get_optlist(cxt); + + if (!ol) + return NULL; + cxt->fs = mnt_new_fs(); + if (!cxt->fs) + return NULL; + + mnt_fs_follow_optlist(cxt->fs, ol); + } + return cxt->fs; +} + +/** + * mnt_context_get_fs_userdata: + * @cxt: mount context + * + * Returns: pointer to userdata or NULL. + */ +void *mnt_context_get_fs_userdata(struct libmnt_context *cxt) +{ + return cxt->fs ? mnt_fs_get_userdata(cxt->fs) : NULL; +} + +/** + * mnt_context_get_fstab_userdata: + * @cxt: mount context + * + * Returns: pointer to userdata or NULL. + */ +void *mnt_context_get_fstab_userdata(struct libmnt_context *cxt) +{ + return cxt->fstab ? mnt_table_get_userdata(cxt->fstab) : NULL; +} + +/** + * mnt_context_get_mtab_userdata: + * @cxt: mount context + * + * The file /etc/mtab is no more used, @context points always to mountinfo + * (/proc/self/mountinfo). The function uses "mtab" in the name for backward + * compatibility only. + * + * Returns: pointer to userdata or NULL. + */ +void *mnt_context_get_mtab_userdata(struct libmnt_context *cxt) +{ + return cxt->mountinfo ? mnt_table_get_userdata(cxt->mountinfo) : NULL; +} + +/** + * mnt_context_set_source: + * @cxt: mount context + * @source: mount source (device, directory, UUID, LABEL, ...) + * + * Note that libmount does not interpret "nofail" (MNT_MS_NOFAIL) + * mount option. The real return code is always returned, when + * the device does not exist then it's usually MNT_ERR_NOSOURCE + * from libmount or ENOENT, ENOTDIR, ENOTBLK, ENXIO from mount(2). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_source(struct libmnt_context *cxt, const char *source) +{ + return mnt_fs_set_source(mnt_context_get_fs(cxt), source); +} + +/** + * mnt_context_get_source: + * @cxt: mount context + * + * Returns: returns pointer or NULL in case of error or if not set. + */ +const char *mnt_context_get_source(struct libmnt_context *cxt) +{ + return mnt_fs_get_source(mnt_context_get_fs(cxt)); +} + +/** + * mnt_context_set_target: + * @cxt: mount context + * @target: mountpoint + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_target(struct libmnt_context *cxt, const char *target) +{ + return mnt_fs_set_target(mnt_context_get_fs(cxt), target); +} + +/** + * mnt_context_get_target: + * @cxt: mount context + * + * Returns: returns pointer or NULL in case of error or if not set. + */ +const char *mnt_context_get_target(struct libmnt_context *cxt) +{ + return mnt_fs_get_target(mnt_context_get_fs(cxt)); +} + +/** + * mnt_context_set_target_prefix: + * @cxt: mount context + * @path: mountpoint prefix + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_target_prefix(struct libmnt_context *cxt, const char *path) +{ + char *p = NULL; + + if (!cxt) + return -EINVAL; + if (path) { + p = strdup(path); + if (!p) + return -ENOMEM; + } + free(cxt->tgt_prefix); + cxt->tgt_prefix = p; + + return 0; +} + +/** + * mnt_context_get_target_prefix: + * @cxt: mount context + * + * Returns: returns pointer or NULL in case of error or if not set. + */ +const char *mnt_context_get_target_prefix(struct libmnt_context *cxt) +{ + return cxt ? cxt->tgt_prefix : NULL; +} + + +/** + * mnt_context_set_fstype: + * @cxt: mount context + * @fstype: filesystem type + * + * Note that the @fstype has to be a FS type. For patterns with + * comma-separated list of filesystems or for the "nofs" notation, use + * mnt_context_set_fstype_pattern(). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_fstype(struct libmnt_context *cxt, const char *fstype) +{ + return mnt_fs_set_fstype(mnt_context_get_fs(cxt), fstype); +} + +/** + * mnt_context_get_fstype: + * @cxt: mount context + * + * Returns: pointer or NULL in case of error or if not set. + */ +const char *mnt_context_get_fstype(struct libmnt_context *cxt) +{ + return mnt_fs_get_fstype(mnt_context_get_fs(cxt)); +} + +struct libmnt_optlist *mnt_context_get_optlist(struct libmnt_context *cxt) +{ + if (!cxt) + return NULL; + if (!cxt->optlist) { + cxt->optlist = mnt_new_optlist(); + if (!cxt->optlist) + return NULL; + if (mnt_optlist_register_map(cxt->optlist, cxt->map_linux)) + goto fail; + if (mnt_optlist_register_map(cxt->optlist, cxt->map_userspace)) + goto fail; + } + + return cxt->optlist; +fail: + mnt_unref_optlist(cxt->optlist); + return NULL; +} + +/** + * mnt_context_set_options: + * @cxt: mount context + * @optstr: comma delimited mount options + * + * Note that MS_MOVE cannot be specified as "string". It's operation that + * is no supported in fstab (etc.) + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_options(struct libmnt_context *cxt, const char *optstr) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + return mnt_optlist_set_optstr(ls, optstr, NULL); +} + +/** + * mnt_context_append_options: + * @cxt: mount context + * @optstr: comma delimited mount options + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_append_options(struct libmnt_context *cxt, const char *optstr) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + return mnt_optlist_append_optstr(ls, optstr, NULL); +} + +/** + * mnt_context_get_options: + * @cxt: mount context + * + * This function returns mount options set by mnt_context_set_options(), + * mnt_context_append_options() or mnt_context_set_mflags(); + * + * Before v2.39 this function ignored options specified by flags (see + * mnt_context_set_mflags()) before mnt_context_prepare_mount() call. Now this + * function always returns all mount options. + * + * Returns: pointer or NULL + */ +const char *mnt_context_get_options(struct libmnt_context *cxt) +{ + const char *str = NULL; + + if (cxt->optlist && !mnt_optlist_is_empty(cxt->optlist)) + mnt_optlist_get_optstr(cxt->optlist, &str, NULL, 0); + return str; +} + +/** + * mnt_context_set_fstype_pattern: + * @cxt: mount context + * @pattern: FS name pattern (or NULL to reset the current setting) + * + * See mount(8), option -t. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_fstype_pattern(struct libmnt_context *cxt, const char *pattern) +{ + char *p = NULL; + + if (!cxt) + return -EINVAL; + if (pattern) { + p = strdup(pattern); + if (!p) + return -ENOMEM; + } + free(cxt->fstype_pattern); + cxt->fstype_pattern = p; + return 0; +} + +/** + * mnt_context_set_options_pattern: + * @cxt: mount context + * @pattern: options pattern (or NULL to reset the current setting) + * + * See mount(8), option -O. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_options_pattern(struct libmnt_context *cxt, const char *pattern) +{ + char *p = NULL; + + if (!cxt) + return -EINVAL; + if (pattern) { + p = strdup(pattern); + if (!p) + return -ENOMEM; + } + free(cxt->optstr_pattern); + cxt->optstr_pattern = p; + return 0; +} + +/** + * mnt_context_set_fstab: + * @cxt: mount context + * @tb: fstab + * + * The mount context reads /etc/fstab to the private struct libmnt_table by default. + * This function can be used to overwrite the private fstab with an external + * instance. + * + * This function modify the @tb reference counter. This function does not set + * the cache for the @tb. You have to explicitly call mnt_table_set_cache(tb, + * mnt_context_get_cache(cxt)); + * + * The fstab is used read-only and is not modified, it should be possible to + * share the fstab between more mount contexts (TODO: test it.) + * + * If the @tb argument is NULL, then the current private fstab instance is + * reset. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_fstab(struct libmnt_context *cxt, struct libmnt_table *tb) +{ + if (!cxt) + return -EINVAL; + + mnt_ref_table(tb); /* new */ + mnt_unref_table(cxt->fstab); /* old */ + + cxt->fstab = tb; + return 0; +} + +/** + * mnt_context_get_fstab: + * @cxt: mount context + * @tb: returns fstab + * + * See also mnt_table_parse_fstab() for more details about fstab. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_get_fstab(struct libmnt_context *cxt, struct libmnt_table **tb) +{ + struct libmnt_ns *ns_old; + + if (!cxt) + return -EINVAL; + if (!cxt->fstab) { + int rc; + + cxt->fstab = mnt_new_table(); + if (!cxt->fstab) + return -ENOMEM; + if (cxt->table_errcb) + mnt_table_set_parser_errcb(cxt->fstab, cxt->table_errcb); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + mnt_table_set_cache(cxt->fstab, mnt_context_get_cache(cxt)); + rc = mnt_table_parse_fstab(cxt->fstab, NULL); + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + if (rc) + return rc; + } + + if (tb) + *tb = cxt->fstab; + return 0; +} + +int mnt_context_get_mountinfo(struct libmnt_context *cxt, struct libmnt_table **tb) +{ + int rc = 0; + struct libmnt_ns *ns_old = NULL; + + if (!cxt) + return -EINVAL; + if (!cxt->mountinfo) { + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + context_init_paths(cxt, 0); + + cxt->mountinfo = mnt_new_table(); + if (!cxt->mountinfo) { + rc = -ENOMEM; + goto end; + } + + mnt_table_enable_noautofs(cxt->mountinfo, cxt->noautofs); + + if (cxt->table_errcb) + mnt_table_set_parser_errcb(cxt->mountinfo, cxt->table_errcb); + if (cxt->table_fltrcb) + mnt_table_set_parser_fltrcb(cxt->mountinfo, + cxt->table_fltrcb, + cxt->table_fltrcb_data); + + mnt_table_set_cache(cxt->mountinfo, mnt_context_get_cache(cxt)); + } + + /* Read the table; it's empty, because this first mnt_context_get_mountinfo() + * call, or because /proc was not accessible in previous calls */ + if (mnt_table_is_empty(cxt->mountinfo)) { + if (!ns_old) { + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + } + + rc = __mnt_table_parse_mountinfo(cxt->mountinfo, NULL, cxt->utab); + if (rc) + goto end; + } + + if (tb) + *tb = cxt->mountinfo; + + DBG(CXT, ul_debugobj(cxt, "mountinfo requested [nents=%d]", + mnt_table_get_nents(cxt->mountinfo))); + +end: + if (ns_old && !mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return rc; +} + +/** + * mnt_context_get_mtab: + * @cxt: mount context + * @tb: returns mtab + * + * Parse /proc/self/mountinfo mount table. + * + * The file /etc/mtab is no more used, @context points always to mountinfo + * (/proc/self/mountinfo). The function uses "mtab" in the name for backward + * compatibility only. + * + * See also mnt_table_parse_mtab() for more details about mountinfo. The + * result will be deallocated by mnt_free_context(@cxt). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_get_mtab(struct libmnt_context *cxt, struct libmnt_table **tb) +{ + return mnt_context_get_mountinfo(cxt, tb); +} + +/* + * Called by mountinfo parser to filter out entries, non-zero means that + * an entry has to be filtered out. + */ +static int mountinfo_filter(struct libmnt_fs *fs, void *data) +{ + if (!fs || !data) + return 0; + if (mnt_fs_streq_target(fs, data)) + return 0; + if (mnt_fs_streq_srcpath(fs, data)) + return 0; + return 1; +} + +/* + * The same like mnt_context_get_mountinfo(), but does not read all mountinfo + * file, but only entries relevant for @tgt. + */ +int mnt_context_get_mountinfo_for_target(struct libmnt_context *cxt, + struct libmnt_table **mountinfo, + const char *tgt) +{ + struct stat st; + struct libmnt_cache *cache = NULL; + char *cn_tgt = NULL; + int rc; + struct libmnt_ns *ns_old; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + if (mnt_context_is_nocanonicalize(cxt)) + mnt_context_set_tabfilter(cxt, mountinfo_filter, (void *) tgt); + + else if (mnt_safe_stat(tgt, &st) == 0 && S_ISDIR(st.st_mode)) { + cache = mnt_context_get_cache(cxt); + cn_tgt = mnt_resolve_path(tgt, cache); + if (cn_tgt) + mnt_context_set_tabfilter(cxt, mountinfo_filter, cn_tgt); + } + + rc = mnt_context_get_mountinfo(cxt, mountinfo); + mnt_context_set_tabfilter(cxt, NULL, NULL); + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + if (cn_tgt && !cache) + free(cn_tgt); + + return rc; +} + +/* + * Allows to specify a filter for tab file entries. The filter is called by + * the table parser. Currently used for utab only. + */ +int mnt_context_set_tabfilter(struct libmnt_context *cxt, + int (*fltr)(struct libmnt_fs *, void *), + void *data) +{ + if (!cxt) + return -EINVAL; + + cxt->table_fltrcb = fltr; + cxt->table_fltrcb_data = data; + + if (cxt->mountinfo) + mnt_table_set_parser_fltrcb(cxt->mountinfo, + cxt->table_fltrcb, + cxt->table_fltrcb_data); + + DBG(CXT, ul_debugobj(cxt, "tabfilter %s", fltr ? "ENABLED!" : "disabled")); + return 0; +} + +/** + * mnt_context_get_table: + * @cxt: mount context + * @filename: e.g. /proc/self/mountinfo + * @tb: returns the table + * + * This function allocates a new table and parses the @file. The parser error + * callback and cache for tags and paths is set according to the @cxt setting. + * See also mnt_table_parse_file(). + * + * It's strongly recommended to use the mnt_context_get_mtab() and + * mnt_context_get_fstab() functions for mountinfo and fstab files. This function + * does not care about LIBMOUNT_* env.variables and does not merge userspace + * options. + * + * The result will NOT be deallocated by mnt_free_context(@cxt). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_get_table(struct libmnt_context *cxt, + const char *filename, struct libmnt_table **tb) +{ + int rc; + struct libmnt_ns *ns_old; + + if (!cxt || !tb) + return -EINVAL; + + *tb = mnt_new_table(); + if (!*tb) + return -ENOMEM; + + if (cxt->table_errcb) + mnt_table_set_parser_errcb(*tb, cxt->table_errcb); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + rc = mnt_table_parse_file(*tb, filename); + + if (rc) { + mnt_unref_table(*tb); + goto end; + } + + mnt_table_set_cache(*tb, mnt_context_get_cache(cxt)); + +end: + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return rc; +} + +/** + * mnt_context_set_tables_errcb + * @cxt: mount context + * @cb: pointer to callback function + * + * The error callback is used for all tab files (e.g. mountinfo, fstab) + * parsed within the context. + * + * See also mnt_context_get_mtab(), + * mnt_context_get_fstab(), + * mnt_table_set_parser_errcb(). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_tables_errcb(struct libmnt_context *cxt, + int (*cb)(struct libmnt_table *tb, const char *filename, int line)) +{ + if (!cxt) + return -EINVAL; + + if (cxt->mountinfo) + mnt_table_set_parser_errcb(cxt->mountinfo, cb); + if (cxt->fstab) + mnt_table_set_parser_errcb(cxt->fstab, cb); + + cxt->table_errcb = cb; + return 0; +} + +/** + * mnt_context_set_cache: + * @cxt: mount context + * @cache: cache instance or NULL + * + * The mount context maintains a private struct libmnt_cache by default. This + * function can be used to overwrite the private cache with an external instance. + * This function increments cache reference counter. + * + * If the @cache argument is NULL, then the current cache instance is reset. + * This function apply the cache to fstab and mountinfo instances (if already + * exists). + * + * The old cache instance reference counter is de-incremented. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_cache(struct libmnt_context *cxt, struct libmnt_cache *cache) +{ + if (!cxt) + return -EINVAL; + + mnt_ref_cache(cache); /* new */ + mnt_unref_cache(cxt->cache); /* old */ + + cxt->cache = cache; + + if (cxt->mountinfo) + mnt_table_set_cache(cxt->mountinfo, cache); + if (cxt->fstab) + mnt_table_set_cache(cxt->fstab, cache); + + return 0; +} + +/** + * mnt_context_get_cache + * @cxt: mount context + * + * See also mnt_context_set_cache(). + * + * Returns: pointer to cache or NULL if canonicalization is disabled. + */ +struct libmnt_cache *mnt_context_get_cache(struct libmnt_context *cxt) +{ + if (!cxt || mnt_context_is_nocanonicalize(cxt)) + return NULL; + + if (!cxt->cache) { + struct libmnt_cache *cache = mnt_new_cache(); + mnt_context_set_cache(cxt, cache); + mnt_unref_cache(cache); + } + return cxt->cache; +} + +/** + * mnt_context_set_passwd_cb: + * @cxt: mount context + * @get: callback to get password + * @release: callback to release (deallocate) password + * + * Sets callbacks for encryption password (e.g encrypted loopdev). This + * function is deprecated (encrypted loops are no longer supported). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_passwd_cb(struct libmnt_context *cxt, + char *(*get)(struct libmnt_context *), + void (*release)(struct libmnt_context *, char *)) +{ + if (!cxt) + return -EINVAL; + cxt->pwd_get_cb = get; + cxt->pwd_release_cb = release; + return 0; +} + +/** + * mnt_context_get_lock: + * @cxt: mount context + * + * The libmount applications don't have to care about utab locking, but with a + * small exception: the application has to be able to remove the lock file when + * interrupted by signal or signals have to be ignored when the lock is locked. + * + * The default behavior is to ignore all signals (except SIGALRM and + * SIGTRAP for utab update) when the lock is locked. If this behavior + * is unacceptable, then use: + * + * lc = mnt_context_get_lock(cxt); + * if (lc) + * mnt_lock_block_signals(lc, FALSE); + * + * and don't forget to call mnt_unlock_file(lc) before exit. + * + * Returns: pointer to lock struct or NULL. + */ +struct libmnt_lock *mnt_context_get_lock(struct libmnt_context *cxt) +{ + /* + * DON'T call this function within libmount, it will always allocate + * the lock. The mnt_update_* functions are able to allocate the lock + * only when utab update is really necessary. + */ + if (!cxt || mnt_context_is_nomtab(cxt)) + return NULL; + + if (!cxt->lock) { + cxt->lock = mnt_new_lock( + mnt_context_get_writable_tabpath(cxt), 0); + if (cxt->lock) + mnt_lock_block_signals(cxt->lock, TRUE); + } + return cxt->lock; +} + +/** + * mnt_context_set_mflags: + * @cxt: mount context + * @flags: mount(2) flags (MS_* flags) + * + * Sets mount flags (see mount(2) man page). + * + * Note that order of mount options (strings) and flags matter if you mix + * mnt_context_append_options() and mnt_context_set_mflags(). + * + * Be careful if use MS_REC flag -- this is flags is generic for + * all mask. In this case is better to use options string where + * mount options are independent and nothign is applied to all options. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_mflags(struct libmnt_context *cxt, unsigned long flags) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + + return mnt_optlist_set_flags(ls, flags, cxt->map_linux); +} + +/** + * mnt_context_get_mflags: + * @cxt: mount context + * @flags: returns MS_* mount flags + * + * Converts mount options string to MS_* flags and bitwise-OR the result with + * the already defined flags (see mnt_context_set_mflags()). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_get_mflags(struct libmnt_context *cxt, unsigned long *flags) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + + return mnt_optlist_get_flags(ls, flags, cxt->map_linux, 0); +} + +/** + * mnt_context_set_user_mflags: + * @cxt: mount context + * @flags: mount(2) flags (MNT_MS_* flags, e.g. MNT_MS_LOOP) + * + * Sets userspace mount flags. + * + * See also notes for mnt_context_set_mflags(). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_user_mflags(struct libmnt_context *cxt, unsigned long flags) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + + return mnt_optlist_set_flags(ls, flags, cxt->map_userspace); +} + +/** + * mnt_context_get_user_mflags: + * @cxt: mount context + * @flags: returns mount flags + * + * Converts mount options string to MNT_MS_* flags and bitwise-OR the result + * with the already defined flags (see mnt_context_set_user_mflags()). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_get_user_mflags(struct libmnt_context *cxt, unsigned long *flags) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + + return mnt_optlist_get_flags(ls, flags, cxt->map_userspace, 0); +} + +/** + * mnt_context_set_mountdata: + * @cxt: mount context + * @data: mount(2) data + * + * The mount context generates mountdata from mount options by default. This + * function can be used to overwrite this behavior, and @data will be used instead + * of mount options. + * + * The libmount does not deallocate the data by mnt_free_context(). Note that + * NULL is also valid mount data. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_mountdata(struct libmnt_context *cxt, void *data) +{ + if (!cxt) + return -EINVAL; + cxt->mountdata = data; + cxt->flags |= MNT_FL_MOUNTDATA; + return 0; +} + +/* + * Translates LABEL/UUID/path to mountable path + */ +int mnt_context_prepare_srcpath(struct libmnt_context *cxt) +{ + const char *path = NULL; + struct libmnt_cache *cache; + const char *t, *v, *src, *type; + int rc = 0; + struct libmnt_ns *ns_old; + struct libmnt_optlist *ol; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "--> preparing source path")); + + src = mnt_fs_get_source(cxt->fs); + + if (!src && mnt_context_propagation_only(cxt)) + /* mount --make-{shared,private,...} */ + return mnt_fs_set_source(cxt->fs, "none"); + + /* ignore filesystems without source or filesystems + * where the source is a quasi-path (//foo/bar) + */ + if (!src || mnt_fs_is_netfs(cxt->fs)) + return 0; + + /* ZFS source is always "dataset", not a real path */ + type = mnt_fs_get_fstype(cxt->fs); + if (type && strcmp(type, "zfs") == 0) + return 0; + + DBG(CXT, ul_debugobj(cxt, "srcpath '%s'", src)); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + cache = mnt_context_get_cache(cxt); + + if (!mnt_fs_get_tag(cxt->fs, &t, &v)) { + /* + * Source is TAG (evaluate) + */ + if (cache) + path = mnt_resolve_tag(t, v, cache); + + rc = path ? mnt_fs_set_source(cxt->fs, path) : -MNT_ERR_NOSOURCE; + + } else if (cache && !mnt_fs_is_pseudofs(cxt->fs)) { + /* + * Source is PATH (canonicalize) + */ + path = mnt_resolve_path(src, cache); + if (path && strcmp(path, src) != 0) + rc = mnt_fs_set_source(cxt->fs, path); + } + + if (rc) { + DBG(CXT, ul_debugobj(cxt, "failed to prepare srcpath [rc=%d]", rc)); + goto end; + } + + if (!path) + path = src; + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + if (mnt_optlist_is_bind(ol) + || mnt_optlist_is_move(ol) + || mnt_optlist_is_remount(ol) + || mnt_fs_is_pseudofs(cxt->fs)) { + DBG(CXT, ul_debugobj(cxt, "REMOUNT/BIND/MOVE/pseudo FS source: %s", path)); + goto end; + } + + rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_SOURCE); + if (rc) + goto end; + + DBG(CXT, ul_debugobj(cxt, "final srcpath '%s'", + mnt_fs_get_source(cxt->fs))); + +end: + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; +} + +/* Guess type, but not set to cxt->fs, always use free() for the result. It's + * no error when we're not able to guess a filesystem type. Note that error + * does not mean that result in @type is NULL. + */ +int mnt_context_guess_srcpath_fstype(struct libmnt_context *cxt, char **type) +{ + int rc = 0; + struct libmnt_ns *ns_old; + const char *dev; + + assert(type); + assert(cxt); + + *type = NULL; + + dev = mnt_fs_get_srcpath(cxt->fs); + if (!dev) + return 0; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + if (access(dev, F_OK) == 0) { + struct libmnt_cache *cache = mnt_context_get_cache(cxt); + int ambi = 0; + + *type = mnt_get_fstype(dev, &ambi, cache); + if (ambi) + rc = -MNT_ERR_AMBIFS; + + if (cache && *type) { + *type = strdup(*type); + if (!*type) + rc = -ENOMEM; + } + } else { + DBG(CXT, ul_debugobj(cxt, "access(%s) failed [%m]", dev)); + if (strchr(dev, ':') != NULL) { + *type = strdup("nfs"); + if (!*type) + rc = -ENOMEM; + } else if (!strncmp(dev, "//", 2)) { + *type = strdup("cifs"); + if (!*type) + rc = -ENOMEM; + } + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + if (rc == 0 && *type) { + struct libmnt_optlist *ol = mnt_context_get_optlist(cxt); + struct libmnt_opt *opt; + const char *allowed; + + if (!ol) + return -ENOMEM; + + opt = mnt_optlist_get_named(ol, + "X-mount.auto-fstypes", cxt->map_userspace); + + if (opt + && (allowed = mnt_opt_get_value(opt)) + && !match_fstype(*type, allowed)) { + DBG(CXT, ul_debugobj(cxt, "%s is not allowed by auto-fstypes=%s", + *type, allowed)); + free(*type); + *type = NULL; + rc = -MNT_ERR_NOFSTYPE; + } + } + + return rc; +} + +/* + * It's usually no error when we're not able to detect the filesystem type -- we + * will try to use the types from /{etc,proc}/filesystems. + */ +int mnt_context_guess_fstype(struct libmnt_context *cxt) +{ + struct libmnt_optlist *ol; + char *type; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "--> preparing fstype")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + if (mnt_optlist_is_bind(ol) + || mnt_optlist_is_move(ol) + || mnt_context_propagation_only(cxt)) + goto none; + + type = (char *) mnt_fs_get_fstype(cxt->fs); + if (type && !strcmp(type, "auto")) { + mnt_fs_set_fstype(cxt->fs, NULL); + type = NULL; + } + + if (type) + goto done; + if (mnt_optlist_is_remount(ol)) + goto none; + if (cxt->fstype_pattern) + goto done; + + rc = mnt_context_guess_srcpath_fstype(cxt, &type); + if (rc == 0 && type) + __mnt_fs_set_fstype_ptr(cxt->fs, type); + else + free(type); +done: + DBG(CXT, ul_debugobj(cxt, "FS type: %s [rc=%d]", + mnt_fs_get_fstype(cxt->fs), rc)); + return rc; +none: + return mnt_fs_set_fstype(cxt->fs, "none"); +} + +/* + * The default is to use fstype from cxt->fs, this could be overwritten by + * @type. The @act is MNT_ACT_{MOUNT,UMOUNT}. + * + * Returns: 0 on success or negative number in case of error. Note that success + * does not mean that there is any usable helper, you have to check cxt->helper. + */ +int mnt_context_prepare_helper(struct libmnt_context *cxt, const char *name, + const char *type) +{ + char search_path[] = FS_SEARCH_PATH; /* from config.h */ + char *p = NULL, *path; + struct libmnt_ns *ns_old; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "checking for helper")); + + if (cxt->helper) { + free(cxt->helper); + cxt->helper = NULL; + } + + if (!type) + type = mnt_fs_get_fstype(cxt->fs); + + if (type && strchr(type, ',')) + return 0; /* type is fstype pattern */ + + if (mnt_context_is_nohelpers(cxt) + || !type + || !strcmp(type, "none") + || strstr(type, "/..") /* don't try to smuggle path */ + || mnt_fs_is_swaparea(cxt->fs)) + return 0; + + ns_old = mnt_context_switch_origin_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* Ignore errors when search in $PATH and do not modify @rc + */ + path = strtok_r(search_path, ":", &p); + while (path) { + char helper[PATH_MAX]; + int len, found = 0; + + len = snprintf(helper, sizeof(helper), "%s/%s.%s", + path, name, type); + path = strtok_r(NULL, ":", &p); + + if (len < 0 || (size_t) len >= sizeof(helper)) + continue; + + found = mnt_is_path(helper); + if (!found && strchr(type, '.')) { + /* If type ends with ".subtype" try without it */ + char *hs = strrchr(helper, '.'); + if (hs) + *hs = '\0'; + found = mnt_is_path(helper); + } + + DBG(CXT, ul_debugobj(cxt, "%-25s ... %s", helper, + found ? "found" : "not found")); + if (!found) + continue; + + /* success */ + rc = strdup_to_struct_member(cxt, helper, helper); + break; + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + rc = -MNT_ERR_NAMESPACE; + + /* make sure helper is not set on error */ + if (rc) { + free(cxt->helper); + cxt->helper = NULL; + } + return rc; +} + +/* stop differentiate between options defined by flags and strings */ +int mnt_context_merge_mflags(struct libmnt_context *cxt) +{ + struct libmnt_optlist *ls = mnt_context_get_optlist(cxt); + + if (!ls) + return -ENOMEM; + + /* TODO: optlist returns always flags as merged, so + * MNT_FL_MOUNTFLAGS_MERGED is unncessary anymore + */ + cxt->flags |= MNT_FL_MOUNTFLAGS_MERGED; + return mnt_optlist_merge_opts(ls); +} + +/* + * Prepare /run/mount/utab + */ +int mnt_context_prepare_update(struct libmnt_context *cxt) +{ + int rc; + const char *target, *name; + unsigned long flags = 0; + + assert(cxt); + assert(cxt->fs); + assert(cxt->action); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "--> prepare update")); + + if (mnt_context_propagation_only(cxt)) { + DBG(CXT, ul_debugobj(cxt, "skip update: only MS_PROPAGATION")); + return 0; + } + + target = mnt_fs_get_target(cxt->fs); + + if (cxt->action == MNT_ACT_UMOUNT && target && !strcmp(target, "/")) { + DBG(CXT, ul_debugobj(cxt, "root umount: setting NOMTAB")); + mnt_context_disable_mtab(cxt, TRUE); + } + if (mnt_context_is_nomtab(cxt)) { + DBG(CXT, ul_debugobj(cxt, "skip update: NOMTAB flag")); + return 0; + } + name = mnt_context_get_writable_tabpath(cxt); + if (!name) { + DBG(CXT, ul_debugobj(cxt, "skip update: no writable destination")); + return 0; + } + /* 0 = success, 1 = not called yet */ + if (cxt->syscall_status != 1 && cxt->syscall_status != 0) { + DBG(CXT, ul_debugobj(cxt, + "skip update: syscall failed [status=%d]", + cxt->syscall_status)); + return 0; + } + + if (!cxt->update) { + if (cxt->action == MNT_ACT_UMOUNT && is_file_empty(name)) { + DBG(CXT, ul_debugobj(cxt, "skip update: umount, no table")); + return 0; + } + + cxt->update = mnt_new_update(); + if (!cxt->update) + return -ENOMEM; + + mnt_update_set_filename(cxt->update, name); + } + + mnt_context_get_mflags(cxt, &flags); + + if (cxt->action == MNT_ACT_UMOUNT) + rc = mnt_update_set_fs(cxt->update, flags, + mnt_context_get_target(cxt), NULL); + else + rc = mnt_update_set_fs(cxt->update, flags, + NULL, cxt->fs); + + return rc < 0 ? rc : 0; +} + +int mnt_context_update_tabs(struct libmnt_context *cxt) +{ + int rc = 0; + struct libmnt_ns *ns_old; + + assert(cxt); + + if (mnt_context_is_nomtab(cxt)) { + DBG(CXT, ul_debugobj(cxt, "don't update: NOMTAB flag")); + return 0; + } + if (!cxt->update || !mnt_update_is_ready(cxt->update)) { + DBG(CXT, ul_debugobj(cxt, "don't update: no update prepared")); + return 0; + } + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* check utab update when external helper executed */ + if (mnt_context_helper_executed(cxt) + && mnt_context_get_helper_status(cxt) == 0 + && mnt_context_utab_writable(cxt)) { + + if (mnt_update_already_done(cxt->update, cxt->lock)) { + DBG(CXT, ul_debugobj(cxt, "don't update: error evaluate or already updated")); + goto end; + } + } else if (cxt->helper) { + DBG(CXT, ul_debugobj(cxt, "don't update: external helper")); + goto end; + } + + if (cxt->syscall_status != 0 + && !(mnt_context_helper_executed(cxt) && + mnt_context_get_helper_status(cxt) == 0)) { + + DBG(CXT, ul_debugobj(cxt, "don't update: syscall/helper failed/not called")); + goto end; + } + + rc = mnt_update_table(cxt->update, cxt->lock); + +end: + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; +} + +/* apply @fs to @cxt; + * + * @mflags are mount flags as specified on command-line -- used only to save + * MS_RDONLY which is allowed for non-root users. + */ +static int apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs, unsigned long mflags) +{ + struct libmnt_optlist *ls; + int rc; + + + + if (!cxt->optsmode) { + if (mnt_context_is_restricted(cxt)) { + DBG(CXT, ul_debugobj(cxt, "force fstab usage for non-root users!")); + cxt->optsmode = MNT_OMODE_USER; + } else { + DBG(CXT, ul_debugobj(cxt, "use default optsmode")); + cxt->optsmode = MNT_OMODE_AUTO; + } + + } + + if (!mnt_context_get_fs(cxt)) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, "apply entry:")); + DBG(CXT, mnt_fs_print_debug(fs, stderr)); + DBG(CXT, ul_debugobj(cxt, "OPTSMODE (opt-part): ignore=%d, append=%d, prepend=%d, replace=%d", + cxt->optsmode & MNT_OMODE_IGNORE ? 1 : 0, + cxt->optsmode & MNT_OMODE_APPEND ? 1 : 0, + cxt->optsmode & MNT_OMODE_PREPEND ? 1 : 0, + cxt->optsmode & MNT_OMODE_REPLACE ? 1 : 0)); + + /* copy from fs to our FS description + */ + rc = mnt_fs_set_source(cxt->fs, mnt_fs_get_source(fs)); + if (!rc) + rc = mnt_fs_set_target(cxt->fs, mnt_fs_get_target(fs)); + + if (!rc && !mnt_fs_get_fstype(cxt->fs)) + rc = mnt_fs_set_fstype(cxt->fs, mnt_fs_get_fstype(fs)); + + if (!rc && !mnt_fs_get_root(cxt->fs) && mnt_fs_get_root(fs)) + rc = mnt_fs_set_root(cxt->fs, mnt_fs_get_root(fs)); + + if (rc) + goto done; + + ls = mnt_context_get_optlist(cxt); + if (!ls) { + rc = -ENOMEM; + goto done; + } + + if (cxt->optsmode & MNT_OMODE_IGNORE) + ; + else if (cxt->optsmode & MNT_OMODE_REPLACE) { + rc = mnt_optlist_set_optstr(ls, mnt_fs_get_options(fs), NULL); + + /* mount --read-only for non-root users is allowed */ + if (rc == 0 && (mflags & MS_RDONLY) + && mnt_context_is_restricted(cxt) + && cxt->optsmode == MNT_OMODE_USER) + rc = mnt_optlist_append_optstr(ls, "ro", NULL); + } + else if (cxt->optsmode & MNT_OMODE_APPEND) + rc = mnt_optlist_append_optstr(ls, mnt_fs_get_options(fs), NULL); + + else if (cxt->optsmode & MNT_OMODE_PREPEND) + rc = mnt_optlist_prepend_optstr(ls, mnt_fs_get_options(fs), NULL); + + if (!rc) + cxt->flags |= MNT_FL_TAB_APPLIED; + +done: + DBG(CXT, ul_debugobj(cxt, "final entry [rc=%d]", rc)); + DBG(CXT, mnt_fs_print_debug(cxt->fs, stderr)); + + return rc; +} + +static int apply_table(struct libmnt_context *cxt, struct libmnt_table *tb, + int direction, unsigned long mflags) +{ + struct libmnt_fs *fs = NULL; + const char *src, *tgt; + + assert(cxt); + assert(cxt->fs); + + src = mnt_fs_get_source(cxt->fs); + tgt = mnt_fs_get_target(cxt->fs); + + if (tgt && src) + fs = mnt_table_find_pair(tb, src, tgt, direction); + else { + if (src) + fs = mnt_table_find_source(tb, src, direction); + else if (tgt) + fs = mnt_table_find_target(tb, tgt, direction); + + if (!fs && mnt_context_is_swapmatch(cxt)) { + /* swap source and target (if @src is not LABEL/UUID), + * for example in + * + * mount /foo/bar + * + * the path could be a mountpoint as well as a source (for + * example bind mount, symlink to a device, ...). + */ + if (src && !mnt_fs_get_tag(cxt->fs, NULL, NULL)) + fs = mnt_table_find_target(tb, src, direction); + if (!fs && tgt) + fs = mnt_table_find_source(tb, tgt, direction); + } + } + + if (!fs) + return -MNT_ERR_NOFSTAB; /* not found */ + + return apply_fs(cxt, fs, mflags); +} + +/* apply @fs to @cxt -- use mnt_context_apply_fstab() if not sure + */ +int mnt_context_apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs) +{ + return apply_fs(cxt, fs, 0); +} + +/** + * mnt_context_apply_fstab: + * @cxt: mount context + * + * This function is optional. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_apply_fstab(struct libmnt_context *cxt) +{ + int rc = -1, isremount = 0, iscmdbind = 0; + struct libmnt_ns *ns_old; + struct libmnt_table *tab = NULL; + const char *src = NULL, *tgt = NULL; + unsigned long mflags = 0; + + if (!cxt || !cxt->fs) + return -EINVAL; + + if (mnt_context_tab_applied(cxt)) { /* already applied */ + DBG(CXT, ul_debugobj(cxt, "fstab already applied -- skip")); + return 0; + } + + if (mnt_context_is_restricted(cxt)) { + DBG(CXT, ul_debugobj(cxt, "force fstab usage for non-root users!")); + cxt->optsmode = MNT_OMODE_USER; + } else if (cxt->optsmode == 0) { + DBG(CXT, ul_debugobj(cxt, "use default optsmode")); + cxt->optsmode = MNT_OMODE_AUTO; + } else if (cxt->optsmode & MNT_OMODE_NOTAB) { + cxt->optsmode &= ~MNT_OMODE_FSTAB; + cxt->optsmode &= ~MNT_OMODE_MTAB; + cxt->optsmode &= ~MNT_OMODE_FORCE; + } + + if (mnt_context_get_mflags(cxt, &mflags) == 0) { + isremount = !!(mflags & MS_REMOUNT); + iscmdbind = !!(mflags & MS_BIND); + } + + if (cxt->fs) { + src = mnt_fs_get_source(cxt->fs); + tgt = mnt_fs_get_target(cxt->fs); + } + + DBG(CXT, ul_debugobj(cxt, "OPTSMODE (file-part): force=%d, fstab=%d, mtab=%d", + cxt->optsmode & MNT_OMODE_FORCE ? 1 : 0, + cxt->optsmode & MNT_OMODE_FSTAB ? 1 : 0, + cxt->optsmode & MNT_OMODE_MTAB ? 1 : 0)); + + /* fstab is not required if source and target are specified */ + if (src && tgt && !(cxt->optsmode & MNT_OMODE_FORCE)) { + DBG(CXT, ul_debugobj(cxt, "fstab not required -- skip")); + return 0; + } + + if (!src && tgt + && !(cxt->optsmode & MNT_OMODE_FSTAB) + && !(cxt->optsmode & MNT_OMODE_MTAB)) { + DBG(CXT, ul_debugobj(cxt, "only target; fstab/mtab not required " + "-- skip, probably MS_PROPAGATION")); + return 0; + } + + /* let's initialize cxt->fs */ + ignore_result( mnt_context_get_fs(cxt) ); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* try fstab */ + if (cxt->optsmode & MNT_OMODE_FSTAB) { + DBG(CXT, ul_debugobj(cxt, "trying to apply fstab (src=%s, target=%s)", src, tgt)); + rc = mnt_context_get_fstab(cxt, &tab); + if (!rc) + rc = apply_table(cxt, tab, MNT_ITER_FORWARD, mflags); + } + + /* try mountinfo */ + if (rc < 0 && (cxt->optsmode & MNT_OMODE_MTAB) + && (isremount || cxt->action == MNT_ACT_UMOUNT)) { + DBG(CXT, ul_debugobj(cxt, "trying to apply mountinfo (src=%s, target=%s)", src, tgt)); + if (tgt) + rc = mnt_context_get_mountinfo_for_target(cxt, &tab, tgt); + else + rc = mnt_context_get_mountinfo(cxt, &tab); + if (!rc) + rc = apply_table(cxt, tab, MNT_ITER_BACKWARD, mflags); + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + if (rc) { + if (!mnt_context_is_restricted(cxt) + && tgt && !src + && isremount) { + DBG(CXT, ul_debugobj(cxt, "only target; ignore missing mountinfo entry on remount")); + return 0; + } + + DBG(CXT, ul_debugobj(cxt, "failed to find entry in fstab/mountinfo [rc=%d]: %m", rc)); + + /* force to "not found in fstab/mountinfo" error, the details why + * not found are not so important and may be misinterpreted by + * applications... */ + rc = -MNT_ERR_NOFSTAB; + + + } else if (isremount && !iscmdbind && cxt->optlist) { + + /* ignore "bind" on remount when the flag is read from fstab */ + mnt_optlist_remove_named(cxt->optlist, "bind", NULL); + } + return rc; +} + +/** + * mnt_context_tab_applied: + * @cxt: mount context + * + * Returns: 1 if fstab (or mountinfo) has been applied to the context, or 0. + */ +int mnt_context_tab_applied(struct libmnt_context *cxt) +{ + return cxt->flags & MNT_FL_TAB_APPLIED; +} + +/* + * This is not a public function! + * + * Returns 1 if *only propagation flags* change is requested. + */ +int mnt_context_propagation_only(struct libmnt_context *cxt) +{ + struct libmnt_optlist *ls; + + if (cxt->action != MNT_ACT_MOUNT) + return 0; + if (cxt->mountdata || cxt->fs == NULL) + return 0; + if (cxt->fs->fstype && strcmp(cxt->fs->fstype, "none") != 0) + return 0; + if (cxt->fs->source && strcmp(cxt->fs->source, "none") != 0) + return 0; + + ls = mnt_context_get_optlist(cxt); + return ls ? mnt_optlist_is_propagation_only(ls) : 0; +} + +/** + * mnt_context_get_status: + * @cxt: mount context + * + * Global libmount status. + * + * The real exit code of the mount.type helper has to be tested by + * mnt_context_get_helper_status(). The mnt_context_get_status() only informs + * that exec() has been successful. + * + * Returns: 1 if mount.type or mount(2) syscall has been successfully called. + */ +int mnt_context_get_status(struct libmnt_context *cxt) +{ + return !cxt->syscall_status || !cxt->helper_exec_status; +} + +/** + * mnt_context_helper_executed: + * @cxt: mount context + * + * Returns: 1 if mount.type helper has been executed, or 0. + */ +int mnt_context_helper_executed(struct libmnt_context *cxt) +{ + return cxt->helper_exec_status != 1; +} + +/** + * mnt_context_get_helper_status: + * @cxt: mount context + * + * Return: mount.type helper exit status, result is reliable only if + * mnt_context_helper_executed() returns 1. + */ +int mnt_context_get_helper_status(struct libmnt_context *cxt) +{ + return cxt->helper_status; +} + +/** + * mnt_context_syscall_called: + * @cxt: mount context + * + * Returns: 1 if mount(2) syscall has been called, or 0. + */ +int mnt_context_syscall_called(struct libmnt_context *cxt) +{ + return cxt->syscall_status != 1; +} + +/** + * mnt_context_get_syscall_errno: + * @cxt: mount context + * + * The result from this function is reliable only if + * mnt_context_syscall_called() returns 1. + * + * Returns: mount(2) errno if the syscall failed or 0. + */ +int mnt_context_get_syscall_errno(struct libmnt_context *cxt) +{ + if (cxt->syscall_status < 0) + return -cxt->syscall_status; + return 0; +} + +/** + * mnt_context_set_syscall_status: + * @cxt: mount context + * @status: mount(2) status + * + * The @status should be 0 on success, or negative number on error (-errno). + * + * This function should only be used if the [u]mount(2) syscall is NOT called by + * libmount code. + * + * Returns: 0 or negative number in case of error. + */ +int mnt_context_set_syscall_status(struct libmnt_context *cxt, int status) +{ + if (!cxt) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "syscall status set to: %d", status)); + cxt->syscall_status = status; + return 0; +} + +/** + * mnt_context_strerror + * @cxt: context + * @buf: buffer + * @bufsiz: size of the buffer + * + * Not implemented, deprecated in favor or mnt_context_get_excode(). + * + * Returns: 0 or negative number in case of error. + */ +int mnt_context_strerror(struct libmnt_context *cxt __attribute__((__unused__)), + char *buf __attribute__((__unused__)), + size_t bufsiz __attribute__((__unused__))) +{ + /* TODO: based on cxt->syscall_errno or cxt->helper_status */ + return 0; +} + +/** + * mnt_context_enable_noautofs: + * @cxt: context + * @ignore: ignore or don't ignore + * + * Enable/disable ignore autofs mount table entries on reading the + * mount table. Only effective if the "ignore" mount option is being + * used for autofs mounts (such as if automount(8) has been configured + * to do so). + */ +int mnt_context_enable_noautofs(struct libmnt_context *cxt, int ignore) +{ + if (!cxt) + return -EINVAL; + cxt->noautofs = ignore ? 1 : 0; + return 0; +} + +int mnt_context_get_generic_excode(int rc, char *buf, size_t bufsz, const char *fmt, ...) +{ + va_list va; + + if (rc == 0) + return MNT_EX_SUCCESS; + + va_start(va, fmt); + + /* we need to support "%m" */ + errno = rc < 0 ? -rc : rc; + + if (buf && bufsz && vsnprintf(buf, bufsz, fmt, va) < 0) + *buf = '\0'; + + switch (errno) { + case EINVAL: + case EPERM: + rc = MNT_EX_USAGE; + break; + case ENOMEM: + rc = MNT_EX_SYSERR; + break; + default: + rc = MNT_EX_FAIL; + break; + } + va_end(va); + return rc; +} + +/** + * mnt_context_get_excode: + * @cxt: context + * @rc: return code of the previous operation + * @buf: buffer to print error message (optional) + * @bufsz: size of the buffer + * + * This function analyzes context, [u]mount syscall and external helper status + * and @mntrc and generates unified return code (see MNT_EX_*) as expected + * from mount(8) or umount(8). + * + * If the external helper (e.g. /sbin/mount.type) has been executed than it + * returns status from wait() of the helper. It's not libmount fail if helper + * returns some crazy undocumented codes... See mnt_context_helper_executed() + * and mnt_context_get_helper_status(). Note that mount(8) and umount(8) utils + * always return code from helper without extra care about it. + * + * The current implementation does not read error message from external + * helper into @buf. + * + * If the argument @buf is not NULL then error message is generated (if + * anything failed). + * + * The @mntrc is usually return code from mnt_context_mount(), + * mnt_context_umount(), or 'mntrc' as returned by mnt_context_next_mount(). + * + * Since: 2.30 + * + * Returns: MNT_EX_* codes. + */ +int mnt_context_get_excode( + struct libmnt_context *cxt, + int rc, + char *buf, + size_t bufsz) +{ + if (buf) { + *buf = '\0'; /* for sure */ + + if (!cxt->enabled_textdomain) { + bindtextdomain(LIBMOUNT_TEXTDOMAIN, LOCALEDIR); + cxt->enabled_textdomain = 1; + } + } + + switch (cxt->action) { + case MNT_ACT_MOUNT: + rc = mnt_context_get_mount_excode(cxt, rc, buf, bufsz); + break; + case MNT_ACT_UMOUNT: + rc = mnt_context_get_umount_excode(cxt, rc, buf, bufsz); + break; + default: + if (rc) + rc = mnt_context_get_generic_excode(rc, buf, bufsz, + _("operation failed: %m")); + else + rc = MNT_EX_SUCCESS; + break; + } + + DBG(CXT, ul_debugobj(cxt, "excode: rc=%d message=\"%s\"", rc, + buf ? buf : "<no-message>")); + return rc; +} + + +/** + * mnt_context_init_helper + * @cxt: mount context + * @action: MNT_ACT_{UMOUNT,MOUNT} + * @flags: not used now + * + * This function informs libmount that used from [u]mount.type helper. + * + * The function also calls mnt_context_disable_helpers() to avoid recursive + * mount.type helpers calling. It you really want to call another + * mount.type helper from your helper, then you have to explicitly enable this + * feature by: + * + * mnt_context_disable_helpers(cxt, FALSE); + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_init_helper(struct libmnt_context *cxt, int action, + int flags __attribute__((__unused__))) +{ + int rc; + + if (!cxt) + return -EINVAL; + + rc = mnt_context_disable_helpers(cxt, TRUE); + if (!rc) + rc = set_flag(cxt, MNT_FL_HELPER, 1); + if (!rc) + cxt->action = action; + + DBG(CXT, ul_debugobj(cxt, "initialized for [u]mount.<type> helper [rc=%d]", rc)); + return rc; +} + +/** + * mnt_context_helper_setopt: + * @cxt: context + * @c: getopt() result + * @arg: getopt() optarg + * + * This function applies the [u]mount.type command line option (for example parsed + * by getopt or getopt_long) to @cxt. All unknown options are ignored and + * then 1 is returned. + * + * Returns: negative number on error, 1 if @c is unknown option, 0 on success. + */ +int mnt_context_helper_setopt(struct libmnt_context *cxt, int c, char *arg) +{ + if (cxt) { + switch(cxt->action) { + case MNT_ACT_MOUNT: + return mnt_context_mount_setopt(cxt, c, arg); + case MNT_ACT_UMOUNT: + return mnt_context_umount_setopt(cxt, c, arg); + } + } + return -EINVAL; +} + +/** + * mnt_context_is_fs_mounted: + * @cxt: context + * @fs: filesystem + * @mounted: returns 1 for mounted and 0 for non-mounted filesystems + * + * Please, read the mnt_table_is_fs_mounted() description! + * + * Returns: 0 on success and negative number in case of error. + */ +int mnt_context_is_fs_mounted(struct libmnt_context *cxt, + struct libmnt_fs *fs, int *mounted) +{ + struct libmnt_table *mountinfo, *orig; + int rc = 0; + struct libmnt_ns *ns_old; + + if (!cxt || !fs || !mounted) + return -EINVAL; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + orig = cxt->mountinfo; + rc = mnt_context_get_mountinfo(cxt, &mountinfo); + if (rc == -ENOENT && mnt_fs_streq_target(fs, "/proc")) { + if (!orig) { + mnt_unref_table(cxt->mountinfo); + cxt->mountinfo = NULL; + } + *mounted = 0; + rc = 0; /* /proc not mounted */ + + } else if (rc == 0) { + *mounted = __mnt_table_is_fs_mounted(mountinfo, fs, + mnt_context_get_target_prefix(cxt)); + + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; +} + +static int mnt_context_add_child(struct libmnt_context *cxt, pid_t pid) +{ + pid_t *pids; + + if (!cxt) + return -EINVAL; + + pids = realloc(cxt->children, sizeof(pid_t) * cxt->nchildren + 1); + if (!pids) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, "add new child %d", pid)); + cxt->children = pids; + cxt->children[cxt->nchildren++] = pid; + + return 0; +} + +int mnt_fork_context(struct libmnt_context *cxt) +{ + int rc = 0; + pid_t pid; + + assert(cxt); + if (!mnt_context_is_parent(cxt)) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "forking context")); + + DBG_FLUSH; + + pid = fork(); + + switch (pid) { + case -1: /* error */ + DBG(CXT, ul_debugobj(cxt, "fork failed %m")); + return -errno; + + case 0: /* child */ + cxt->pid = getpid(); + mnt_context_enable_fork(cxt, FALSE); + DBG(CXT, ul_debugobj(cxt, "child created")); + break; + + default: + rc = mnt_context_add_child(cxt, pid); + break; + } + + return rc; +} + +int mnt_context_wait_for_children(struct libmnt_context *cxt, + int *nchildren, int *nerrs) +{ + int i; + + if (!cxt) + return -EINVAL; + + assert(mnt_context_is_parent(cxt)); + + for (i = 0; i < cxt->nchildren; i++) { + pid_t pid = cxt->children[i]; + int rc = 0, ret = 0; + + if (!pid) + continue; + do { + DBG(CXT, ul_debugobj(cxt, + "waiting for child (%d/%d): %d", + i + 1, cxt->nchildren, pid)); + errno = 0; + rc = waitpid(pid, &ret, 0); + + } while (rc == -1 && errno == EINTR); + + if (nchildren) + (*nchildren)++; + + if (rc != -1 && nerrs) { + if (WIFEXITED(ret)) + (*nerrs) += WEXITSTATUS(ret) == 0 ? 0 : 1; + else + (*nerrs)++; + } + cxt->children[i] = 0; + } + + cxt->nchildren = 0; + free(cxt->children); + cxt->children = NULL; + return 0; +} + +static void close_ns(struct libmnt_ns *ns) +{ + if (ns->fd == -1) + return; + + close(ns->fd); + ns->fd = -1; + + mnt_unref_cache(ns->cache); + ns->cache = NULL; +} + +/** + * mnt_context_set_target_ns: + * @cxt: mount context + * @path: path to target namespace or NULL + * + * Sets target namespace to namespace represented by @path. If @path is NULL, + * target namespace is cleared. + * + * This function sets errno to ENOSYS and returns error if libmount is + * compiled without namespaces support. + * + * Returns: 0 on success, negative number in case of error. + * + * Since: 2.33 + */ +int mnt_context_set_target_ns(struct libmnt_context *cxt, const char *path) +{ + if (!cxt) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "Setting %s as target namespace", path)); + + /* cleanup only */ + if (!path) { + close_ns(&cxt->ns_orig); + close_ns(&cxt->ns_tgt); + return 0; + } + +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + int errsv = 0; + int tmp; + + errno = 0; + + /* open original namespace */ + if (cxt->ns_orig.fd == -1) { + cxt->ns_orig.fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (cxt->ns_orig.fd == -1) + return -errno; + cxt->ns_orig.cache = NULL; + } + + /* open target (wanted) namespace */ + tmp = open(path, O_RDONLY | O_CLOEXEC); + if (tmp == -1) + return -errno; + + /* test whether namespace switching works */ + DBG(CXT, ul_debugobj(cxt, "Trying whether namespace is valid")); + if (setns(tmp, CLONE_NEWNS) + || setns(cxt->ns_orig.fd, CLONE_NEWNS)) { + errsv = errno; + DBG(CXT, ul_debugobj(cxt, "setns(2) failed [errno=%d %m]", errno)); + goto err; + } + + close_ns(&cxt->ns_tgt); + + cxt->ns_tgt.fd = tmp; + cxt->ns_tgt.cache = NULL; + + return 0; +err: + close(tmp); + errno = errsv; + +#else /* ! USE_LIBMOUNT_SUPPORT_NAMESPACES */ + errno = ENOSYS; +#endif + return -errno; +} + +/** + * mnt_context_get_target_ns: + * @cxt: mount context + * + * Returns: pointer to target namespace + * + * Since: 2.33 + */ +struct libmnt_ns *mnt_context_get_target_ns(struct libmnt_context *cxt) +{ + return &cxt->ns_tgt; +} + +/** + * mnt_context_get_origin_ns: + * @cxt: mount context + * + * Returns: pointer to original namespace + * + * Since: 2.33 + */ +struct libmnt_ns *mnt_context_get_origin_ns(struct libmnt_context *cxt) +{ + return &cxt->ns_orig; +} + + +/** + * mnt_context_switch_ns: + * @cxt: mount context + * @ns: namespace to switch to + * + * Switch to namespace specified by ns + * + * Typical usage: + * <informalexample> + * <programlisting> + * struct libmnt_ns *ns_old; + * ns_old = mnt_context_switch_ns(cxt, mnt_context_get_target_ns(cxt)); + * ... code ... + * mnt_context_switch_ns(cxt, ns_old); + * </programlisting> + * </informalexample> + * + * Returns: pointer to previous namespace or NULL on error + * + * Since: 2.33 + */ +struct libmnt_ns *mnt_context_switch_ns(struct libmnt_context *cxt, struct libmnt_ns *ns) +{ + struct libmnt_ns *old = NULL; + + if (!cxt || !ns) + return NULL; + + /* + * If mnt_context_set_target_ns() has never been used than @ns file + * descriptor is -1 and this function is noop. + */ + old = cxt->ns_cur; + if (ns == old || ns->fd == -1) + return old; + +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + /* remember the current cache */ + if (old->cache != cxt->cache) { + mnt_unref_cache(old->cache); + old->cache = cxt->cache; + mnt_ref_cache(old->cache); + } + + /* switch */ + DBG(CXT, ul_debugobj(cxt, "Switching to %s namespace", + ns == mnt_context_get_target_ns(cxt) ? "target" : + ns == mnt_context_get_origin_ns(cxt) ? "original" : "other")); + + if (setns(ns->fd, CLONE_NEWNS)) { + int errsv = errno; + + DBG(CXT, ul_debugobj(cxt, "setns(2) failed [errno=%d %m]", errno)); + errno = errsv; + return NULL; + } + + /* update pointer to the current namespace */ + cxt->ns_cur = ns; + + /* update pointer to the cache */ + mnt_unref_cache(cxt->cache); + cxt->cache = ns->cache; + mnt_ref_cache(cxt->cache); +#endif /* USE_LIBMOUNT_SUPPORT_NAMESPACES */ + + return old; +} + +/** + * mnt_context_switch_origin_ns: + * @cxt: mount context + * + * Switch to original namespace + * + * This is shorthand for + * <informalexample> + * <programlisting> + * mnt_context_switch_ns(cxt, mnt_context_get_origin_ns(cxt)); + * </programlisting> + * </informalexample> + * + * Returns: pointer to previous namespace or NULL on error + * + * Since: 2.33 + */ +struct libmnt_ns *mnt_context_switch_origin_ns(struct libmnt_context *cxt) +{ + return mnt_context_switch_ns(cxt, mnt_context_get_origin_ns(cxt)); +} + +/** + * mnt_context_switch_target_ns: + * @cxt: mount context + * + * Switch to target namespace + * + * This is shorthand for + * <informalexample> + * <programlisting> + * mnt_context_switch_ns(cxt, mnt_context_get_target_ns(cxt)); + * </programlisting> + * </informalexample> + * + * Returns: pointer to previous namespace or NULL on error + * + * Since: 2.33 + */ +struct libmnt_ns *mnt_context_switch_target_ns(struct libmnt_context *cxt) +{ + return mnt_context_switch_ns(cxt, mnt_context_get_target_ns(cxt)); +} + + +#ifdef TEST_PROGRAM + +static int test_search_helper(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_context *cxt; + const char *type; + int rc; + + if (argc < 2) + return -EINVAL; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + type = argv[1]; + + mnt_context_get_fs(cxt); /* just to fill cxt->fs */ + cxt->flags |= MNT_FL_MOUNTFLAGS_MERGED; /* fake */ + + rc = mnt_context_prepare_helper(cxt, "mount", type); + printf("helper is: %s\n", cxt->helper ? cxt->helper : "not found"); + + mnt_free_context(cxt); + return rc; +} + + +static struct libmnt_lock *lock; + +static void lock_fallback(void) +{ + if (lock) + mnt_unlock_file(lock); +} + +static int test_mount(struct libmnt_test *ts, int argc, char *argv[]) +{ + int idx = 1, rc = 0; + struct libmnt_context *cxt; + + if (argc < 2) + return -EINVAL; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + if (!strcmp(argv[idx], "-o")) { + mnt_context_set_options(cxt, argv[idx + 1]); + idx += 2; + } + if (!strcmp(argv[idx], "-t")) { + /* TODO: use mnt_context_set_fstype_pattern() */ + mnt_context_set_fstype(cxt, argv[idx + 1]); + idx += 2; + } + + if (argc == idx + 1) + /* mount <mountpoint>|<device> */ + mnt_context_set_target(cxt, argv[idx++]); + + else if (argc == idx + 2) { + /* mount <device> <mountpoint> */ + mnt_context_set_source(cxt, argv[idx++]); + mnt_context_set_target(cxt, argv[idx++]); + } + + /* this is unnecessary! -- libmount is able to internally + * create and manage the lock + */ + lock = mnt_context_get_lock(cxt); + if (lock) + atexit(lock_fallback); + + rc = mnt_context_mount(cxt); + if (rc) + warn("failed to mount"); + else + printf("successfully mounted\n"); + + lock = NULL; /* because we use atexit lock_fallback */ + mnt_free_context(cxt); + return rc; +} + +static int test_umount(struct libmnt_test *ts, int argc, char *argv[]) +{ + int idx = 1, rc = 0; + struct libmnt_context *cxt; + + if (argc < 2) + return -EINVAL; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + if (!strcmp(argv[idx], "-t")) { + mnt_context_set_fstype(cxt, argv[idx + 1]); + idx += 2; + } + + if (!strcmp(argv[idx], "-f")) { + mnt_context_enable_force(cxt, TRUE); + idx++; + } + + if (!strcmp(argv[idx], "-l")) { + mnt_context_enable_lazy(cxt, TRUE); + idx++; + } + + if (!strcmp(argv[idx], "-r")) { + mnt_context_enable_rdonly_umount(cxt, TRUE); + idx++; + } + + if (argc == idx + 1) { + /* mount <mountpoint>|<device> */ + mnt_context_set_target(cxt, argv[idx++]); + } else { + rc = -EINVAL; + goto err; + } + + lock = mnt_context_get_lock(cxt); + if (lock) + atexit(lock_fallback); + + rc = mnt_context_umount(cxt); + if (rc) + printf("failed to umount\n"); + else + printf("successfully umounted\n"); +err: + lock = NULL; /* because we use atexit lock_fallback */ + mnt_free_context(cxt); + return rc; +} + +static int test_flags(struct libmnt_test *ts, int argc, char *argv[]) +{ + int idx = 1, rc = 0; + struct libmnt_context *cxt; + const char *opt = NULL; + unsigned long flags = 0; + + if (argc < 2) + return -EINVAL; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + if (!strcmp(argv[idx], "-o")) { + mnt_context_set_options(cxt, argv[idx + 1]); + idx += 2; + } + + if (argc == idx + 1) + /* mount <mountpoint>|<device> */ + mnt_context_set_target(cxt, argv[idx++]); + + rc = mnt_context_prepare_mount(cxt); + if (rc) + printf("failed to prepare mount %s\n", strerror(-rc)); + + opt = mnt_fs_get_options(cxt->fs); + if (opt) + fprintf(stdout, "options: %s\n", opt); + + mnt_context_get_mflags(cxt, &flags); + fprintf(stdout, "flags: %08lx\n", flags); + + mnt_free_context(cxt); + return rc; +} + +static int test_cxtsync(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_context *cxt; + struct libmnt_fs *fs; + unsigned long flags = 0; + int rc; + + if (argc != 4) + return -EINVAL; + + fs = mnt_new_fs(); + if (!fs) + return -ENOMEM; + + rc = mnt_fs_set_options(fs, argv[1]); + if (rc) + return rc; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + rc = mnt_context_set_fs(cxt, fs); + if (rc) + return rc; + + rc = mnt_context_append_options(cxt, argv[2]); + if (rc) + return rc; + + rc = mnt_fs_append_options(fs, argv[3]); + if (rc) + return rc; + + mnt_context_get_mflags(cxt, &flags); + + printf(" fs options: %s\n", mnt_fs_get_options(fs)); + printf("context options: %s\n", mnt_context_get_options(cxt)); + printf(" context mflags: %08lx\n", flags); + + mnt_free_context(cxt); + return 0; +} + +static int test_mountall(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_context *cxt; + struct libmnt_iter *itr; + struct libmnt_fs *fs; + int mntrc, ignored, idx = 1; + + cxt = mnt_new_context(); + itr = mnt_new_iter(MNT_ITER_FORWARD); + + if (!cxt || !itr) + return -ENOMEM; + + if (argc > 2) { + if (argv[idx] && !strcmp(argv[idx], "-O")) { + mnt_context_set_options_pattern(cxt, argv[idx + 1]); + idx += 2; + } + if (argv[idx] && !strcmp(argv[idx], "-t")) { + mnt_context_set_fstype_pattern(cxt, argv[idx + 1]); + idx += 2; + } + } + + while (mnt_context_next_mount(cxt, itr, &fs, &mntrc, &ignored) == 0) { + + const char *tgt = mnt_fs_get_target(fs); + + if (ignored == 1) + printf("%s: ignored: not match\n", tgt); + else if (ignored == 2) + printf("%s: ignored: already mounted\n", tgt); + + else if (!mnt_context_get_status(cxt)) { + if (mntrc > 0) { + errno = mntrc; + warn("%s: mount failed", tgt); + } else + warnx("%s: mount failed", tgt); + } else + printf("%s: successfully mounted\n", tgt); + } + + mnt_free_context(cxt); + return 0; +} + + + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--mount", test_mount, "[-o <opts>] [-t <type>] <spec>|<src> <target>" }, + { "--umount", test_umount, "[-t <type>] [-f][-l][-r] <src>|<target>" }, + { "--mount-all", test_mountall, "[-O <pattern>] [-t <pattern] mount all filesystems from fstab" }, + { "--flags", test_flags, "[-o <opts>] <spec>" }, + { "--cxtsync", test_cxtsync, "<fsopts> <cxtopts> <fsopts>" }, + { "--search-helper", test_search_helper, "<fstype>" }, + { NULL }}; + + umask(S_IWGRP|S_IWOTH); /* to be compatible with mount(8) */ + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c new file mode 100644 index 0000000..41986e7 --- /dev/null +++ b/libmount/src/context_mount.c @@ -0,0 +1,1866 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2010-2018 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. + */ + +/** + * SECTION: context-mount + * @title: Mount context + * @short_description: high-level API to mount operation. + */ +#include <sys/wait.h> +#include <sys/mount.h> + +#include "mountP.h" +#include "strutils.h" + +#if defined(HAVE_SMACK) +static int is_option(const char *name, const char *const *names) +{ + const char *const *p; + + for (p = names; p && *p; p++) { + if (strcmp(name, *p) == 0) + return 1; + } + return 0; +} +#endif /* HAVE_SMACK */ + +/* + * this has to be called after mnt_context_evaluate_permissions() + */ +static int fix_optstr(struct libmnt_context *cxt) +{ + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + struct libmnt_ns *ns_old; + const char *val; + int rc = 0; + + assert(cxt); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (cxt->flags & MNT_FL_MOUNTOPTS_FIXED) + return 0; + + DBG(CXT, ul_debugobj(cxt, "--> preparing options")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -EINVAL; + + ns_old = mnt_context_switch_origin_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* Fix user (convert "user" to "user=username") */ + if (mnt_context_is_restricted(cxt)) { + opt = mnt_optlist_get_opt(ol, MNT_MS_USER, cxt->map_userspace); + if (opt) { + char *name = mnt_get_username(getuid()); + + if (!name) + rc = -ENOMEM; + else { + rc = mnt_opt_set_value(opt, name); + free(name); + } + if (rc) + goto done; + } + } + + /* Fix UID */ + opt = mnt_optlist_get_named(ol, "uid", NULL); + if (opt && (val = mnt_opt_get_value(opt)) && !isdigit_string(val)) { + uid_t id; + + if (strcmp(val, "useruid") == 0) /* UID of the current user */ + id = getuid(); + else + rc = mnt_get_uid(val, &id); /* UID for the username */ + if (!rc) + rc = mnt_opt_set_u64value(opt, id); + if (rc) + goto done; + } + + /* Fix GID */ + opt = mnt_optlist_get_named(ol, "gid", NULL); + if (opt && (val = mnt_opt_get_value(opt)) && !isdigit_string(val)) { + gid_t id; + + if (strcmp(val, "usergid") == 0) /* UID of the current user */ + id = getgid(); + else + rc = mnt_get_gid(val, &id); /* UID for the groupname */ + if (!rc) + rc = mnt_opt_set_u64value(opt, id); + if (rc) + goto done; + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + +#ifdef HAVE_SMACK + /* Fix Smack */ + if (access("/sys/fs/smackfs", F_OK) != 0) { + struct libmnt_iter itr; + + static const char *const smack_options[] = { + "smackfsdef", + "smackfsfloor", + "smackfshat", + "smackfsroot", + "smackfstransmute", + NULL, + }; + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { + if (!is_option(mnt_opt_get_name(opt), smack_options)) + continue; + rc = mnt_optlist_remove_opt(ol, opt); + if (rc) + goto done; + } + } +#endif + rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_OPTIONS); +done: + DBG(CXT, ul_debugobj(cxt, "<-- preparing options done [rc=%d]", rc)); + cxt->flags |= MNT_FL_MOUNTOPTS_FIXED; + + if (rc) + rc = -MNT_ERR_MOUNTOPT; + return rc; +} + +/* + * this has to be called before fix_optstr() + * + * Note that user=<name> may be used by some filesystems as a filesystem + * specific option (e.g. cifs). Yes, developers of such filesystems have + * allocated pretty hot place in hell... + */ +static int evaluate_permissions(struct libmnt_context *cxt) +{ + struct libmnt_optlist *ol; + struct libmnt_opt *opt = NULL; + + unsigned long user_flags = 0; /* userspace mount flags */ + int rc; + + assert(cxt); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt->fs) + return 0; + + DBG(CXT, ul_debugobj(cxt, "mount: evaluating permissions")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -EINVAL; + + /* get userspace mount flags (user[=<name>] etc.*/ + rc = mnt_optlist_get_flags(ol, &user_flags, cxt->map_userspace, 0); + if (rc) + return rc; + + /* + * Ignore user=<name> (if <name> is set). Let's keep it hidden + * for normal library operations, but visible for /sbin/mount.<type> + * helpers. + */ + if (user_flags & MNT_MS_USER + && (opt = mnt_optlist_get_opt(ol, MNT_MS_USER, cxt->map_userspace)) + && mnt_opt_has_value(opt)) { + DBG(CXT, ul_debugobj(cxt, "perms: user=<name> detected, ignore")); + + cxt->flags |= MNT_FL_SAVED_USER; + + mnt_opt_set_external(opt, 1); + user_flags &= ~MNT_MS_USER; + } + + if (!mnt_context_is_restricted(cxt)) { + /* + * superuser mount + * + * Let's convert user, users, owenr and groups to MS_* flags + * to be compatible with non-root execution. + * + * The old deprecated way is to use mnt_optstr_get_flags(). + */ + if (user_flags & (MNT_MS_OWNER | MNT_MS_GROUP)) + rc = mnt_optlist_remove_flags(ol, + MNT_MS_OWNER | MNT_MS_GROUP, cxt->map_userspace); + + if (!rc && (user_flags & MNT_MS_OWNER)) + rc = mnt_optlist_insert_flags(ol, + MS_OWNERSECURE, cxt->map_linux, + MNT_MS_OWNER, cxt->map_userspace); + + if (!rc && (user_flags & MNT_MS_GROUP)) + rc = mnt_optlist_insert_flags(ol, + MS_OWNERSECURE, cxt->map_linux, + MNT_MS_GROUP, cxt->map_userspace); + + if (!rc && (user_flags & MNT_MS_USER) + && (opt = mnt_optlist_get_opt(ol, MNT_MS_USER, cxt->map_userspace)) + && !mnt_opt_has_value(opt)) + rc = mnt_optlist_insert_flags(ol, MS_SECURE, cxt->map_linux, + MNT_MS_USER, cxt->map_userspace); + + if (!rc && (user_flags & MNT_MS_USERS)) + rc = mnt_optlist_insert_flags(ol, MS_SECURE, cxt->map_linux, + MNT_MS_USERS, cxt->map_userspace); + + DBG(CXT, ul_debugobj(cxt, "perms: superuser [rc=%d]", rc)); + if (rc) + return rc; + + if (user_flags & (MNT_MS_OWNER | MNT_MS_GROUP | + MNT_MS_USER | MNT_MS_USERS)) + mnt_optlist_merge_opts(ol); + } else { + + /* + * user mount + */ + if (!mnt_context_tab_applied(cxt)) + { + DBG(CXT, ul_debugobj(cxt, "perms: fstab not applied, ignore user mount")); + return -EPERM; + } + + /* + * Insert MS_SECURE between system flags on position where is MNT_MS_USER + */ + if ((user_flags & MNT_MS_USER) + && (rc = mnt_optlist_insert_flags(ol, MS_SECURE, cxt->map_linux, + MNT_MS_USER, cxt->map_userspace))) + return rc; + + if ((user_flags & MNT_MS_USERS) + && (rc = mnt_optlist_insert_flags(ol, MS_SECURE, cxt->map_linux, + MNT_MS_USERS, cxt->map_userspace))) + return rc; + + /* + * MS_OWNER: Allow owners to mount when fstab contains the + * owner option. Note that this should never be used in a high + * security environment, but may be useful to give people at + * the console the possibility of mounting a floppy. MS_GROUP: + * Allow members of device group to mount. (Martin Dickopp) + */ + if (user_flags & (MNT_MS_OWNER | MNT_MS_GROUP)) { + struct stat sb; + struct libmnt_cache *cache = NULL; + char *xsrc = NULL; + const char *srcpath = mnt_fs_get_srcpath(cxt->fs); + + DBG(CXT, ul_debugobj(cxt, "perms: owner/group")); + + if (!srcpath) { /* Ah... source is TAG */ + cache = mnt_context_get_cache(cxt); + xsrc = mnt_resolve_spec( + mnt_context_get_source(cxt), + cache); + srcpath = xsrc; + } + if (!srcpath) { + DBG(CXT, ul_debugobj(cxt, "perms: src undefined")); + return -EPERM; + } + + if (strncmp(srcpath, "/dev/", 5) == 0 && + stat(srcpath, &sb) == 0 && + (((user_flags & MNT_MS_OWNER) && getuid() == sb.st_uid) || + ((user_flags & MNT_MS_GROUP) && mnt_in_group(sb.st_gid)))) { + + /* insert MS_OWNERSECURE between system flags */ + if (user_flags & MNT_MS_OWNER) + mnt_optlist_insert_flags(ol, + MS_OWNERSECURE, cxt->map_linux, + MNT_MS_OWNER, cxt->map_userspace); + if (user_flags & MNT_MS_GROUP) + mnt_optlist_insert_flags(ol, + MS_OWNERSECURE, cxt->map_linux, + MNT_MS_GROUP, cxt->map_userspace); + + /* continue as like "user" was specified */ + user_flags |= MNT_MS_USER; + mnt_optlist_append_flags(ol, MNT_MS_USER, cxt->map_userspace); + } + + if (!cache) + free(xsrc); + } + + if (!(user_flags & (MNT_MS_USER | MNT_MS_USERS))) { + DBG(CXT, ul_debugobj(cxt, "perms: evaluation ends with -EPERMS [flags=0x%08lx]", user_flags)); + return -EPERM; + } + + /* we have modified some flags (noexec, ...), let's cleanup the + * options to remove duplicate stuff etc.*/ + mnt_optlist_merge_opts(ol); + } + + return 0; +} + +/* + * mnt_context_helper_setopt() backend + * + * This function applies the mount.type command line option (for example parsed + * by getopt() or getopt_long()) to @cxt. All unknown options are ignored and + * then 1 is returned. + * + * Returns: negative number on error, 1 if @c is unknown option, 0 on success. + */ +int mnt_context_mount_setopt(struct libmnt_context *cxt, int c, char *arg) +{ + int rc = -EINVAL; + + assert(cxt); + assert(cxt->action == MNT_ACT_MOUNT); + + switch(c) { + case 'f': + rc = mnt_context_enable_fake(cxt, TRUE); + break; + case 'n': + rc = mnt_context_disable_mtab(cxt, TRUE); + break; + case 'r': + rc = mnt_context_append_options(cxt, "ro"); + break; + case 'v': + rc = mnt_context_enable_verbose(cxt, TRUE); + break; + case 'w': + rc = mnt_context_append_options(cxt, "rw"); + break; + case 'o': + if (arg) + rc = mnt_context_append_options(cxt, arg); + break; + case 's': + rc = mnt_context_enable_sloppy(cxt, TRUE); + break; + case 't': + if (arg) + rc = mnt_context_set_fstype(cxt, arg); + break; + case 'N': + if (arg) + rc = mnt_context_set_target_ns(cxt, arg); + break; + default: + return 1; + } + + return rc; +} + +static int exec_helper(struct libmnt_context *cxt) +{ + struct libmnt_ns *ns_tgt = mnt_context_get_target_ns(cxt); + char *namespace = NULL; + int rc; + pid_t pid; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "mount: executing helper %s", cxt->helper)); + + if (ns_tgt->fd != -1 + && asprintf(&namespace, "/proc/%i/fd/%i", + getpid(), ns_tgt->fd) == -1) { + return -ENOMEM; + } + + DBG_FLUSH; + + pid = fork(); + switch (pid) { + case 0: + { + const char *args[14], *type; + struct libmnt_optlist *ol = mnt_context_get_optlist(cxt); + struct libmnt_opt *opt; + const char *o = NULL; + int i = 0; + + if (!ol) + _exit(EXIT_FAILURE); + + /* Call helper with original user=<name> (aka "saved user") + * or remove the username at all. + */ + opt = mnt_optlist_get_opt(ol, MNT_MS_USER, cxt->map_userspace); + if (opt && !(cxt->flags & MNT_FL_SAVED_USER)) + mnt_opt_set_value(opt, NULL); + + if (mnt_optlist_get_optstr(ol, &o, NULL, MNT_OL_FLTR_HELPERS)) + _exit(EXIT_FAILURE); + + if (drop_permissions() != 0) + _exit(EXIT_FAILURE); + + if (!mnt_context_switch_origin_ns(cxt)) + _exit(EXIT_FAILURE); + + type = mnt_fs_get_fstype(cxt->fs); + + args[i++] = cxt->helper; /* 1 */ + args[i++] = mnt_fs_get_srcpath(cxt->fs);/* 2 */ + args[i++] = mnt_fs_get_target(cxt->fs); /* 3 */ + + if (mnt_context_is_sloppy(cxt)) + args[i++] = "-s"; /* 4 */ + if (mnt_context_is_fake(cxt)) + args[i++] = "-f"; /* 5 */ + if (mnt_context_is_nomtab(cxt)) + args[i++] = "-n"; /* 6 */ + if (mnt_context_is_verbose(cxt)) + args[i++] = "-v"; /* 7 */ + if (o) { + args[i++] = "-o"; /* 8 */ + args[i++] = o; /* 9 */ + } + if (type + && strchr(type, '.') + && !endswith(cxt->helper, type)) { + args[i++] = "-t"; /* 10 */ + args[i++] = type; /* 11 */ + } + if (namespace) { + args[i++] = "-N"; /* 11 */ + args[i++] = namespace; /* 12 */ + } + args[i] = NULL; /* 13 */ + for (i = 0; args[i]; i++) + DBG(CXT, ul_debugobj(cxt, "argv[%d] = \"%s\"", + i, args[i])); + DBG_FLUSH; + execv(cxt->helper, (char * const *) args); + _exit(EXIT_FAILURE); + } + default: + { + int st; + + if (waitpid(pid, &st, 0) == (pid_t) -1) { + cxt->helper_status = -1; + rc = -errno; + } else { + cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1; + cxt->helper_exec_status = rc = 0; + } + DBG(CXT, ul_debugobj(cxt, "%s executed [status=%d, rc=%d%s]", + cxt->helper, + cxt->helper_status, rc, + rc ? " waitpid failed" : "")); + break; + } + + case -1: + cxt->helper_exec_status = rc = -errno; + DBG(CXT, ul_debugobj(cxt, "fork() failed")); + break; + } + + free(namespace); + return rc; +} + +/* + * The default is to use fstype from cxt->fs, this could be overwritten by + * @try_type argument. If @try_type is specified then mount with MS_SILENT. + * + * Returns: 0 on success, + * >0 in case of mount(2) error (returns syscall errno), + * <0 in case of other errors. + */ +static int do_mount(struct libmnt_context *cxt, const char *try_type) +{ + int rc = 0; + char *org_type = NULL; + struct libmnt_optlist *ol = NULL; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + mnt_context_reset_status(cxt); + + if (try_type) { + rc = mnt_context_prepare_helper(cxt, "mount", try_type); + if (rc) + return rc; + } + + if (cxt->helper) + return exec_helper(cxt); + + if (try_type) { + ol = mnt_context_get_optlist(cxt); + assert(ol); + + mnt_optlist_append_flags(ol, MS_SILENT, cxt->map_linux); + if (mnt_fs_get_fstype(cxt->fs)) { + org_type = strdup(mnt_fs_get_fstype(cxt->fs)); + if (!org_type) { + rc = -ENOMEM; + goto done; + } + } + mnt_fs_set_fstype(cxt->fs, try_type); + } + + + /* + * mount(2) or others syscalls + */ + if (!rc) + rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT); + + if (rc == 0 && mnt_context_is_fake(cxt)) { + DBG(CXT, ul_debugobj(cxt, "FAKE (-f) set status=0")); + cxt->syscall_status = 0; + } + + if (org_type && rc != 0) + __mnt_fs_set_fstype_ptr(cxt->fs, org_type); + org_type = NULL; + + if (rc == 0 && try_type && cxt->update) { + struct libmnt_fs *fs = mnt_update_get_fs(cxt->update); + if (fs) + rc = mnt_fs_set_fstype(fs, try_type); + } + +done: + if (try_type && ol) + mnt_optlist_remove_flags(ol, MS_SILENT, cxt->map_linux); + free(org_type); + return rc; +} + +static int is_success_status(struct libmnt_context *cxt) +{ + if (mnt_context_helper_executed(cxt)) + return mnt_context_get_helper_status(cxt) == 0; + + if (mnt_context_syscall_called(cxt)) + return mnt_context_get_status(cxt) == 1; + + return 0; +} + +/* try mount(2) for all items in comma separated list of the filesystem @types */ +static int do_mount_by_types(struct libmnt_context *cxt, const char *types) +{ + int rc = -EINVAL; + char *p, *p0; + + assert(cxt); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "trying to mount by FS list '%s'", types)); + + p0 = p = strdup(types); + if (!p) + return -ENOMEM; + do { + char *autotype = NULL; + char *end = strchr(p, ','); + + if (end) + *end = '\0'; + + DBG(CXT, ul_debugobj(cxt, "-->trying '%s'", p)); + + /* Let's support things like "udf,iso9660,auto" */ + if (strcmp(p, "auto") == 0) { + rc = mnt_context_guess_srcpath_fstype(cxt, &autotype); + if (rc) { + DBG(CXT, ul_debugobj(cxt, "failed to guess FS type [rc=%d]", rc)); + free(p0); + free(autotype); + return rc; + } + p = autotype; + DBG(CXT, ul_debugobj(cxt, " --> '%s'", p)); + } + + if (p) + rc = do_mount(cxt, p); + p = end ? end + 1 : NULL; + free(autotype); + } while (!is_success_status(cxt) && p); + + free(p0); + return rc; +} + + +static int do_mount_by_pattern(struct libmnt_context *cxt, const char *pattern) +{ + int neg = pattern && strncmp(pattern, "no", 2) == 0; + int rc = -EINVAL; + char **filesystems, **fp; + struct libmnt_ns *ns_old; + + assert(cxt); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + /* + * Use the pattern as list of the filesystems + */ + if (!neg && pattern) { + DBG(CXT, ul_debugobj(cxt, "use FS pattern as FS list")); + return do_mount_by_types(cxt, pattern); + } + + DBG(CXT, ul_debugobj(cxt, "trying to mount by FS pattern '%s'", pattern)); + + /* + * Apply pattern to /etc/filesystems and /proc/filesystems + */ + ns_old = mnt_context_switch_origin_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + rc = mnt_get_filesystems(&filesystems, neg ? pattern : NULL); + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + if (rc) + return rc; + + if (filesystems == NULL) + return -MNT_ERR_NOFSTYPE; + + for (fp = filesystems; *fp; fp++) { + DBG(CXT, ul_debugobj(cxt, " ##### trying '%s'", *fp)); + rc = do_mount(cxt, *fp); + if (is_success_status(cxt)) + break; + if (mnt_context_get_syscall_errno(cxt) != EINVAL && + mnt_context_get_syscall_errno(cxt) != ENODEV) + break; + } + mnt_free_filesystems(filesystems); + return rc; +} + +static int prepare_target(struct libmnt_context *cxt) +{ + const char *tgt, *prefix; + int rc = 0; + struct libmnt_ns *ns_old; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "--> preparing target path")); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + + /* apply prefix */ + prefix = mnt_context_get_target_prefix(cxt); + if (prefix) { + const char *p = *tgt == '/' ? tgt + 1 : tgt; + + if (!*p) + /* target is "/", use "/prefix" */ + rc = mnt_fs_set_target(cxt->fs, prefix); + else { + char *path = NULL; + + if (asprintf(&path, "%s/%s", prefix, p) <= 0) + rc = -ENOMEM; + else { + rc = mnt_fs_set_target(cxt->fs, path); + free(path); + } + } + if (rc) + return rc; + tgt = mnt_fs_get_target(cxt->fs); + } + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* canonicalize the path */ + if (rc == 0) { + struct libmnt_cache *cache = mnt_context_get_cache(cxt); + + if (cache) { + char *path = mnt_resolve_path(tgt, cache); + if (path && strcmp(path, tgt) != 0) + rc = mnt_fs_set_target(cxt->fs, path); + } + } + + if (rc == 0) + rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_TARGET); + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + DBG(CXT, ul_debugobj(cxt, "final target '%s' [rc=%d]", + mnt_fs_get_target(cxt->fs), rc)); + return rc; +} + +/** + * mnt_context_prepare_mount: + * @cxt: context + * + * Prepare context for mounting, unnecessary for mnt_context_mount(). + * + * Returns: negative number on error, zero on success + */ +int mnt_context_prepare_mount(struct libmnt_context *cxt) +{ + int rc = -EINVAL; + struct libmnt_ns *ns_old; + + if (!cxt || !cxt->fs || mnt_fs_is_swaparea(cxt->fs)) + return -EINVAL; + if (!mnt_fs_get_source(cxt->fs) && !mnt_fs_get_target(cxt->fs)) + return -EINVAL; + if (cxt->flags & MNT_FL_PREPARED) + return 0; + + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + cxt->action = MNT_ACT_MOUNT; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + DBG(CXT, ul_debugobj(cxt, "mount: preparing")); + + rc = mnt_context_apply_fstab(cxt); + if (!rc) + rc = mnt_context_merge_mflags(cxt); + if (!rc && cxt->fs && cxt->optlist) + rc = mnt_fs_follow_optlist(cxt->fs, cxt->optlist); + if (!rc) + rc = evaluate_permissions(cxt); + if (!rc) + rc = fix_optstr(cxt); + if (!rc) + rc = mnt_context_prepare_srcpath(cxt); + if (!rc) + rc = mnt_context_guess_fstype(cxt); + if (!rc) + rc = prepare_target(cxt); + if (!rc) + rc = mnt_context_prepare_helper(cxt, "mount", NULL); + + if (!rc && mnt_context_is_onlyonce(cxt)) { + int mounted = 0; + rc = mnt_context_is_fs_mounted(cxt, cxt->fs, &mounted); + if (rc == 0 && mounted == 1) { + rc = -MNT_ERR_ONLYONCE; + goto end; + } + } + + if (!rc) + rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP); + + if (rc) { + DBG(CXT, ul_debugobj(cxt, "mount: preparing failed")); + goto end; + } + + cxt->flags |= MNT_FL_PREPARED; + +end: + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return rc; +} + +/** + * mnt_context_do_mount + * @cxt: context + * + * Call mount(2) or mount.type helper. Unnecessary for mnt_context_mount(). + * + * Note that this function could be called only once. If you want to mount + * another source or target, then you have to call mnt_reset_context(). + * + * If you want to call mount(2) for the same source and target with different + * mount flags or fstype, then call mnt_context_reset_status() and then try + * again mnt_context_do_mount(). + * + * WARNING: non-zero return code does not mean that mount(2) syscall or + * mount.type helper wasn't successfully called. + * + * Check mnt_context_get_status() after error! See mnt_context_mount() for more + * details about errors and warnings. + * + * Returns: 0 on success; + * >0 in case of mount(2) error (returns syscall errno), + * <0 in case of other errors. + */ +int mnt_context_do_mount(struct libmnt_context *cxt) +{ + const char *type; + int res = 0, rc = 0; + struct libmnt_ns *ns_old; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + assert((cxt->flags & MNT_FL_PREPARED)); + assert((cxt->action == MNT_ACT_MOUNT)); + + DBG(CXT, ul_debugobj(cxt, "mount: do mount")); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* before mount stage */ + rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_PRE); + if (rc) + return rc; + + /* mount stage */ + type = mnt_fs_get_fstype(cxt->fs); + if (type) { + if (strchr(type, ',')) + /* this only happens if fstab contains a list of filesystems */ + res = do_mount_by_types(cxt, type); + else + res = do_mount(cxt, NULL); + } else + res = do_mount_by_pattern(cxt, cxt->fstype_pattern); + + /* after mount stage */ + if (res == 0) { + rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_POST); + if (rc) + return rc; + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + DBG(CXT, ul_debugobj(cxt, "mnt_context_do_mount() done [rc=%d]", res)); + return res; +} + +/* + * Returns mountinfo FS entry of context source patch if the source is already + * mounted. This function is used for "already mounted" message or to get FS of + * re-used loop device. + */ +static struct libmnt_fs *get_already_mounted_source(struct libmnt_context *cxt) +{ + const char *src; + struct libmnt_table *tb; + + assert(cxt); + + src = mnt_fs_get_srcpath(cxt->fs); + + if (src && mnt_context_get_mountinfo(cxt, &tb) == 0) { + struct libmnt_iter itr; + struct libmnt_fs *fs; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + const char *s = mnt_fs_get_srcpath(fs), + *t = mnt_fs_get_target(fs); + + if (t && s && mnt_fs_streq_srcpath(fs, src)) + return fs; + } + } + return NULL; +} + +/* + * Checks if source filesystem superblock is already ro-mounted. Note that we + * care about FS superblock as VFS node is irrelevant here. + */ +static int is_source_already_rdonly(struct libmnt_context *cxt) +{ + struct libmnt_fs *fs = get_already_mounted_source(cxt); + const char *opts = fs ? mnt_fs_get_fs_options(fs) : NULL; + + return opts && mnt_optstr_get_option(opts, "ro", NULL, NULL) == 0; +} + +/** + * mnt_context_finalize_mount: + * @cxt: context + * + * Mtab update, etc. Unnecessary for mnt_context_mount(), but should be called + * after mnt_context_do_mount(). See also mnt_context_set_syscall_status(). + * + * Returns: negative number on error, 0 on success. + */ +int mnt_context_finalize_mount(struct libmnt_context *cxt) +{ + int rc; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + assert((cxt->flags & MNT_FL_PREPARED)); + + rc = mnt_context_prepare_update(cxt); + if (!rc) + rc = mnt_context_update_tabs(cxt); + return rc; +} + +/** + * mnt_context_mount: + * @cxt: mount context + * + * High-level, mounts the filesystem by mount(2) or fork()+exec(/sbin/mount.type). + * + * This is similar to: + * + * mnt_context_prepare_mount(cxt); + * mnt_context_do_mount(cxt); + * mnt_context_finalize_mount(cxt); + * + * See also mnt_context_disable_helpers(). + * + * Note that this function should be called only once. If you want to mount with + * different settings, then you have to call mnt_reset_context(). It's NOT enough + * to call mnt_context_reset_status(). If you want to call this function more than + * once, the whole context has to be reset. + * + * WARNING: non-zero return code does not mean that mount(2) syscall or + * mount.type helper wasn't successfully called. + * + * Always use mnt_context_get_status(): + * + * <informalexample> + * <programlisting> + * rc = mnt_context_mount(cxt); + * + * if (mnt_context_helper_executed(cxt)) + * return mnt_context_get_helper_status(cxt); + * if (rc == 0 && mnt_context_get_status(cxt) == 1) + * return MNT_EX_SUCCESS; + * return MNT_EX_FAIL; + * </programlisting> + * </informalexample> + * + * or mnt_context_get_excode() to generate mount(8) compatible error + * or warning message: + * + * <informalexample> + * <programlisting> + * rc = mnt_context_mount(cxt); + * rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf)); + * if (buf) + * warnx(_("%s: %s"), mnt_context_get_target(cxt), buf); + * return rc; // MNT_EX_* + * </programlisting> + * </informalexample> + * + * Returns: 0 on success; + * >0 in case of mount(2) error (returns syscall errno), + * <0 in case of other errors. + */ +int mnt_context_mount(struct libmnt_context *cxt) +{ + int rc; + struct libmnt_ns *ns_old; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + +again: + rc = mnt_context_prepare_mount(cxt); + if (!rc) + rc = mnt_context_prepare_update(cxt); + if (!rc) + rc = mnt_context_do_mount(cxt); + if (!rc) + rc = mnt_context_update_tabs(cxt); + + /* + * Read-only device or already read-only mounted FS. + * Try mount the filesystem read-only. + */ + if ((rc == -EROFS && !mnt_context_syscall_called(cxt)) /* before syscall; rdonly loopdev */ + || mnt_context_get_syscall_errno(cxt) == EROFS /* syscall failed with EROFS */ + || mnt_context_get_syscall_errno(cxt) == EACCES /* syscall failed with EACCES */ + || (mnt_context_get_syscall_errno(cxt) == EBUSY /* already ro-mounted FS */ + && is_source_already_rdonly(cxt))) + { + unsigned long mflags = 0; + + mnt_context_get_mflags(cxt, &mflags); + + if (!(mflags & MS_RDONLY) /* not yet RDONLY */ + && !(mflags & MS_REMOUNT) /* not remount */ + && !(mflags & MS_BIND) /* not bin mount */ + && !mnt_context_is_rwonly_mount(cxt)) { /* no explicit read-write */ + + assert(!(cxt->flags & MNT_FL_FORCED_RDONLY)); + DBG(CXT, ul_debugobj(cxt, "write-protected source, trying RDONLY.")); + + mnt_context_reset_status(cxt); + mnt_context_set_mflags(cxt, mflags | MS_RDONLY); + cxt->flags |= MNT_FL_FORCED_RDONLY; + goto again; + } + } + + if (rc == 0) + rc = mnt_context_call_hooks(cxt, MNT_STAGE_POST); + + mnt_context_deinit_hooksets(cxt); + + if (!mnt_context_switch_ns(cxt, ns_old)) + rc = -MNT_ERR_NAMESPACE; + + DBG(CXT, ul_debugobj(cxt, "mnt_context_mount() done [rc=%d]", rc)); + return rc; +} + +/** + * mnt_context_next_mount: + * @cxt: context + * @itr: iterator + * @fs: returns the current filesystem + * @mntrc: returns the return code from mnt_context_mount() + * @ignored: returns 1 for non-matching and 2 for already mounted filesystems + * + * This function tries to mount the next filesystem from fstab (as returned by + * mnt_context_get_fstab()). See also mnt_context_set_fstab(). + * + * You can filter out filesystems by: + * mnt_context_set_options_pattern() to simulate mount -a -O pattern + * mnt_context_set_fstype_pattern() to simulate mount -a -t pattern + * + * If the filesystem is already mounted or does not match defined criteria, + * then the mnt_context_next_mount() function returns zero, but the @ignored is + * non-zero. Note that the root filesystem and filesystems with "noauto" option + * are always ignored. + * + * If mount(2) syscall or mount.type helper failed, then the + * mnt_context_next_mount() function returns zero, but the @mntrc is non-zero. + * Use also mnt_context_get_status() to check if the filesystem was + * successfully mounted. + * + * See mnt_context_mount() for more details about errors and warnings. + * + * Returns: 0 on success, + * <0 in case of error (!= mount(2) errors) + * 1 at the end of the list. + */ +int mnt_context_next_mount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, + int *ignored) +{ + struct libmnt_table *fstab, *mountinfo; + const char *o, *tgt; + int rc, mounted = 0; + + if (ignored) + *ignored = 0; + if (mntrc) + *mntrc = 0; + + if (!cxt || !fs || !itr) + return -EINVAL; + + /* ingore --onlyonce, it's default behavior for --all */ + mnt_context_enable_onlyonce(cxt, 0); + + rc = mnt_context_get_fstab(cxt, &fstab); + if (rc) + return rc; + + rc = mnt_table_next_fs(fstab, itr, fs); + if (rc != 0) + return rc; /* more filesystems (or error) */ + + o = mnt_fs_get_user_options(*fs); + tgt = mnt_fs_get_target(*fs); + + DBG(CXT, ul_debugobj(cxt, "next-mount: trying %s", tgt)); + + /* ignore swap */ + if (mnt_fs_is_swaparea(*fs) || + + /* ignore root filesystem */ + (tgt && (strcmp(tgt, "/") == 0 || strcmp(tgt, "root") == 0)) || + + /* ignore noauto filesystems */ + (o && mnt_optstr_get_option(o, "noauto", NULL, NULL) == 0) || + + /* ignore filesystems which don't match options patterns */ + (cxt->fstype_pattern && !mnt_fs_match_fstype(*fs, + cxt->fstype_pattern)) || + + /* ignore filesystems which don't match type patterns */ + (cxt->optstr_pattern && !mnt_fs_match_options(*fs, + cxt->optstr_pattern))) { + if (ignored) + *ignored = 1; + DBG(CXT, ul_debugobj(cxt, "next-mount: not-match " + "[fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]", + mnt_fs_get_fstype(*fs), + cxt->fstype_pattern, + mnt_fs_get_options(*fs), + cxt->optstr_pattern)); + return 0; + } + + /* ignore already mounted filesystems */ + rc = mnt_context_is_fs_mounted(cxt, *fs, &mounted); + if (rc) { + if (mnt_table_is_empty(cxt->mountinfo)) { + DBG(CXT, ul_debugobj(cxt, "next-mount: no mount table [rc=%d], ignore", rc)); + rc = 0; + if (ignored) + *ignored = 1; + } + return rc; + } + if (mounted) { + if (ignored) + *ignored = 2; + return 0; + } + + /* Save mount options, etc. -- this is effective for the first + * mnt_context_next_mount() call only. Make sure that cxt has not set + * source, target or fstype. + */ + if (!mnt_context_has_template(cxt)) { + mnt_context_set_source(cxt, NULL); + mnt_context_set_target(cxt, NULL); + mnt_context_set_fstype(cxt, NULL); + mnt_context_save_template(cxt); + } + + /* reset context, but protect mountinfo */ + mountinfo = cxt->mountinfo; + cxt->mountinfo = NULL; + mnt_reset_context(cxt); + cxt->mountinfo = mountinfo; + + if (mnt_context_is_fork(cxt)) { + rc = mnt_fork_context(cxt); + if (rc) + return rc; /* fork error */ + + if (mnt_context_is_parent(cxt)) { + return 0; /* parent */ + } + } + + /* + * child or non-forked + */ + + /* copy stuff from fstab to context */ + rc = mnt_context_apply_fs(cxt, *fs); + if (!rc) { + /* + * "-t <pattern>" is used to filter out fstab entries, but for ordinary + * mount operation -t means "-t <type>". We have to zeroize the pattern + * to avoid misinterpretation. + */ + char *pattern = cxt->fstype_pattern; + cxt->fstype_pattern = NULL; + + rc = mnt_context_mount(cxt); + + cxt->fstype_pattern = pattern; + + if (mntrc) + *mntrc = rc; + } + + if (mnt_context_is_child(cxt)) { + DBG(CXT, ul_debugobj(cxt, "next-mount: child exit [rc=%d]", rc)); + DBG_FLUSH; + _exit(rc); + } + return 0; +} + + +/** + * mnt_context_next_remount: + * @cxt: context + * @itr: iterator + * @fs: returns the current filesystem + * @mntrc: returns the return code from mnt_context_mount() + * @ignored: returns 1 for non-matching + * + * This function tries to remount the next mounted filesystem (as returned by + * mnt_context_get_mtab()). + * + * You can filter out filesystems by: + * mnt_context_set_options_pattern() to simulate mount -a -O pattern + * mnt_context_set_fstype_pattern() to simulate mount -a -t pattern + * + * If the filesystem does not match defined criteria, then the + * mnt_context_next_remount() function returns zero, but the @ignored is + * non-zero. + * + * IMPORTANT -- the mount operation is performed in the current context. + * The context is reset before the next mount (see mnt_reset_context()). + * The context setting related to the filesystem (e.g. mount options, + * etc.) are protected. + + * If mount(2) syscall or mount.type helper failed, then the + * mnt_context_next_mount() function returns zero, but the @mntrc is non-zero. + * Use also mnt_context_get_status() to check if the filesystem was + * successfully mounted. + * + * See mnt_context_mount() for more details about errors and warnings. + * + * Returns: 0 on success, + * <0 in case of error (!= mount(2) errors) + * 1 at the end of the list. + * + * Since: 2.34 + */ +int mnt_context_next_remount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, + int *ignored) +{ + struct libmnt_table *mountinfo; + const char *tgt; + int rc; + + if (ignored) + *ignored = 0; + if (mntrc) + *mntrc = 0; + + if (!cxt || !fs || !itr) + return -EINVAL; + + rc = mnt_context_get_mountinfo(cxt, &mountinfo); + if (rc) + return rc; + + rc = mnt_table_next_fs(mountinfo, itr, fs); + if (rc != 0) + return rc; /* more filesystems (or error) */ + + tgt = mnt_fs_get_target(*fs); + + DBG(CXT, ul_debugobj(cxt, "next-remount: trying %s", tgt)); + + /* ignore filesystems which don't match options patterns */ + if ((cxt->fstype_pattern && !mnt_fs_match_fstype(*fs, + cxt->fstype_pattern)) || + + /* ignore filesystems which don't match type patterns */ + (cxt->optstr_pattern && !mnt_fs_match_options(*fs, + cxt->optstr_pattern))) { + if (ignored) + *ignored = 1; + DBG(CXT, ul_debugobj(cxt, "next-remount: not-match " + "[fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]", + mnt_fs_get_fstype(*fs), + cxt->fstype_pattern, + mnt_fs_get_options(*fs), + cxt->optstr_pattern)); + return 0; + } + + /* Save mount options, etc. -- this is effective for the first + * mnt_context_next_remount() call only. Make sure that cxt has not set + * source, target or fstype. + */ + if (!mnt_context_has_template(cxt)) { + mnt_context_set_source(cxt, NULL); + mnt_context_set_target(cxt, NULL); + mnt_context_set_fstype(cxt, NULL); + mnt_context_save_template(cxt); + } + + /* restore original, but protect mountinfo */ + cxt->mountinfo = NULL; + mnt_reset_context(cxt); + cxt->mountinfo = mountinfo; + + rc = mnt_context_set_target(cxt, tgt); + if (!rc) { + /* + * "-t <pattern>" is used to filter out fstab entries, but for ordinary + * mount operation -t means "-t <type>". We have to zeroize the pattern + * to avoid misinterpretation. + */ + char *pattern = cxt->fstype_pattern; + cxt->fstype_pattern = NULL; + + rc = mnt_context_mount(cxt); + + cxt->fstype_pattern = pattern; + + if (mntrc) + *mntrc = rc; + rc = 0; + } + + return rc; +} + +/* + * Returns 1 if @dir parent is shared + */ +static int is_shared_tree(struct libmnt_context *cxt, const char *dir) +{ + struct libmnt_table *tb = NULL; + struct libmnt_fs *fs; + unsigned long mflags = 0; + char *mnt = NULL, *p; + int rc = 0; + struct libmnt_ns *ns_old; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + if (!dir) + return 0; + if (mnt_context_get_mountinfo(cxt, &tb) || !tb) + goto done; + + mnt = strdup(dir); + if (!mnt) + goto done; + p = strrchr(mnt, '/'); + if (!p) + goto done; + if (p > mnt) + *p = '\0'; + fs = mnt_table_find_mountpoint(tb, mnt, MNT_ITER_BACKWARD); + + rc = fs && mnt_fs_is_kernel(fs) + && mnt_fs_get_propagation(fs, &mflags) == 0 + && (mflags & MS_SHARED); +done: + free(mnt); + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; +} + +int mnt_context_get_mount_excode( + struct libmnt_context *cxt, + int rc, + char *buf, + size_t bufsz) +{ + int syserr; + struct stat st; + unsigned long uflags = 0, mflags = 0; + + int restricted = mnt_context_is_restricted(cxt); + const char *tgt = mnt_context_get_target(cxt); + const char *src = mnt_context_get_source(cxt); + + if (mnt_context_helper_executed(cxt)) { + /* + * /sbin/mount.<type> called, return status + */ + if (rc == -MNT_ERR_APPLYFLAGS && buf) + snprintf(buf, bufsz, _("WARNING: failed to apply propagation flags")); + + return mnt_context_get_helper_status(cxt); + } + + if (rc == 0 && mnt_context_get_status(cxt) == 1) { + /* + * Libmount success && syscall success. + */ + if (buf && mnt_context_forced_rdonly(cxt)) + snprintf(buf, bufsz, _("WARNING: source write-protected, mounted read-only")); + return MNT_EX_SUCCESS; + } + + mnt_context_get_mflags(cxt, &mflags); /* mount(2) flags */ + mnt_context_get_user_mflags(cxt, &uflags); /* userspace flags */ + + if (!mnt_context_syscall_called(cxt)) { + /* + * libmount errors (extra library checks) + */ + switch (rc) { + case -EPERM: + if (buf) + snprintf(buf, bufsz, _("operation permitted for root only")); + return MNT_EX_USAGE; + case -EBUSY: + if (buf) + snprintf(buf, bufsz, _("%s is already mounted"), src); + return MNT_EX_USAGE; + case -MNT_ERR_NOFSTAB: + if (!buf) + return MNT_EX_USAGE; + if (mnt_context_is_swapmatch(cxt)) + snprintf(buf, bufsz, _("can't find in %s"), + mnt_get_fstab_path()); + else if (tgt) + snprintf(buf, bufsz, _("can't find mount point in %s"), + mnt_get_fstab_path()); + else if (src) + snprintf(buf, bufsz, _("can't find mount source %s in %s"), + src, mnt_get_fstab_path()); + return MNT_EX_USAGE; + case -MNT_ERR_AMBIFS: + if (buf) + snprintf(buf, bufsz, _("more filesystems detected on %s; use -t <type> or wipefs(8)"), src); + return MNT_EX_USAGE; + case -MNT_ERR_NOFSTYPE: + if (buf) + snprintf(buf, bufsz, restricted ? + _("failed to determine filesystem type") : + _("no valid filesystem type specified")); + return MNT_EX_USAGE; + case -MNT_ERR_NOSOURCE: + if (uflags & MNT_MS_NOFAIL) + return MNT_EX_SUCCESS; + if (buf) { + if (src) + snprintf(buf, bufsz, _("can't find %s"), src); + else + snprintf(buf, bufsz, _("no mount source specified")); + } + return MNT_EX_USAGE; + case -MNT_ERR_MOUNTOPT: + if (buf) { + const char *opts = mnt_context_get_options(cxt); + + if (!opts) + opts = ""; + if (opts) + snprintf(buf, bufsz, errno ? + _("failed to parse mount options '%s': %m") : + _("failed to parse mount options '%s'"), opts); + else + snprintf(buf, bufsz, errno ? + _("failed to parse mount options: %m") : + _("failed to parse mount options")); + } + return MNT_EX_USAGE; + case -MNT_ERR_LOOPDEV: + if (buf) + snprintf(buf, bufsz, _("failed to setup loop device for %s"), src); + return MNT_EX_FAIL; + case -MNT_ERR_LOOPOVERLAP: + if (buf) + snprintf(buf, bufsz, _("overlapping loop device exists for %s"), src); + return MNT_EX_FAIL; + case -MNT_ERR_LOCK: + if (buf) + snprintf(buf, bufsz, _("locking failed")); + return MNT_EX_FILEIO; + case -MNT_ERR_NAMESPACE: + if (buf) + snprintf(buf, bufsz, _("failed to switch namespace")); + return MNT_EX_SYSERR; + case -MNT_ERR_ONLYONCE: + if (buf) + snprintf(buf, bufsz, _("filesystem already mounted")); + return MNT_EX_FAIL; + default: + return mnt_context_get_generic_excode(rc, buf, bufsz, _("mount failed: %m")); + } + + } else if (mnt_context_get_syscall_errno(cxt) == 0) { + /* + * mount(2) syscall success, but something else failed + * (probably error in utab processing). + */ + if (rc == -MNT_ERR_APPLYFLAGS) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to apply flags")); + return MNT_EX_USAGE; + } + + if (rc == -MNT_ERR_LOCK) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to update userspace mount table")); + return MNT_EX_FILEIO; + } + + if (rc == -MNT_ERR_NAMESPACE) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to switch namespace back")); + return MNT_EX_SYSERR; + } + + if (rc == -MNT_ERR_CHOWN) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to change ownership: %m")); + return MNT_EX_SYSERR; + } + + if (rc == -MNT_ERR_CHMOD) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to change mode: %m")); + return MNT_EX_SYSERR; + } + + if (rc == -MNT_ERR_IDMAP) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to attach idmapping")); + return MNT_EX_SYSERR; + } + + if (rc < 0) + return mnt_context_get_generic_excode(rc, buf, bufsz, + _("filesystem was mounted, but any subsequent operation failed: %m")); + + return MNT_EX_SOFTWARE; /* internal error */ + + } + + /* + * mount(2) and other mount related syscalls errors + */ + syserr = mnt_context_get_syscall_errno(cxt); + + + switch(syserr) { + case EPERM: + if (!buf) + break; + if (geteuid() == 0) { + + if (mnt_safe_stat(tgt, &st) == 0 + && ((mflags & MS_BIND && S_ISREG(st.st_mode)) + || S_ISDIR(st.st_mode))) + snprintf(buf, bufsz, _("permission denied")); + else + snprintf(buf, bufsz, _("mount point is not a directory")); + } else + snprintf(buf, bufsz, _("must be superuser to use mount")); + break; + + case EBUSY: + if (!buf) + break; + if (mflags & MS_REMOUNT) { + snprintf(buf, bufsz, _("mount point is busy")); + break; + } + if (src) { + struct libmnt_fs *fs = get_already_mounted_source(cxt); + + if (fs && mnt_fs_get_target(fs)) + snprintf(buf, bufsz, _("%s already mounted on %s"), + src, mnt_fs_get_target(fs)); + } + if (!*buf) + snprintf(buf, bufsz, _("%s already mounted or mount point busy"), src); + break; + case ENOENT: + if (tgt && mnt_safe_lstat(tgt, &st)) { + if (buf) + snprintf(buf, bufsz, _("mount point does not exist")); + } else if (tgt && mnt_safe_stat(tgt, &st)) { + if (buf) + snprintf(buf, bufsz, _("mount point is a symbolic link to nowhere")); + } else if (src && !mnt_is_path(src)) { + if (uflags & MNT_MS_NOFAIL) + return MNT_EX_SUCCESS; + if (buf) + snprintf(buf, bufsz, _("special device %s does not exist"), src); + } else if (buf) { + errno = syserr; + snprintf(buf, bufsz, _("mount(2) system call failed: %m")); + } + break; + + case ENOTDIR: + if (mnt_safe_stat(tgt, &st) || ! S_ISDIR(st.st_mode)) { + if (buf) + snprintf(buf, bufsz, _("mount point is not a directory")); + } else if (src && !mnt_is_path(src)) { + if (uflags & MNT_MS_NOFAIL) + return MNT_EX_SUCCESS; + if (buf) + snprintf(buf, bufsz, _("special device %s does not exist " + "(a path prefix is not a directory)"), src); + } else if (buf) { + errno = syserr; + snprintf(buf, bufsz, _("mount(2) system call failed: %m")); + } + break; + + case EINVAL: + if (!buf) + break; + if (mflags & MS_REMOUNT) + snprintf(buf, bufsz, _("mount point not mounted or bad option")); + else if (rc == -MNT_ERR_APPLYFLAGS) + snprintf(buf, bufsz, _("not mount point or bad option")); + else if ((mflags & MS_MOVE) && is_shared_tree(cxt, src)) + snprintf(buf, bufsz, + _("bad option; moving a mount " + "residing under a shared mount is unsupported")); + else if (mnt_fs_is_netfs(mnt_context_get_fs(cxt))) + snprintf(buf, bufsz, + _("bad option; for several filesystems (e.g. nfs, cifs) " + "you might need a /sbin/mount.<type> helper program")); + else + snprintf(buf, bufsz, + _("wrong fs type, bad option, bad superblock on %s, " + "missing codepage or helper program, or other error"), + src); + break; + + case EMFILE: + if (buf) + snprintf(buf, bufsz, _("mount table full")); + break; + + case EIO: + if (buf) + snprintf(buf, bufsz, _("can't read superblock on %s"), src); + break; + + case ENODEV: + if (!buf) + break; + if (mnt_context_get_fstype(cxt)) + snprintf(buf, bufsz, _("unknown filesystem type '%s'"), + mnt_context_get_fstype(cxt)); + else + snprintf(buf, bufsz, _("unknown filesystem type")); + break; + + case ENOTBLK: + if (uflags & MNT_MS_NOFAIL) + return MNT_EX_SUCCESS; + if (!buf) + break; + if (src && mnt_safe_stat(src, &st)) + snprintf(buf, bufsz, _("%s is not a block device, and stat(2) fails?"), src); + else if (src && S_ISBLK(st.st_mode)) + snprintf(buf, bufsz, + _("the kernel does not recognize %s as a block device; " + "maybe \"modprobe driver\" is necessary"), src); + else if (src && S_ISREG(st.st_mode)) + snprintf(buf, bufsz, _("%s is not a block device; try \"-o loop\""), src); + else + snprintf(buf, bufsz, _("%s is not a block device"), src); + break; + + case ENXIO: + if (uflags & MNT_MS_NOFAIL) + return MNT_EX_SUCCESS; + if (buf) + snprintf(buf, bufsz, _("%s is not a valid block device"), src); + break; + + case EACCES: + case EROFS: + if (!buf) + break; + if (mflags & MS_RDONLY) + snprintf(buf, bufsz, _("cannot mount %s read-only"), src); + else if (mnt_context_is_rwonly_mount(cxt)) + snprintf(buf, bufsz, _("%s is write-protected but explicit read-write mode requested"), src); + else if (mflags & MS_REMOUNT) + snprintf(buf, bufsz, _("cannot remount %s read-write, is write-protected"), src); + else if (mflags & MS_BIND) + snprintf(buf, bufsz, _("bind %s failed"), src); + else { + errno = syserr; + snprintf(buf, bufsz, _("mount(2) system call failed: %m")); + } + break; + + case ENOMEDIUM: + if (uflags & MNT_MS_NOFAIL) + return MNT_EX_SUCCESS; + if (buf) + snprintf(buf, bufsz, _("no medium found on %s"), src); + break; + + case EBADMSG: + /* Bad CRC for classic filesystems (e.g. extN or XFS) */ + if (buf && src && mnt_safe_stat(src, &st) == 0 + && (S_ISBLK(st.st_mode) || S_ISREG(st.st_mode))) { + snprintf(buf, bufsz, _("cannot mount; probably corrupted filesystem on %s"), src); + break; + } + /* fallthrough */ + + default: + if (buf) { + errno = syserr; + snprintf(buf, bufsz, _("mount(2) system call failed: %m")); + } + break; + } + + return MNT_EX_FAIL; +} + +#ifdef TEST_PROGRAM + +static int test_perms(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_context *cxt; + struct libmnt_optlist *ls; + int rc; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + cxt->restricted = 1; /* emulate suid mount(8) */ + mnt_context_get_fs(cxt); /* due to assert() in evaluate_permissions() */ + + if (argc < 2) { + warn("missing fstab options"); + return -EPERM; + } + if (argc == 3 && strcmp(argv[2], "--root") == 0) + cxt->restricted = 0; + + ls = mnt_context_get_optlist(cxt); + if (!ls) + return -ENOMEM; + rc = mnt_optlist_set_optstr(ls, argv[1], NULL); + if (rc) { + warn("cannot apply fstab options"); + return rc; + } + cxt->flags |= MNT_FL_TAB_APPLIED; /* emulate mnt_context_apply_fstab() */ + + mnt_context_merge_mflags(cxt); + + rc = evaluate_permissions(cxt); + if (rc) { + warn("evaluate permission failed [rc=%d]", rc); + return rc; + } + printf("user can mount\n"); + + mnt_free_context(cxt); + return 0; +} + +static int test_fixopts(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_context *cxt; + struct libmnt_optlist *ls; + unsigned long flags = 0; + const char *p; + int rc; + + cxt = mnt_new_context(); + if (!cxt) + return -ENOMEM; + + cxt->restricted = 1; /* emulate suid mount(8) */ + mnt_context_get_fs(cxt); /* to fill fs->*_optstr */ + + if (argc < 2) { + warn("missing fstab options"); + return -EPERM; + } + if (argc == 3 && strcmp(argv[2], "--root") == 0) + cxt->restricted = 0; + + ls = mnt_context_get_optlist(cxt); + if (!ls) + return -ENOMEM; + rc = mnt_optlist_set_optstr(ls, argv[1], NULL); + if (rc) { + warn("cannot apply fstab options"); + return rc; + } + cxt->flags |= MNT_FL_TAB_APPLIED; /* emulate mnt_context_apply_fstab() */ + + mnt_context_merge_mflags(cxt); + + rc = evaluate_permissions(cxt); + if (rc) { + warn("evaluate permission failed [rc=%d]", rc); + return rc; + } + rc = fix_optstr(cxt); + if (rc) { + warn("fix options failed [rc=%d]", rc); + return rc; + } + + mnt_optlist_get_optstr(ls, &p, NULL, 0); + + mnt_optlist_get_flags(ls, &flags, cxt->map_linux, 0); + printf("options (dfl): '%s' [mount flags: %08lx]\n", p, flags); + + mnt_optlist_get_optstr(ls, &p, NULL, MNT_OL_FLTR_ALL); + printf("options (ex.): '%s'\n", p); + + mnt_free_context(cxt); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--perms", test_perms, "<fstab-options> [--root]" }, + { "--fix-options", test_fixopts, "<fstab-options> [--root]" }, + + { NULL }}; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c new file mode 100644 index 0000000..26394d5 --- /dev/null +++ b/libmount/src/context_umount.c @@ -0,0 +1,1333 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2010-2018 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. + */ + +/** + * SECTION: context-umount + * @title: Umount context + * @short_description: high-level API to umount operation. + */ + +#include <sys/wait.h> +#include <sys/mount.h> + +#include "pathnames.h" +#include "loopdev.h" +#include "strutils.h" +#include "mountP.h" + +/* + * umount2 flags + */ +#ifndef MNT_FORCE +# define MNT_FORCE 0x00000001 /* Attempt to forcibly umount */ +#endif + +#ifndef MNT_DETACH +# define MNT_DETACH 0x00000002 /* Just detach from the tree */ +#endif + +#ifndef UMOUNT_NOFOLLOW +# define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */ +#endif + +#ifndef UMOUNT_UNUSED +# define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */ +#endif + +/* search in mountinfo */ +static int __mountinfo_find_umount_fs(struct libmnt_context *cxt, + const char *tgt, + struct libmnt_fs **pfs) +{ + int rc; + struct libmnt_ns *ns_old; + struct libmnt_table *mountinfo = NULL; + struct libmnt_fs *fs; + char *loopdev = NULL; + + assert(cxt); + assert(tgt); + assert(pfs); + + *pfs = NULL; + DBG(CXT, ul_debugobj(cxt, " search %s in mountinfo", tgt)); + + /* + * The mount table may be huge, and on systems with utab we have to + * merge userspace mount options into /proc/self/mountinfo. This all is + * expensive. The tab filter can be used to filter out entries, then a mount + * table and utab are very tiny files. + * + * The filter uses mnt_fs_streq_{target,srcpath} function where all + * paths should be absolute and canonicalized. This is done within + * mnt_context_get_mountinfo_for_target() where LABEL, UUID or symlinks are + * canonicalized. If --no-canonicalize is enabled than the target path + * is expected already canonical. + * + * Anyway it's better to read huge mount table than canonicalize target + * paths. It means we use the filter only if --no-canonicalize enabled. + * + * It also means that we have to read mount table from kernel. + */ + if (mnt_context_is_nocanonicalize(cxt) && *tgt == '/') + rc = mnt_context_get_mountinfo_for_target(cxt, &mountinfo, tgt); + else + rc = mnt_context_get_mountinfo(cxt, &mountinfo); + + if (rc) { + DBG(CXT, ul_debugobj(cxt, "umount: failed to read mountinfo")); + return rc; + } + + if (mnt_table_get_nents(mountinfo) == 0) { + DBG(CXT, ul_debugobj(cxt, "umount: mountinfo empty")); + return 1; + } + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + +try_loopdev: + fs = mnt_table_find_target(mountinfo, tgt, MNT_ITER_BACKWARD); + if (!fs && mnt_context_is_swapmatch(cxt)) { + /* + * Maybe the option is source rather than target (sometimes + * people use e.g. "umount /dev/sda1") + */ + fs = mnt_table_find_source(mountinfo, tgt, MNT_ITER_BACKWARD); + + if (fs) { + struct libmnt_fs *fs1 = mnt_table_find_target(mountinfo, + mnt_fs_get_target(fs), + MNT_ITER_BACKWARD); + if (!fs1) { + DBG(CXT, ul_debugobj(cxt, "mountinfo is broken?!?!")); + rc = -EINVAL; + goto err; + } + if (fs != fs1) { + /* Something was stacked over `file' on the + * same mount point. */ + DBG(CXT, ul_debugobj(cxt, + "umount: %s: %s is mounted " + "over it on the same point", + tgt, mnt_fs_get_source(fs1))); + rc = -EINVAL; + goto err; + } + } + } + + if (!fs && !loopdev && mnt_context_is_swapmatch(cxt)) { + /* + * Maybe the option is /path/file.img, try to convert to /dev/loopN + */ + struct stat st; + + if (mnt_safe_stat(tgt, &st) == 0 && S_ISREG(st.st_mode)) { + int count; + struct libmnt_cache *cache = mnt_context_get_cache(cxt); + const char *bf = cache ? mnt_resolve_path(tgt, cache) : tgt; + + count = loopdev_count_by_backing_file(bf, &loopdev); + if (count == 1) { + DBG(CXT, ul_debugobj(cxt, + "umount: %s --> %s (retry)", tgt, loopdev)); + tgt = loopdev; + goto try_loopdev; + + } else if (count > 1) + DBG(CXT, ul_debugobj(cxt, + "umount: warning: %s is associated " + "with more than one loopdev", tgt)); + } + } + + *pfs = fs; + free(loopdev); + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + DBG(CXT, ul_debugobj(cxt, "umount fs: %s", fs ? mnt_fs_get_target(fs) : + "<not found>")); + return fs ? 0 : 1; +err: + free(loopdev); + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; +} + +/** + * mnt_context_find_umount_fs: + * @cxt: mount context + * @tgt: mountpoint, device, ... + * @pfs: returns point to filesystem + * + * Returns: 0 on success, <0 on error, 1 if target filesystem not found + */ +int mnt_context_find_umount_fs(struct libmnt_context *cxt, + const char *tgt, + struct libmnt_fs **pfs) +{ + if (pfs) + *pfs = NULL; + + if (!cxt || !tgt || !pfs) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "umount: lookup FS for '%s'", tgt)); + + if (!*tgt) + return 1; /* empty string is not an error */ + + /* In future this function should be extended to support for example + * fsinfo() (or another cheap way kernel will support), for now the + * default is expensive mountinfo. + */ + return __mountinfo_find_umount_fs(cxt, tgt, pfs); +} + +/* Check if there is something important in the utab file. The parsed utab is + * stored in context->utab and deallocated by mnt_free_context(). + * + * This function exists to avoid (if possible) /proc/self/mountinfo usage, so + * don't use things like mnt_resolve_target(), mnt_context_get_mountinfo() etc here. + * See lookup_umount_fs() for more details. + */ +static int has_utab_entry(struct libmnt_context *cxt, const char *target) +{ + struct libmnt_cache *cache = NULL; + struct libmnt_fs *fs; + struct libmnt_iter itr; + char *cn = NULL; + int rc = 0; + + assert(cxt); + + if (!cxt->utab) { + const char *path = mnt_get_utab_path(); + + if (!path || is_file_empty(path)) + return 0; + cxt->utab = mnt_new_table(); + if (!cxt->utab) + return 0; + cxt->utab->fmt = MNT_FMT_UTAB; + if (mnt_table_parse_file(cxt->utab, path)) + return 0; + } + + /* paths in utab are canonicalized */ + cache = mnt_context_get_cache(cxt); + cn = mnt_resolve_path(target, cache); + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + while (mnt_table_next_fs(cxt->utab, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, cn)) { + rc = 1; + break; + } + } + + if (!cache) + free(cn); + return rc; +} + +/* returns: 1 not found; <0 on error; 1 success */ +static int lookup_umount_fs_by_statfs(struct libmnt_context *cxt, const char *tgt) +{ + struct stat st; + const char *type; + + assert(cxt); + assert(cxt->fs); + + DBG(CXT, ul_debugobj(cxt, " lookup by statfs")); + + /* + * Let's try to avoid mountinfo usage at all to minimize performance + * degradation. Don't forget that kernel has to compose *whole* + * mountinfo about all mountpoints although we look for only one entry. + * + * All we need is fstype and to check if there is no userspace mount + * options for the target (e.g. helper=udisks to call /sbin/umount.udisks). + * + * So, let's use statfs() if possible (it's bad idea for --lazy/--force + * umounts as target is probably unreachable NFS, also for --detach-loop + * as this additionally needs to know the name of the loop device). + */ + if (mnt_context_is_restricted(cxt) + || *tgt != '/' + || (cxt->flags & MNT_FL_HELPER) + || mnt_context_is_force(cxt) + || mnt_context_is_lazy(cxt) + || mnt_context_is_nocanonicalize(cxt) + || mnt_context_is_loopdel(cxt) + || mnt_safe_stat(tgt, &st) != 0 || !S_ISDIR(st.st_mode) + || has_utab_entry(cxt, tgt)) + return 1; /* not found */ + + type = mnt_fs_get_fstype(cxt->fs); + if (!type) { + struct statfs vfs; + int fd; + + DBG(CXT, ul_debugobj(cxt, " trying fstatfs()")); + + /* O_PATH avoids triggering automount points. */ + fd = open(tgt, O_PATH); + if (fd >= 0) { + if (fstatfs(fd, &vfs) == 0) + type = mnt_statfs_get_fstype(&vfs); + close(fd); + } + if (type) { + int rc = mnt_fs_set_fstype(cxt->fs, type); + if (rc) + return rc; + } + } + if (type) { + DBG(CXT, ul_debugobj(cxt, " umount: disabling mountinfo")); + mnt_context_disable_mtab(cxt, TRUE); + + DBG(CXT, ul_debugobj(cxt, + " mountinfo unnecessary [type=%s]", type)); + return 0; + } + + return 1; /* not found */ +} + +/* returns: 1 not found; <0 on error; 1 success */ +static int lookup_umount_fs_by_mountinfo(struct libmnt_context *cxt, const char *tgt) +{ + struct libmnt_fs *fs = NULL; + int rc; + + assert(cxt); + assert(cxt->fs); + + DBG(CXT, ul_debugobj(cxt, " lookup by mountinfo")); + + /* search */ + rc = __mountinfo_find_umount_fs(cxt, tgt, &fs); + if (rc != 0) + return rc; + + /* apply result */ + if (fs != cxt->fs) { + mnt_fs_set_source(cxt->fs, NULL); + mnt_fs_set_target(cxt->fs, NULL); + + if (!mnt_copy_fs(cxt->fs, fs)) { + DBG(CXT, ul_debugobj(cxt, " failed to copy FS")); + return -errno; + } + DBG(CXT, ul_debugobj(cxt, " mountinfo applied")); + } + + cxt->flags |= MNT_FL_TAB_APPLIED; + return 0; +} + +/* This function searchs for FS according to cxt->fs->target, + * apply result to cxt->fs and it's umount replacement to + * mnt_context_apply_fstab(), use mnt_context_tab_applied() + * to check result. + * + * The goal is to minimize situations when we need to parse + * /proc/self/mountinfo. + */ +static int lookup_umount_fs(struct libmnt_context *cxt) +{ + const char *tgt; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + + DBG(CXT, ul_debugobj(cxt, "umount: lookup FS")); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) { + DBG(CXT, ul_debugobj(cxt, " undefined target")); + return -EINVAL; + } + + /* try get fs type by statfs() */ + rc = lookup_umount_fs_by_statfs(cxt, tgt); + if (rc <= 0) + goto done; + + /* get complete fs from fs entry from mountinfo */ + rc = lookup_umount_fs_by_mountinfo(cxt, tgt); + if (rc <= 0) + goto done; + + DBG(CXT, ul_debugobj(cxt, " cannot find '%s'", tgt)); + return 0; /* this is correct! */ + +done: + if (rc == 0 && cxt->fs) { + struct libmnt_optlist *ol = mnt_context_get_optlist(cxt); + + if (!ol) + return -ENOMEM; + + rc = mnt_optlist_set_optstr(ol, mnt_fs_get_options(cxt->fs), NULL); + } + DBG(CXT, ul_debugobj(cxt, " lookup done [rc=%d]", rc)); + return rc; +} + +/* check if @devname is loopdev and if the device is associated + * with a source from @fstab_fs + */ +static int is_associated_fs(const char *devname, struct libmnt_fs *fs) +{ + uintmax_t offset = 0; + const char *src, *optstr; + char *val; + size_t valsz; + int flags = 0; + + /* check if it begins with /dev/loop */ + if (strncmp(devname, _PATH_DEV_LOOP, sizeof(_PATH_DEV_LOOP) - 1) != 0) + return 0; + + src = mnt_fs_get_srcpath(fs); + if (!src) + return 0; + + /* check for the offset option in @fs */ + optstr = mnt_fs_get_user_options(fs); + + if (optstr && + mnt_optstr_get_option(optstr, "offset", &val, &valsz) == 0) { + flags |= LOOPDEV_FL_OFFSET; + + if (mnt_parse_offset(val, valsz, &offset) != 0) + return 0; + } + + return loopdev_is_used(devname, src, offset, 0, flags); +} + +/* returns: <0 on error; 1 not found (not wanted) */ +static int prepare_helper_from_option(struct libmnt_context *cxt, + const char *name) +{ + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + const char *suffix; + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + opt = mnt_optlist_get_named(ol, name, cxt->map_userspace); + if (!opt || !mnt_opt_has_value(opt)) + return 1; + + suffix = mnt_opt_get_value(opt); + DBG(CXT, ul_debugobj(cxt, "umount: umount.%s %s requested", suffix, name)); + + return mnt_context_prepare_helper(cxt, "umount", suffix); +} + +static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv) +{ + struct libmnt_ns *ns_old; + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + const char *type = mnt_fs_get_fstype(cxt->fs); + const char *val = NULL;; + uid_t uid, entry_uid; + + *errsv = 0; + + if (!type) + return 0; + + if (strcmp(type, "fuse") != 0 && + strcmp(type, "fuseblk") != 0 && + strncmp(type, "fuse.", 5) != 0 && + strncmp(type, "fuseblk.", 8) != 0) + return 0; + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return 0; + + opt = mnt_optlist_get_named(ol, "user_id", NULL); + if (opt) + val = mnt_opt_get_value(opt); + if (!val || mnt_opt_get_map(opt)) + return 0; + + if (mnt_parse_uid(val, strlen(val), &entry_uid) != 0) + return 0; + + /* get current user */ + ns_old = mnt_context_switch_origin_ns(cxt); + if (!ns_old) { + *errsv = -MNT_ERR_NAMESPACE; + return 0; + } + + uid = getuid(); + + if (!mnt_context_switch_ns(cxt, ns_old)) { + *errsv = -MNT_ERR_NAMESPACE; + return 0; + } + + return uid == entry_uid; +} + +/* + * Note that cxt->fs contains relevant mountinfo entry! + */ +static int evaluate_permissions(struct libmnt_context *cxt) +{ + unsigned long fstab_flags = 0; + struct libmnt_table *fstab; + const char *tgt, *src, *optstr; + int rc = 0, ok = 0; + struct libmnt_fs *fs; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!mnt_context_is_restricted(cxt)) + return 0; /* superuser mount */ + + DBG(CXT, ul_debugobj(cxt, "umount: evaluating permissions")); + + if (!mnt_context_tab_applied(cxt)) { + DBG(CXT, ul_debugobj(cxt, + "cannot find %s in mountinfo and you are not root", + mnt_fs_get_target(cxt->fs))); + goto eperm; + } + + if (!mnt_context_is_nohelpers(cxt)) { + rc = prepare_helper_from_option(cxt, "uhelper"); + if (rc < 0) + return rc; /* error */ + if (rc == 0 && cxt->helper) + return 0; /* we'll call /sbin/umount.<uhelper> */ + } + + /* + * Check if this is a fuse mount for the current user, + * if so then unmounting is allowed + */ + if (is_fuse_usermount(cxt, &rc)) { + DBG(CXT, ul_debugobj(cxt, "fuse user mount, umount is allowed")); + return 0; + } + if (rc) + return rc; + + /* + * User mounts have to be in /etc/fstab + */ + rc = mnt_context_get_fstab(cxt, &fstab); + if (rc) + return rc; + + tgt = mnt_fs_get_target(cxt->fs); + src = mnt_fs_get_source(cxt->fs); + + if (mnt_fs_get_bindsrc(cxt->fs)) { + src = mnt_fs_get_bindsrc(cxt->fs); + DBG(CXT, ul_debugobj(cxt, + "umount: using bind source: %s", src)); + } + + /* If fstab contains the two lines + * /dev/sda1 /mnt/zip auto user,noauto 0 0 + * /dev/sda4 /mnt/zip auto user,noauto 0 0 + * then "mount /dev/sda4" followed by "umount /mnt/zip" used to fail. + * So, we must not look for the file, but for the pair (dev,file) in fstab. + */ + fs = mnt_table_find_pair(fstab, src, tgt, MNT_ITER_FORWARD); + if (!fs) { + /* + * It's possible that there is /path/file.img in fstab and + * /dev/loop0 in mountinfo -- then we have to check the relation + * between loopdev and the file. + */ + fs = mnt_table_find_target(fstab, tgt, MNT_ITER_FORWARD); + if (fs) { + struct libmnt_cache *cache = mnt_context_get_cache(cxt); + const char *sp = mnt_fs_get_srcpath(cxt->fs); /* devname from mountinfo */ + const char *dev = sp && cache ? mnt_resolve_path(sp, cache) : sp; + + if (!dev || !is_associated_fs(dev, fs)) + fs = NULL; + } + if (!fs) { + DBG(CXT, ul_debugobj(cxt, + "umount %s: mountinfo disagrees with fstab", + tgt)); + goto eperm; + } + } + + /* + * User mounting and unmounting is allowed only if fstab contains one + * of the options `user', `users' or `owner' or `group'. + * + * The option `users' allows arbitrary users to mount and unmount - + * this may be a security risk. + * + * The options `user', `owner' and `group' only allow unmounting by the + * user that mounted (visible in mountinfo). + */ + optstr = mnt_fs_get_user_options(fs); /* FSTAB mount options! */ + if (!optstr) + goto eperm; + + if (mnt_optstr_get_flags(optstr, &fstab_flags, + mnt_get_builtin_optmap(MNT_USERSPACE_MAP))) + goto eperm; + + if (fstab_flags & MNT_MS_USERS) { + DBG(CXT, ul_debugobj(cxt, + "umount: promiscuous setting ('users') in fstab")); + return 0; + } + /* + * Check user=<username> setting from utab if there is a user, owner or + * group option in /etc/fstab + */ + if (fstab_flags & (MNT_MS_USER | MNT_MS_OWNER | MNT_MS_GROUP)) { + + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + char *curr_user = NULL; + struct libmnt_ns *ns_old; + + DBG(CXT, ul_debugobj(cxt, + "umount: checking user=<username> from mountinfo")); + + ns_old = mnt_context_switch_origin_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + curr_user = mnt_get_username(getuid()); + + if (!mnt_context_switch_ns(cxt, ns_old)) { + free(curr_user); + return -MNT_ERR_NAMESPACE; + } + if (!curr_user) { + DBG(CXT, ul_debugobj(cxt, "umount %s: cannot " + "convert %d to username", tgt, getuid())); + goto eperm; + } + + /* get "user=" from utab */ + ol = mnt_context_get_optlist(cxt); + if (!ol) { + free(curr_user); + return -ENOMEM; + } + opt = mnt_optlist_get_named(ol, "user", cxt->map_userspace); + if (opt && mnt_opt_has_value(opt)) + ok = !strcmp(curr_user, mnt_opt_get_value(opt)); + + free(curr_user); + } + + if (ok) { + DBG(CXT, ul_debugobj(cxt, "umount %s is allowed", tgt)); + return 0; + } +eperm: + DBG(CXT, ul_debugobj(cxt, "umount is not allowed for you")); + return -EPERM; +} + +static int exec_helper(struct libmnt_context *cxt) +{ + char *namespace = NULL; + struct libmnt_ns *ns_tgt = mnt_context_get_target_ns(cxt); + int rc; + pid_t pid; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + assert(cxt->helper_exec_status == 1); + + if (mnt_context_is_fake(cxt)) { + DBG(CXT, ul_debugobj(cxt, "fake mode: does not execute helper")); + cxt->helper_exec_status = rc = 0; + return rc; + } + + if (ns_tgt->fd != -1 + && asprintf(&namespace, "/proc/%i/fd/%i", + getpid(), ns_tgt->fd) == -1) { + return -ENOMEM; + } + + DBG_FLUSH; + + pid = fork(); + switch (pid) { + case 0: + { + const char *args[12], *type; + int i = 0; + + if (drop_permissions() != 0) + _exit(EXIT_FAILURE); + + if (!mnt_context_switch_origin_ns(cxt)) + _exit(EXIT_FAILURE); + + type = mnt_fs_get_fstype(cxt->fs); + + args[i++] = cxt->helper; /* 1 */ + args[i++] = mnt_fs_get_target(cxt->fs); /* 2 */ + + if (mnt_context_is_nomtab(cxt)) + args[i++] = "-n"; /* 3 */ + if (mnt_context_is_lazy(cxt)) + args[i++] = "-l"; /* 4 */ + if (mnt_context_is_force(cxt)) + args[i++] = "-f"; /* 5 */ + if (mnt_context_is_verbose(cxt)) + args[i++] = "-v"; /* 6 */ + if (mnt_context_is_rdonly_umount(cxt)) + args[i++] = "-r"; /* 7 */ + if (type + && strchr(type, '.') + && !endswith(cxt->helper, type)) { + args[i++] = "-t"; /* 8 */ + args[i++] = type; /* 9 */ + } + if (namespace) { + args[i++] = "-N"; /* 10 */ + args[i++] = namespace; /* 11 */ + } + + args[i] = NULL; /* 12 */ + for (i = 0; args[i]; i++) + DBG(CXT, ul_debugobj(cxt, "argv[%d] = \"%s\"", + i, args[i])); + DBG_FLUSH; + execv(cxt->helper, (char * const *) args); + _exit(EXIT_FAILURE); + } + default: + { + int st; + + if (waitpid(pid, &st, 0) == (pid_t) -1) { + cxt->helper_status = -1; + rc = -errno; + } else { + cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1; + cxt->helper_exec_status = rc = 0; + } + DBG(CXT, ul_debugobj(cxt, "%s executed [status=%d, rc=%d%s]", + cxt->helper, + cxt->helper_status, rc, + rc ? " waitpid failed" : "")); + break; + } + + case -1: + cxt->helper_exec_status = rc = -errno; + DBG(CXT, ul_debugobj(cxt, "fork() failed")); + break; + } + + free(namespace); + return rc; +} + +/* + * mnt_context_helper_setopt() backend. + * + * This function applies umount.type command line option (for example parsed + * by getopt() or getopt_long()) to @cxt. All unknown options are ignored and + * then 1 is returned. + * + * Returns: negative number on error, 1 if @c is unknown option, 0 on success. + */ +int mnt_context_umount_setopt(struct libmnt_context *cxt, int c, char *arg) +{ + int rc = -EINVAL; + + assert(cxt); + assert(cxt->action == MNT_ACT_UMOUNT); + + switch(c) { + case 'n': + rc = mnt_context_disable_mtab(cxt, TRUE); + break; + case 'l': + rc = mnt_context_enable_lazy(cxt, TRUE); + break; + case 'f': + rc = mnt_context_enable_force(cxt, TRUE); + break; + case 'v': + rc = mnt_context_enable_verbose(cxt, TRUE); + break; + case 'r': + rc = mnt_context_enable_rdonly_umount(cxt, TRUE); + break; + case 't': + if (arg) + rc = mnt_context_set_fstype(cxt, arg); + break; + case 'N': + if (arg) + rc = mnt_context_set_target_ns(cxt, arg); + break; + default: + return 1; + } + + return rc; +} + +/* Check whether the kernel supports the UMOUNT_NOFOLLOW flag */ +static int umount_nofollow_support(void) +{ + int res = umount2("", UMOUNT_UNUSED); + if (res != -1 || errno != EINVAL) + return 0; + + res = umount2("", UMOUNT_NOFOLLOW); + if (res != -1 || errno != ENOENT) + return 0; + + return 1; +} + +static int do_umount(struct libmnt_context *cxt) +{ + int rc = 0, flags = 0; + const char *src, *target; + char *tgtbuf = NULL; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + assert(cxt->syscall_status == 1); + + if (cxt->helper) + return exec_helper(cxt); + + src = mnt_fs_get_srcpath(cxt->fs); + target = mnt_fs_get_target(cxt->fs); + + if (!target) + return -EINVAL; + + DBG(CXT, ul_debugobj(cxt, "do umount")); + + if (mnt_context_is_restricted(cxt) && !mnt_context_is_fake(cxt)) { + /* + * extra paranoia for non-root users + * -- chdir to the parent of the mountpoint and use NOFOLLOW + * flag to avoid races and symlink attacks. + */ + if (umount_nofollow_support()) + flags |= UMOUNT_NOFOLLOW; + + rc = mnt_chdir_to_parent(target, &tgtbuf); + if (rc) + return rc; + target = tgtbuf; + } + + if (mnt_context_is_lazy(cxt)) + flags |= MNT_DETACH; + + if (mnt_context_is_force(cxt)) + flags |= MNT_FORCE; + + DBG(CXT, ul_debugobj(cxt, "umount(2) [target='%s', flags=0x%08x]%s", + target, flags, + mnt_context_is_fake(cxt) ? " (FAKE)" : "")); + + if (mnt_context_is_fake(cxt)) + rc = 0; + else { + rc = flags ? umount2(target, flags) : umount(target); + if (rc < 0) + cxt->syscall_status = -errno; + free(tgtbuf); + } + + /* + * try remount read-only + */ + if (rc < 0 + && cxt->syscall_status == -EBUSY + && mnt_context_is_rdonly_umount(cxt) + && src) { + struct libmnt_optlist *ol = mnt_context_get_optlist(cxt); + + /* keep info about remount in mount flags */ + assert(ol); + mnt_optlist_append_flags(ol, MS_REMOUNT | MS_RDONLY, cxt->map_linux); + + mnt_context_enable_loopdel(cxt, FALSE); + + DBG(CXT, ul_debugobj(cxt, + "umount(2) failed [errno=%d] -- trying to remount read-only", + -cxt->syscall_status)); + + rc = mount(src, mnt_fs_get_target(cxt->fs), NULL, + MS_REMOUNT | MS_RDONLY, NULL); + if (rc < 0) { + cxt->syscall_status = -errno; + DBG(CXT, ul_debugobj(cxt, + "read-only re-mount(2) failed [errno=%d]", + -cxt->syscall_status)); + + return -cxt->syscall_status; + } + cxt->syscall_status = 0; + DBG(CXT, ul_debugobj(cxt, "read-only re-mount(2) success")); + return 0; + } + + if (rc < 0) { + DBG(CXT, ul_debugobj(cxt, "umount(2) failed [errno=%d]", + -cxt->syscall_status)); + return -cxt->syscall_status; + } + + cxt->syscall_status = 0; + DBG(CXT, ul_debugobj(cxt, "umount(2) success")); + return 0; +} + +/** + * mnt_context_prepare_umount: + * @cxt: mount context + * + * Prepare context for umounting, unnecessary for mnt_context_umount(). + * + * Returns: 0 on success, and negative number in case of error. + */ +int mnt_context_prepare_umount(struct libmnt_context *cxt) +{ + int rc; + unsigned long flags = 0; + struct libmnt_ns *ns_old; + + if (!cxt || !cxt->fs || mnt_fs_is_swaparea(cxt->fs)) + return -EINVAL; + if (!mnt_context_get_source(cxt) && !mnt_context_get_target(cxt)) + return -EINVAL; + if (cxt->flags & MNT_FL_PREPARED) + return 0; + + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + free(cxt->helper); /* be paranoid */ + cxt->helper = NULL; + cxt->action = MNT_ACT_UMOUNT; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + rc = lookup_umount_fs(cxt); + if (!rc) + rc = mnt_context_merge_mflags(cxt); + if (!rc) + rc = evaluate_permissions(cxt); + + if (!rc && !mnt_context_is_nohelpers(cxt) && !cxt->helper) { + /* on helper= mount option based helper */ + rc = prepare_helper_from_option(cxt, "helper"); + if (rc < 0) + return rc; + if (!cxt->helper) + /* on fstype based helper */ + rc = mnt_context_prepare_helper(cxt, "umount", NULL); + } + + if (!rc) + rc = mnt_context_get_user_mflags(cxt, &flags); + + if (!rc && (flags & MNT_MS_LOOP)) + /* loop option explicitly specified in utab, detach this loop */ + mnt_context_enable_loopdel(cxt, TRUE); + + if (!rc && mnt_context_is_loopdel(cxt) && cxt->fs) { + const char *src = mnt_fs_get_srcpath(cxt->fs); + + if (src && (!is_loopdev(src) || loopdev_is_autoclear(src))) + mnt_context_enable_loopdel(cxt, FALSE); + } + + if (rc) { + DBG(CXT, ul_debugobj(cxt, "umount: preparing failed")); + return rc; + } + cxt->flags |= MNT_FL_PREPARED; + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return rc; +} + +/** + * mnt_context_do_umount: + * @cxt: mount context + * + * Umount filesystem by umount(2) or fork()+exec(/sbin/umount.type). + * Unnecessary for mnt_context_umount(). + * + * See also mnt_context_disable_helpers(). + * + * WARNING: non-zero return code does not mean that umount(2) syscall or + * umount.type helper wasn't successfully called. + * + * Check mnt_context_get_status() after error! +* + * Returns: 0 on success; + * >0 in case of umount(2) error (returns syscall errno), + * <0 in case of other errors. + */ +int mnt_context_do_umount(struct libmnt_context *cxt) +{ + int rc; + struct libmnt_ns *ns_old; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + assert((cxt->flags & MNT_FL_PREPARED)); + assert((cxt->action == MNT_ACT_UMOUNT)); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + rc = do_umount(cxt); + if (rc) + goto end; + + if (mnt_context_get_status(cxt) && !mnt_context_is_fake(cxt)) { + /* + * Umounted, do some post-umount operations + * - remove loopdev + * - refresh in-memory utab stuff if remount rather than + * umount has been performed + */ + if (mnt_context_is_loopdel(cxt) + && !mnt_optlist_is_remount(cxt->optlist)) + rc = mnt_context_delete_loopdev(cxt); + } +end: + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return rc; +} + +/** + * mnt_context_finalize_umount: + * @cxt: context + * + * Mtab update, etc. Unnecessary for mnt_context_umount(), but should be called + * after mnt_context_do_umount(). See also mnt_context_set_syscall_status(). + * + * Returns: negative number on error, 0 on success. + */ +int mnt_context_finalize_umount(struct libmnt_context *cxt) +{ + int rc; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_PREPARED)); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + rc = mnt_context_prepare_update(cxt); + if (!rc) + rc = mnt_context_update_tabs(cxt); + return rc; +} + + +/** + * mnt_context_umount: + * @cxt: umount context + * + * High-level, umounts filesystem by umount(2) or fork()+exec(/sbin/umount.type). + * + * This is similar to: + * + * mnt_context_prepare_umount(cxt); + * mnt_context_do_umount(cxt); + * mnt_context_finalize_umount(cxt); + * + * See also mnt_context_disable_helpers(). + * + * WARNING: non-zero return code does not mean that umount(2) syscall or + * umount.type helper wasn't successfully called. + * + * Check mnt_context_get_status() after error! + * + * Returns: 0 on success; + * >0 in case of umount(2) error (returns syscall errno), + * <0 in case of other errors. + */ +int mnt_context_umount(struct libmnt_context *cxt) +{ + int rc; + struct libmnt_ns *ns_old; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + DBG(CXT, ul_debugobj(cxt, "umount: %s", mnt_context_get_target(cxt))); + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + rc = mnt_context_prepare_umount(cxt); + if (!rc) + rc = mnt_context_prepare_update(cxt); + if (!rc) + rc = mnt_context_do_umount(cxt); + if (!rc) + rc = mnt_context_update_tabs(cxt); + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return rc; +} + + +/** + * mnt_context_next_umount: + * @cxt: context + * @itr: iterator + * @fs: returns the current filesystem + * @mntrc: returns the return code from mnt_context_umount() + * @ignored: returns 1 for not matching + * + * This function tries to umount the next filesystem from mountinfo file. + * + * You can filter out filesystems by: + * mnt_context_set_options_pattern() to simulate umount -a -O pattern + * mnt_context_set_fstype_pattern() to simulate umount -a -t pattern + * + * If the filesystem is not mounted or does not match the defined criteria, + * then the function mnt_context_next_umount() returns zero, but the @ignored is + * non-zero. Note that the root filesystem is always ignored. + * + * If umount(2) syscall or umount.type helper failed, then the + * mnt_context_next_umount() function returns zero, but the @mntrc is non-zero. + * Use also mnt_context_get_status() to check if the filesystem was + * successfully umounted. + * + * Returns: 0 on success, + * <0 in case of error (!= umount(2) errors) + * 1 at the end of the list. + */ +int mnt_context_next_umount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, + int *ignored) +{ + struct libmnt_table *mountinfo; + const char *tgt; + int rc; + + if (ignored) + *ignored = 0; + if (mntrc) + *mntrc = 0; + + if (!cxt || !fs || !itr) + return -EINVAL; + + rc = mnt_context_get_mountinfo(cxt, &mountinfo); + cxt->mountinfo = NULL; /* do not reset mountinfo */ + mnt_reset_context(cxt); + + if (rc) + return rc; + + cxt->mountinfo = mountinfo; + + do { + rc = mnt_table_next_fs(mountinfo, itr, fs); + if (rc != 0) + return rc; /* no more filesystems (or error) */ + + tgt = mnt_fs_get_target(*fs); + } while (!tgt); + + DBG(CXT, ul_debugobj(cxt, "next-umount: trying %s [fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]", tgt, + mnt_fs_get_fstype(*fs), cxt->fstype_pattern, mnt_fs_get_options(*fs), cxt->optstr_pattern)); + + /* ignore filesystems which don't match options patterns */ + if ((cxt->fstype_pattern && !mnt_fs_match_fstype(*fs, + cxt->fstype_pattern)) || + + /* ignore filesystems which don't match type patterns */ + (cxt->optstr_pattern && !mnt_fs_match_options(*fs, + cxt->optstr_pattern))) { + if (ignored) + *ignored = 1; + + DBG(CXT, ul_debugobj(cxt, "next-umount: not-match")); + return 0; + } + + rc = mnt_context_set_fs(cxt, *fs); + if (rc) + return rc; + rc = mnt_context_umount(cxt); + if (mntrc) + *mntrc = rc; + return 0; +} + + +int mnt_context_get_umount_excode( + struct libmnt_context *cxt, + int rc, + char *buf, + size_t bufsz) +{ + if (mnt_context_helper_executed(cxt)) + /* + * /sbin/umount.<type> called, return status + */ + return mnt_context_get_helper_status(cxt); + + if (rc == 0 && mnt_context_get_status(cxt) == 1) + /* + * Libmount success && syscall success. + */ + return MNT_EX_SUCCESS; + + if (!mnt_context_syscall_called(cxt)) { + /* + * libmount errors (extra library checks) + */ + if (rc == -EPERM && !mnt_context_tab_applied(cxt)) { + /* failed to evaluate permissions because not found + * relevant entry in mountinfo */ + if (buf) + snprintf(buf, bufsz, _("not mounted")); + return MNT_EX_USAGE; + } + + if (rc == -MNT_ERR_LOCK) { + if (buf) + snprintf(buf, bufsz, _("locking failed")); + return MNT_EX_FILEIO; + } + + if (rc == -MNT_ERR_NAMESPACE) { + if (buf) + snprintf(buf, bufsz, _("failed to switch namespace")); + return MNT_EX_SYSERR; + } + return mnt_context_get_generic_excode(rc, buf, bufsz, + _("umount failed: %m")); + + } if (mnt_context_get_syscall_errno(cxt) == 0) { + /* + * umount(2) syscall success, but something else failed + * (probably error in utab processing). + */ + if (rc == -MNT_ERR_LOCK) { + if (buf) + snprintf(buf, bufsz, _("filesystem was unmounted, but failed to update userspace mount table")); + return MNT_EX_FILEIO; + } + + if (rc == -MNT_ERR_NAMESPACE) { + if (buf) + snprintf(buf, bufsz, _("filesystem was unmounted, but failed to switch namespace back")); + return MNT_EX_SYSERR; + + } + + if (rc < 0) + return mnt_context_get_generic_excode(rc, buf, bufsz, + _("filesystem was unmounted, but any subsequent operation failed: %m")); + + return MNT_EX_SOFTWARE; /* internal error */ + } + + /* + * umount(2) errors + */ + if (buf) { + int syserr = mnt_context_get_syscall_errno(cxt); + + switch (syserr) { + case ENXIO: + snprintf(buf, bufsz, _("invalid block device")); /* ??? */ + break; + case EINVAL: + snprintf(buf, bufsz, _("not mounted")); + break; + case EIO: + snprintf(buf, bufsz, _("can't write superblock")); + break; + case EBUSY: + snprintf(buf, bufsz, _("target is busy")); + break; + case ENOENT: + snprintf(buf, bufsz, _("no mount point specified")); + break; + case EPERM: + snprintf(buf, bufsz, _("must be superuser to unmount")); + break; + case EACCES: + snprintf(buf, bufsz, _("block devices are not permitted on filesystem")); + break; + default: + return mnt_context_get_generic_excode(syserr, buf, bufsz,_("umount(2) system call failed: %m")); + } + } + return MNT_EX_FAIL; +} diff --git a/libmount/src/fs.c b/libmount/src/fs.c new file mode 100644 index 0000000..79e32a1 --- /dev/null +++ b/libmount/src/fs.c @@ -0,0 +1,1799 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 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. + */ + +/** + * SECTION: fs + * @title: Filesystem + * @short_description: represents one entry from fstab, or mountinfo file + * + */ +#include <ctype.h> +#include <blkid.h> +#include <stddef.h> + +#include "mountP.h" +#include "strutils.h" + +/** + * mnt_new_fs: + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the filesystem. + * + * Returns: newly allocated struct libmnt_fs. + */ +struct libmnt_fs *mnt_new_fs(void) +{ + struct libmnt_fs *fs = calloc(1, sizeof(*fs)); + + if (!fs) + return NULL; + + fs->refcount = 1; + INIT_LIST_HEAD(&fs->ents); + DBG(FS, ul_debugobj(fs, "alloc")); + return fs; +} + +/** + * mnt_free_fs: + * @fs: fs pointer + * + * Deallocates the fs. This function does not care about reference count. Don't + * use this function directly -- it's better to use mnt_unref_fs(). + * + * The reference counting is supported since util-linux v2.24. + */ +void mnt_free_fs(struct libmnt_fs *fs) +{ + if (!fs) + return; + + DBG(FS, ul_debugobj(fs, "free [refcount=%d]", fs->refcount)); + + mnt_reset_fs(fs); + free(fs); +} + +/** + * mnt_reset_fs: + * @fs: fs pointer + * + * Resets (zeroize) @fs. + */ +void mnt_reset_fs(struct libmnt_fs *fs) +{ + int ref; + + if (!fs) + return; + + ref = fs->refcount; + + list_del(&fs->ents); + free(fs->source); + free(fs->bindsrc); + free(fs->tagname); + free(fs->tagval); + free(fs->root); + free(fs->swaptype); + free(fs->target); + free(fs->fstype); + free(fs->optstr); + free(fs->vfs_optstr); + free(fs->fs_optstr); + free(fs->user_optstr); + free(fs->attrs); + free(fs->opt_fields); + free(fs->comment); + + mnt_unref_optlist(fs->optlist); + fs->optlist = NULL; + + fs->opts_age = 0; + + memset(fs, 0, sizeof(*fs)); + INIT_LIST_HEAD(&fs->ents); + fs->refcount = ref; +} + +/** + * mnt_ref_fs: + * @fs: fs pointer + * + * Increments reference counter. + */ +void mnt_ref_fs(struct libmnt_fs *fs) +{ + if (fs) { + fs->refcount++; + /*DBG(FS, ul_debugobj(fs, "ref=%d", fs->refcount));*/ + } +} + +/** + * mnt_unref_fs: + * @fs: fs pointer + * + * De-increments reference counter, on zero the @fs is automatically + * deallocated by mnt_free_fs(). + */ +void mnt_unref_fs(struct libmnt_fs *fs) +{ + if (fs) { + fs->refcount--; + /*DBG(FS, ul_debugobj(fs, "unref=%d", fs->refcount));*/ + if (fs->refcount <= 0) + mnt_free_fs(fs); + } +} + +static inline int update_str(char **dest, const char *src) +{ + size_t sz; + char *x; + + assert(dest); + + if (!src) { + free(*dest); + *dest = NULL; + return 0; /* source (old) is empty */ + } + + sz = strlen(src) + 1; + x = realloc(*dest, sz); + if (!x) + return -ENOMEM; + *dest = x; + memcpy(*dest, src, sz); + return 0; +} + +/* This function does NOT overwrite (replace) the string in @new, the string in + * @new has to be NULL otherwise this is no-op. */ +static inline int cpy_str_at_offset(void *new, const void *old, size_t offset) +{ + char **o = (char **) ((char *) old + offset); + char **n = (char **) ((char *) new + offset); + + if (*n) + return 0; /* already set, don't overwrite */ + + return update_str(n, *o); +} + +static inline int sync_opts_from_optlist(struct libmnt_fs *fs, struct libmnt_optlist *ol) +{ + unsigned int age; + + assert(fs); + assert(ol); + + age = mnt_optlist_get_age(ol); + if (age != fs->opts_age) { + const char *p; + int rc; + + /* All options */ + rc = mnt_optlist_get_optstr(ol, &p, NULL, 0); + if (!rc) + rc = strdup_to_struct_member(fs, optstr, p); + + /* FS options */ + if (!rc) + rc = mnt_optlist_get_optstr(ol, &p, NULL, MNT_OL_FLTR_UNKNOWN); + if (!rc) + rc = strdup_to_struct_member(fs, fs_optstr, p); + + /* VFS options */ + if (!rc) + rc = mnt_optlist_get_optstr(ol, &p, mnt_get_builtin_optmap(MNT_LINUX_MAP), 0); + if (!rc) + rc = strdup_to_struct_member(fs, vfs_optstr, p); + + /* Userspace options */ + if (!rc) + rc = mnt_optlist_get_optstr(ol, &p, mnt_get_builtin_optmap(MNT_USERSPACE_MAP), 0); + if (!rc) + rc = strdup_to_struct_member(fs, user_optstr, p); + + if (rc) { + DBG(FS, ul_debugobj(fs, "sync failed [rc=%d]", rc)); + return rc; + } else { + DBG(FS, ul_debugobj(fs, "synced: " + "vfs: '%s' fs: '%s' user: '%s', optstr: '%s'", + fs->vfs_optstr, fs->fs_optstr, fs->user_optstr, fs->optstr)); + fs->opts_age = age; + } + } + return 0; +} + +/* If @optlist is not NULL then @fs will read all option strings from @optlist. + * It means that mnt_fs_get_*_options() won't be read-only operations. */ +int mnt_fs_follow_optlist(struct libmnt_fs *fs, struct libmnt_optlist *ol) +{ + assert(fs); + + if (fs->optlist == ol) + return 0; + if (fs->optlist) + mnt_unref_optlist(fs->optlist); + + fs->opts_age = 0; + fs->optlist = ol; + + if (ol) + mnt_ref_optlist(ol); + return 0; +} + +/** + * mnt_copy_fs: + * @dest: destination FS + * @src: source FS + * + * If @dest is NULL, then a new FS is allocated, if any @dest field is already + * set, then the field is NOT overwritten. + * + * This function does not copy userdata (se mnt_fs_set_userdata()). A new copy is + * not linked with any existing mnt_tab or optlist. + * + * Returns: @dest or NULL in case of error + */ +struct libmnt_fs *mnt_copy_fs(struct libmnt_fs *dest, + const struct libmnt_fs *src) +{ + const struct libmnt_fs *org = dest; + + if (!src) + return NULL; + if (!dest) { + dest = mnt_new_fs(); + if (!dest) + return NULL; + + dest->tab = NULL; + } + + dest->id = src->id; + dest->parent = src->parent; + dest->devno = src->devno; + dest->tid = src->tid; + + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, source))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, tagname))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, tagval))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, root))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, swaptype))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, target))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, fstype))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, optstr))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, vfs_optstr))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, fs_optstr))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, user_optstr))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, attrs))) + goto err; + if (cpy_str_at_offset(dest, src, offsetof(struct libmnt_fs, bindsrc))) + goto err; + + dest->freq = src->freq; + dest->passno = src->passno; + dest->flags = src->flags; + dest->size = src->size; + dest->usedsize = src->usedsize; + dest->priority = src->priority; + + return dest; +err: + if (!org) + mnt_free_fs(dest); + return NULL; +} + +/* + * This function copies all @fs description except information that does not + * belong to /etc/mtab (e.g. VFS and userspace mount options with MNT_NOMTAB + * mask). + * + * Returns: copy of @fs. + */ +struct libmnt_fs *mnt_copy_mtab_fs(struct libmnt_fs *fs) +{ + struct libmnt_fs *n = mnt_new_fs(); + + assert(fs); + if (!n) + return NULL; + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + if (strdup_between_structs(n, fs, source)) + goto err; + if (strdup_between_structs(n, fs, target)) + goto err; + if (strdup_between_structs(n, fs, fstype)) + goto err; + + if (fs->vfs_optstr) { + char *p = NULL; + mnt_optstr_get_options(fs->vfs_optstr, &p, + mnt_get_builtin_optmap(MNT_LINUX_MAP), + MNT_NOMTAB); + n->vfs_optstr = p; + } + + if (fs->user_optstr) { + char *p = NULL; + mnt_optstr_get_options(fs->user_optstr, &p, + mnt_get_builtin_optmap(MNT_USERSPACE_MAP), + MNT_NOMTAB); + n->user_optstr = p; + } + + if (strdup_between_structs(n, fs, fs_optstr)) + goto err; + + /* we cannot copy original optstr, the new optstr has to be without + * non-mtab options -- so, let's generate a new string */ + n->optstr = mnt_fs_strdup_options(n); + + n->freq = fs->freq; + n->passno = fs->passno; + n->flags = fs->flags; + + return n; +err: + mnt_free_fs(n); + return NULL; + +} + +/** + * mnt_fs_get_userdata: + * @fs: struct libmnt_file instance + * + * Returns: private data set by mnt_fs_set_userdata() or NULL. + */ +void *mnt_fs_get_userdata(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + return fs->userdata; +} + +/** + * mnt_fs_set_userdata: + * @fs: struct libmnt_file instance + * @data: user data + * + * The "userdata" are library independent data. + * + * Returns: 0 or negative number in case of error (if @fs is NULL). + */ +int mnt_fs_set_userdata(struct libmnt_fs *fs, void *data) +{ + if (!fs) + return -EINVAL; + fs->userdata = data; + return 0; +} + +/** + * mnt_fs_get_srcpath: + * @fs: struct libmnt_file (fstab/mtab/mountinfo) fs + * + * The mount "source path" is: + * - a directory for 'bind' mounts (in fstab or mtab only) + * - a device name for standard mounts + * + * See also mnt_fs_get_tag() and mnt_fs_get_source(). + * + * Returns: mount source path or NULL in case of error or when the path + * is not defined. + */ +const char *mnt_fs_get_srcpath(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + + /* fstab-like fs */ + if (fs->tagname) + return NULL; /* the source contains a "NAME=value" */ + return fs->source; +} + +/** + * mnt_fs_get_source: + * @fs: struct libmnt_file (fstab/mtab/mountinfo) fs + * + * Returns: mount source. Note that the source could be unparsed TAG + * (LABEL/UUID). See also mnt_fs_get_srcpath() and mnt_fs_get_tag(). + */ +const char *mnt_fs_get_source(struct libmnt_fs *fs) +{ + return fs ? fs->source : NULL; +} + +/* + * Used by the parser ONLY (@source has to be freed on error) + */ +int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source) +{ + char *t = NULL, *v = NULL; + + assert(fs); + + if (source && blkid_parse_tag_string(source, &t, &v) == 0 && + !mnt_valid_tagname(t)) { + /* parsable but unknown tag -- ignore */ + free(t); + free(v); + t = v = NULL; + } + + if (fs->source != source) + free(fs->source); + + free(fs->tagname); + free(fs->tagval); + + fs->source = source; + fs->tagname = t; + fs->tagval = v; + return 0; +} + +/** + * mnt_fs_set_source: + * @fs: fstab/mtab/mountinfo entry + * @source: new source + * + * This function creates a private copy (strdup()) of @source. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_source(struct libmnt_fs *fs, const char *source) +{ + char *p = NULL; + int rc; + + if (!fs) + return -EINVAL; + + if (source) { + p = strdup(source); + if (!p) + return -ENOMEM; + } + + rc = __mnt_fs_set_source_ptr(fs, p); + if (rc) + free(p); + return rc; +} + +/** + * mnt_fs_streq_srcpath: + * @fs: fs + * @path: source path + * + * Compares @fs source path with @path. The redundant slashes are ignored. + * This function compares strings and does not canonicalize the paths. + * See also more heavy and generic mnt_fs_match_source(). + * + * Returns: 1 if @fs source path equal to @path, otherwise 0. + */ +int mnt_fs_streq_srcpath(struct libmnt_fs *fs, const char *path) +{ + const char *p; + + if (!fs) + return 0; + + p = mnt_fs_get_srcpath(fs); + + if (!mnt_fs_is_pseudofs(fs)) + return streq_paths(p, path); + + if (!p && !path) + return 1; + + return p && path && strcmp(p, path) == 0; +} + +/** + * mnt_fs_get_table: + * @fs: table entry + * @tb: table that contains @fs + * + * Returns: 0 or negative number on error (if @fs or @tb is NULL). + * + * Since: 2.34 + */ +int mnt_fs_get_table(struct libmnt_fs *fs, struct libmnt_table **tb) +{ + if (!fs || !tb) + return -EINVAL; + + *tb = fs->tab; + return 0; +} + +/** + * mnt_fs_streq_target: + * @fs: fs + * @path: mount point + * + * Compares @fs target path with @path. The redundant slashes are ignored. + * This function compares strings and does not canonicalize the paths. + * See also more generic mnt_fs_match_target(). + * + * Returns: 1 if @fs target path equal to @path, otherwise 0. + */ +int mnt_fs_streq_target(struct libmnt_fs *fs, const char *path) +{ + return fs && streq_paths(mnt_fs_get_target(fs), path); +} + +/** + * mnt_fs_get_tag: + * @fs: fs + * @name: returns pointer to NAME string + * @value: returns pointer to VALUE string + * + * "TAG" is NAME=VALUE (e.g. LABEL=foo) + * + * The TAG is the first column in the fstab file. The TAG or "srcpath" always has + * to be set for all entries. + * + * See also mnt_fs_get_source(). + * + * <informalexample> + * <programlisting> + * char *src; + * struct libmnt_fs *fs = mnt_table_find_target(tb, "/home", MNT_ITER_FORWARD); + * + * if (!fs) + * goto err; + * + * src = mnt_fs_get_srcpath(fs); + * if (!src) { + * char *tag, *val; + * if (mnt_fs_get_tag(fs, &tag, &val) == 0) + * printf("%s: %s\n", tag, val); // LABEL or UUID + * } else + * printf("device: %s\n", src); // device or bind path + * </programlisting> + * </informalexample> + * + * Returns: 0 on success or negative number in case a TAG is not defined. + */ +int mnt_fs_get_tag(struct libmnt_fs *fs, const char **name, const char **value) +{ + if (fs == NULL || !fs->tagname) + return -EINVAL; + if (name) + *name = fs->tagname; + if (value) + *value = fs->tagval; + return 0; +} + +/** + * mnt_fs_get_target: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Returns: pointer to mountpoint path or NULL + */ +const char *mnt_fs_get_target(struct libmnt_fs *fs) +{ + return fs ? fs->target : NULL; +} + +/** + * mnt_fs_set_target: + * @fs: fstab/mtab/mountinfo entry + * @tgt: mountpoint + * + * This function creates a private copy (strdup()) of @tgt. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_target(struct libmnt_fs *fs, const char *tgt) +{ + return strdup_to_struct_member(fs, target, tgt); +} + +int __mnt_fs_set_target_ptr(struct libmnt_fs *fs, char *tgt) +{ + assert(fs); + + free(fs->target); + fs->target = tgt; + return 0; +} + +static int mnt_fs_get_flags(struct libmnt_fs *fs) +{ + return fs ? fs->flags : 0; +} + +/** + * mnt_fs_get_propagation: + * @fs: mountinfo entry + * @flags: returns propagation MS_* flags as present in the mountinfo file + * + * Note that this function sets @flags to zero if no propagation flags are found + * in the mountinfo file. The kernel default is MS_PRIVATE, this flag is not stored + * in the mountinfo file. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_get_propagation(struct libmnt_fs *fs, unsigned long *flags) +{ + if (!fs || !flags) + return -EINVAL; + + *flags = 0; + + if (!fs->opt_fields) + return 0; + + /* + * The optional fields format is incompatible with mount options + * ... we have to parse the field here. + */ + *flags |= strstr(fs->opt_fields, "shared:") ? MS_SHARED : MS_PRIVATE; + + if (strstr(fs->opt_fields, "master:")) + *flags |= MS_SLAVE; + if (strstr(fs->opt_fields, "unbindable")) + *flags |= MS_UNBINDABLE; + + return 0; +} + +/** + * mnt_fs_is_kernel: + * @fs: filesystem + * + * Returns: 1 if the filesystem description is read from kernel e.g. /proc/mounts. + */ +int mnt_fs_is_kernel(struct libmnt_fs *fs) +{ + return mnt_fs_get_flags(fs) & MNT_FS_KERNEL ? 1 : 0; +} + +/** + * mnt_fs_is_swaparea: + * @fs: filesystem + * + * Returns: 1 if the filesystem uses "swap" as a type + */ +int mnt_fs_is_swaparea(struct libmnt_fs *fs) +{ + return mnt_fs_get_flags(fs) & MNT_FS_SWAP ? 1 : 0; +} + +/** + * mnt_fs_is_pseudofs: + * @fs: filesystem + * + * Returns: 1 if the filesystem is a pseudo fs type (proc, cgroups) + */ +int mnt_fs_is_pseudofs(struct libmnt_fs *fs) +{ + return mnt_fs_get_flags(fs) & MNT_FS_PSEUDO ? 1 : 0; +} + +/** + * mnt_fs_is_netfs: + * @fs: filesystem + * + * Returns: 1 if the filesystem is a network filesystem + */ +int mnt_fs_is_netfs(struct libmnt_fs *fs) +{ + return mnt_fs_get_flags(fs) & MNT_FS_NET ? 1 : 0; +} + +/** + * mnt_fs_is_regularfs: + * @fs: filesystem + * + * Returns: 1 if the filesystem is a regular filesystem (not network or pseudo filesystem). + * + * Since: 2.38 + */ +int mnt_fs_is_regularfs(struct libmnt_fs *fs) +{ + return !(mnt_fs_is_pseudofs(fs) + || mnt_fs_is_netfs(fs) + || mnt_fs_is_swaparea(fs)); +} + +/** + * mnt_fs_get_fstype: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Returns: pointer to filesystem type. + */ +const char *mnt_fs_get_fstype(struct libmnt_fs *fs) +{ + return fs ? fs->fstype : NULL; +} + +/* Used by the struct libmnt_file parser only */ +int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype) +{ + assert(fs); + + if (fstype != fs->fstype) + free(fs->fstype); + + fs->fstype = fstype; + fs->flags &= ~MNT_FS_PSEUDO; + fs->flags &= ~MNT_FS_NET; + fs->flags &= ~MNT_FS_SWAP; + + /* save info about pseudo filesystems */ + if (fs->fstype) { + if (mnt_fstype_is_pseudofs(fs->fstype)) + fs->flags |= MNT_FS_PSEUDO; + else if (mnt_fstype_is_netfs(fs->fstype)) + fs->flags |= MNT_FS_NET; + else if (!strcmp(fs->fstype, "swap")) + fs->flags |= MNT_FS_SWAP; + } + return 0; +} + +/** + * mnt_fs_set_fstype: + * @fs: fstab/mtab/mountinfo entry + * @fstype: filesystem type + * + * This function creates a private copy (strdup()) of @fstype. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_fstype(struct libmnt_fs *fs, const char *fstype) +{ + char *p = NULL; + + if (!fs) + return -EINVAL; + if (fstype) { + p = strdup(fstype); + if (!p) + return -ENOMEM; + } + return __mnt_fs_set_fstype_ptr(fs, p); +} + +/* + * Merges @vfs and @fs options strings into a new string. + * This function cares about 'ro/rw' options. The 'ro' is + * always used if @vfs or @fs is read-only. + * For example: + * + * mnt_merge_optstr("rw,noexec", "ro,journal=update") + * + * returns: "ro,noexec,journal=update" + * + * mnt_merge_optstr("rw,noexec", "rw,journal=update") + * + * returns: "rw,noexec,journal=update" + */ +static char *merge_optstr(const char *vfs, const char *fs) +{ + char *res, *p; + size_t sz; + int ro = 0, rw = 0; + + if (!vfs && !fs) + return NULL; + if (!vfs || !fs) + return strdup(fs ? fs : vfs); + if (!strcmp(vfs, fs)) + return strdup(vfs); /* e.g. "aaa" and "aaa" */ + + /* leave space for the leading "r[ow],", "," and the trailing zero */ + sz = strlen(vfs) + strlen(fs) + 5; + res = malloc(sz); + if (!res) + return NULL; + p = res + 3; /* make a room for rw/ro flag */ + + snprintf(p, sz - 3, "%s,%s", vfs, fs); + + /* remove 'rw' flags */ + rw += !mnt_optstr_remove_option(&p, "rw"); /* from vfs */ + rw += !mnt_optstr_remove_option(&p, "rw"); /* from fs */ + + /* remove 'ro' flags if necessary */ + if (rw != 2) { + ro += !mnt_optstr_remove_option(&p, "ro"); + if (ro + rw < 2) + ro += !mnt_optstr_remove_option(&p, "ro"); + } + + if (!strlen(p)) + memcpy(res, ro ? "ro" : "rw", 3); + else + memcpy(res, ro ? "ro," : "rw,", 3); + return res; +} + +/** + * mnt_fs_strdup_options: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Merges all mount options (VFS, FS and userspace) to one options string + * and returns the result. This function does not modify @fs. + * + * Returns: pointer to string (can be freed by free(3)) or NULL in case of error. + */ +char *mnt_fs_strdup_options(struct libmnt_fs *fs) +{ + char *res; + + if (!fs) + return NULL; + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + errno = 0; + if (fs->optstr) + return strdup(fs->optstr); + + res = merge_optstr(fs->vfs_optstr, fs->fs_optstr); + if (!res && errno) + return NULL; + if (fs->user_optstr && + mnt_optstr_append_option(&res, fs->user_optstr, NULL)) { + free(res); + res = NULL; + } + return res; +} + +/** + * mnt_fs_get_options: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Returns: pointer to string or NULL in case of error. + */ +const char *mnt_fs_get_options(struct libmnt_fs *fs) +{ + if (fs && fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + return fs ? fs->optstr : NULL; +} + +/** + * mnt_fs_get_optional_fields + * @fs: mountinfo entry pointer + * + * Returns: pointer to string with mountinfo optional fields + * or NULL in case of error. + */ +const char *mnt_fs_get_optional_fields(struct libmnt_fs *fs) +{ + return fs ? fs->opt_fields : NULL; +} + +/** + * mnt_fs_set_options: + * @fs: fstab/mtab/mountinfo entry pointer + * @optstr: options string + * + * Splits @optstr to VFS, FS and userspace mount options and updates relevant + * parts of @fs. + * + * Returns: 0 on success, or negative number in case of error. + */ +int mnt_fs_set_options(struct libmnt_fs *fs, const char *optstr) +{ + char *v = NULL, *f = NULL, *u = NULL, *n = NULL; + + if (!fs) + return -EINVAL; + + if (fs->optlist) { + fs->opts_age = 0; + return mnt_optlist_set_optstr(fs->optlist, optstr, NULL); + } + + if (optstr) { + int rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0); + if (rc) + return rc; + n = strdup(optstr); + if (!n) { + free(u); + free(v); + free(f); + return -ENOMEM; + } + } + + free(fs->fs_optstr); + free(fs->vfs_optstr); + free(fs->user_optstr); + free(fs->optstr); + + fs->fs_optstr = f; + fs->vfs_optstr = v; + fs->user_optstr = u; + fs->optstr = n; + + return 0; +} + +/** + * mnt_fs_append_options: + * @fs: fstab/mtab/mountinfo entry + * @optstr: mount options + * + * Parses (splits) @optstr and appends results to VFS, FS and userspace lists + * of options. + * + * If @optstr is NULL, then @fs is not modified and 0 is returned. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_append_options(struct libmnt_fs *fs, const char *optstr) +{ + char *v = NULL, *f = NULL, *u = NULL; + int rc; + + if (!fs) + return -EINVAL; + if (!optstr) + return 0; + if (fs->optlist) { + fs->opts_age = 0; + return mnt_optlist_append_optstr(fs->optlist, optstr, NULL); + } + + rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0); + if (rc) + return rc; + + if (!rc && v) + rc = mnt_optstr_append_option(&fs->vfs_optstr, v, NULL); + if (!rc && f) + rc = mnt_optstr_append_option(&fs->fs_optstr, f, NULL); + if (!rc && u) + rc = mnt_optstr_append_option(&fs->user_optstr, u, NULL); + if (!rc) + rc = mnt_optstr_append_option(&fs->optstr, optstr, NULL); + + free(v); + free(f); + free(u); + + return rc; +} + +/** + * mnt_fs_prepend_options: + * @fs: fstab/mtab/mountinfo entry + * @optstr: mount options + * + * Parses (splits) @optstr and prepends the results to VFS, FS and userspace lists + * of options. + * + * If @optstr is NULL, then @fs is not modified and 0 is returned. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_prepend_options(struct libmnt_fs *fs, const char *optstr) +{ + char *v = NULL, *f = NULL, *u = NULL; + int rc; + + if (!fs) + return -EINVAL; + if (!optstr) + return 0; + + if (fs->optlist) { + fs->opts_age = 0; + return mnt_optlist_prepend_optstr(fs->optlist, optstr, NULL); + } + + rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0); + if (rc) + return rc; + + if (!rc && v) + rc = mnt_optstr_prepend_option(&fs->vfs_optstr, v, NULL); + if (!rc && f) + rc = mnt_optstr_prepend_option(&fs->fs_optstr, f, NULL); + if (!rc && u) + rc = mnt_optstr_prepend_option(&fs->user_optstr, u, NULL); + if (!rc) + rc = mnt_optstr_prepend_option(&fs->optstr, optstr, NULL); + + free(v); + free(f); + free(u); + + return rc; +} + + +/** + * mnt_fs_get_fs_options: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Returns: pointer to superblock (fs-depend) mount option string or NULL. + */ +const char *mnt_fs_get_fs_options(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + return fs->fs_optstr; +} + +/** + * mnt_fs_get_vfs_options: + * @fs: fstab/mtab entry pointer + * + * Returns: pointer to fs-independent (VFS) mount option string or NULL. + */ +const char *mnt_fs_get_vfs_options(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + return fs->vfs_optstr; +} + +/** + * mnt_fs_get_vfs_options_all: + * @fs: fstab/mtab entry pointer + * + * Returns: pointer to newlly allocated string (can be freed by free(3)) or + * NULL in case of error. The string contains all (including defaults) mount + * options. + */ +char *mnt_fs_get_vfs_options_all(struct libmnt_fs *fs) +{ + const struct libmnt_optmap *map = mnt_get_builtin_optmap(MNT_LINUX_MAP); + const struct libmnt_optmap *ent; + const char *opts = mnt_fs_get_options(fs); + char *result = NULL; + unsigned long flags = 0; + + if (!opts || mnt_optstr_get_flags(opts, &flags, map)) + return NULL; + + for (ent = map ; ent && ent->name ; ent++){ + if (ent->id & flags) { /* non-default value */ + if (!(ent->mask & MNT_INVERT)) + mnt_optstr_append_option(&result, ent->name, NULL); + else + continue; + } else if (ent->mask & MNT_INVERT) + mnt_optstr_append_option(&result, ent->name, NULL); + } + + return result; +} + +/** + * mnt_fs_get_user_options: + * @fs: fstab/mtab entry pointer + * + * Returns: pointer to userspace mount option string or NULL. + */ +const char *mnt_fs_get_user_options(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + return fs->user_optstr; +} + +/** + * mnt_fs_get_attributes: + * @fs: fstab/mtab entry pointer + * + * Returns: pointer to attributes string or NULL. + */ +const char *mnt_fs_get_attributes(struct libmnt_fs *fs) +{ + return fs ? fs->attrs : NULL; +} + +/** + * mnt_fs_set_attributes: + * @fs: fstab/mtab/mountinfo entry + * @optstr: options string + * + * Sets mount attributes. The attributes are mount(2) and mount(8) independent + * options, these options are not sent to the kernel and are not interpreted by + * libmount. The attributes are stored in /run/mount/utab only. + * + * The attributes are managed by libmount in userspace only. It's possible + * that information stored in userspace will not be available for libmount + * after CLONE_FS unshare. Be careful, and don't use attributes if possible. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_attributes(struct libmnt_fs *fs, const char *optstr) +{ + return strdup_to_struct_member(fs, attrs, optstr); +} + +/** + * mnt_fs_append_attributes + * @fs: fstab/mtab/mountinfo entry + * @optstr: options string + * + * Appends mount attributes. (See mnt_fs_set_attributes()). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_append_attributes(struct libmnt_fs *fs, const char *optstr) +{ + if (!fs) + return -EINVAL; + if (!optstr) + return 0; + return mnt_optstr_append_option(&fs->attrs, optstr, NULL); +} + +/** + * mnt_fs_prepend_attributes + * @fs: fstab/mtab/mountinfo entry + * @optstr: options string + * + * Prepends mount attributes. (See mnt_fs_set_attributes()). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_prepend_attributes(struct libmnt_fs *fs, const char *optstr) +{ + if (!fs) + return -EINVAL; + if (!optstr) + return 0; + return mnt_optstr_prepend_option(&fs->attrs, optstr, NULL); +} + + +/** + * mnt_fs_get_freq: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Returns: dump frequency in days. + */ +int mnt_fs_get_freq(struct libmnt_fs *fs) +{ + return fs ? fs->freq : 0; +} + +/** + * mnt_fs_set_freq: + * @fs: fstab/mtab entry pointer + * @freq: dump frequency in days + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_freq(struct libmnt_fs *fs, int freq) +{ + if (!fs) + return -EINVAL; + fs->freq = freq; + return 0; +} + +/** + * mnt_fs_get_passno: + * @fs: fstab/mtab entry pointer + * + * Returns: "pass number on parallel fsck". + */ +int mnt_fs_get_passno(struct libmnt_fs *fs) +{ + return fs ? fs->passno: 0; +} + +/** + * mnt_fs_set_passno: + * @fs: fstab/mtab entry pointer + * @passno: pass number + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_passno(struct libmnt_fs *fs, int passno) +{ + if (!fs) + return -EINVAL; + fs->passno = passno; + return 0; +} + +/** + * mnt_fs_get_root: + * @fs: /proc/self/mountinfo entry + * + * Returns: root of the mount within the filesystem or NULL + */ +const char *mnt_fs_get_root(struct libmnt_fs *fs) +{ + return fs ? fs->root : NULL; +} + +/** + * mnt_fs_set_root: + * @fs: mountinfo entry + * @path: root path + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_root(struct libmnt_fs *fs, const char *path) +{ + return strdup_to_struct_member(fs, root, path); +} + +/** + * mnt_fs_get_swaptype: + * @fs: /proc/swaps entry + * + * Returns: swap type or NULL + */ +const char *mnt_fs_get_swaptype(struct libmnt_fs *fs) +{ + return fs ? fs->swaptype : NULL; +} + +/** + * mnt_fs_get_size: + * @fs: /proc/swaps entry + * + * Returns: size + */ +off_t mnt_fs_get_size(struct libmnt_fs *fs) +{ + return fs ? fs->size : 0; +} + +/** + * mnt_fs_get_usedsize: + * @fs: /proc/swaps entry + * + * Returns: used size + */ +off_t mnt_fs_get_usedsize(struct libmnt_fs *fs) +{ + return fs ? fs->usedsize : 0; +} + +/** + * mnt_fs_get_priority: + * @fs: /proc/swaps entry + * + * Returns: priority + */ +int mnt_fs_get_priority(struct libmnt_fs *fs) +{ + return fs ? fs->priority : 0; +} + +/** + * mnt_fs_set_priority: + * @fs: /proc/swaps entry + * @prio: priority + * + * Since: 2.28 + * + * Returns: 0 or -1 in case of error + */ +int mnt_fs_set_priority(struct libmnt_fs *fs, int prio) +{ + if (!fs) + return -EINVAL; + fs->priority = prio; + return 0; +} + +/** + * mnt_fs_get_bindsrc: + * @fs: /run/mount/utab entry + * + * Returns: full path that was used for mount(2) on MS_BIND + */ +const char *mnt_fs_get_bindsrc(struct libmnt_fs *fs) +{ + return fs ? fs->bindsrc : NULL; +} + +/** + * mnt_fs_set_bindsrc: + * @fs: filesystem + * @src: path + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_bindsrc(struct libmnt_fs *fs, const char *src) +{ + return strdup_to_struct_member(fs, bindsrc, src); +} + +/** + * mnt_fs_get_id: + * @fs: /proc/self/mountinfo entry + * + * Returns: mount ID (unique identifier of the mount) or negative number in case of error. + */ +int mnt_fs_get_id(struct libmnt_fs *fs) +{ + return fs ? fs->id : -EINVAL; +} + +/** + * mnt_fs_get_parent_id: + * @fs: /proc/self/mountinfo entry + * + * Returns: parent mount ID or negative number in case of error. + */ +int mnt_fs_get_parent_id(struct libmnt_fs *fs) +{ + return fs ? fs->parent : -EINVAL; +} + +/** + * mnt_fs_get_devno: + * @fs: /proc/self/mountinfo entry + * + * Returns: value of st_dev for files on filesystem or 0 in case of error. + */ +dev_t mnt_fs_get_devno(struct libmnt_fs *fs) +{ + return fs ? fs->devno : 0; +} + +/** + * mnt_fs_get_tid: + * @fs: /proc/tid/mountinfo entry + * + * Returns: TID (task ID) for filesystems read from the mountinfo file + */ +pid_t mnt_fs_get_tid(struct libmnt_fs *fs) +{ + return fs ? fs->tid : 0; +} + +/** + * mnt_fs_get_option: + * @fs: fstab/mtab/mountinfo entry pointer + * @name: option name + * @value: returns pointer to the beginning of the value (e.g. name=VALUE) or NULL + * @valsz: returns size of options value or 0 + * + * Returns: 0 on success, 1 when @name not found or negative number in case of error. + */ +int mnt_fs_get_option(struct libmnt_fs *fs, const char *name, + char **value, size_t *valsz) +{ + char rc = 1; + + if (!fs) + return -EINVAL; + + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + if (fs->fs_optstr) + rc = mnt_optstr_get_option(fs->fs_optstr, name, value, valsz); + if (rc == 1 && fs->vfs_optstr) + rc = mnt_optstr_get_option(fs->vfs_optstr, name, value, valsz); + if (rc == 1 && fs->user_optstr) + rc = mnt_optstr_get_option(fs->user_optstr, name, value, valsz); + return rc; +} + +/** + * mnt_fs_get_attribute: + * @fs: fstab/mtab/mountinfo entry pointer + * @name: option name + * @value: returns pointer to the beginning of the value (e.g. name=VALUE) or NULL + * @valsz: returns size of options value or 0 + * + * Returns: 0 on success, 1 when @name not found or negative number in case of error. + */ +int mnt_fs_get_attribute(struct libmnt_fs *fs, const char *name, + char **value, size_t *valsz) +{ + char rc = 1; + + if (!fs) + return -EINVAL; + if (fs->attrs) + rc = mnt_optstr_get_option(fs->attrs, name, value, valsz); + return rc; +} + +/** + * mnt_fs_get_comment: + * @fs: fstab/mtab/mountinfo entry pointer + * + * Returns: 0 on success, 1 when not found the @name or negative number in case of error. + */ +const char *mnt_fs_get_comment(struct libmnt_fs *fs) +{ + if (!fs) + return NULL; + return fs->comment; +} + +/** + * mnt_fs_set_comment: + * @fs: fstab entry pointer + * @comm: comment string + * + * Note that the comment has to be terminated by '\n' (new line), otherwise + * the whole filesystem entry will be written as a comment to the tabfile (e.g. + * fstab). + * + * Returns: 0 on success or <0 in case of error. + */ +int mnt_fs_set_comment(struct libmnt_fs *fs, const char *comm) +{ + return strdup_to_struct_member(fs, comment, comm); +} + +/** + * mnt_fs_append_comment: + * @fs: fstab entry pointer + * @comm: comment string + * + * See also mnt_fs_set_comment(). + * + * Returns: 0 on success or <0 in case of error. + */ +int mnt_fs_append_comment(struct libmnt_fs *fs, const char *comm) +{ + if (!fs) + return -EINVAL; + + return strappend(&fs->comment, comm); +} + +/** + * mnt_fs_match_target: + * @fs: filesystem + * @target: mountpoint path + * @cache: tags/paths cache or NULL + * + * Possible are three attempts: + * 1) compare @target with @fs->target + * + * 2) realpath(@target) with @fs->target + * + * 3) realpath(@target) with realpath(@fs->target) if @fs is not from + * /proc/self/mountinfo. + * + * However, if mnt_cache_set_targets(cache, mtab) was called, and the + * path @target or @fs->target is found in the @mtab, the canonicalization is + * is not performed (see mnt_resolve_target()). + * + * The 2nd and 3rd attempts are not performed when @cache is NULL. + * + * Returns: 1 if @fs target is equal to @target, else 0. + */ +int mnt_fs_match_target(struct libmnt_fs *fs, const char *target, + struct libmnt_cache *cache) +{ + int rc = 0; + + if (!fs || !target || !fs->target) + return 0; + + /* 1) native paths */ + rc = mnt_fs_streq_target(fs, target); + + if (!rc && cache) { + /* 2) - canonicalized and non-canonicalized */ + char *cn = mnt_resolve_target(target, cache); + rc = (cn && mnt_fs_streq_target(fs, cn)); + + /* 3) - canonicalized and canonicalized */ + if (!rc && cn && !mnt_fs_is_kernel(fs) && !mnt_fs_is_swaparea(fs)) { + char *tcn = mnt_resolve_target(fs->target, cache); + rc = (tcn && strcmp(cn, tcn) == 0); + } + } + + return rc; +} + +/** + * mnt_fs_match_source: + * @fs: filesystem + * @source: tag or path (device or so) or NULL + * @cache: tags/paths cache or NULL + * + * Four attempts are possible: + * 1) compare @source with @fs->source + * 2) compare realpath(@source) with @fs->source + * 3) compare realpath(@source) with realpath(@fs->source) + * 4) compare realpath(@source) with evaluated tag from @fs->source + * + * The 2nd, 3rd and 4th attempts are not performed when @cache is NULL. The + * 2nd and 3rd attempts are not performed if @fs->source is tag. + * + * Returns: 1 if @fs source is equal to @source, else 0. + */ +int mnt_fs_match_source(struct libmnt_fs *fs, const char *source, + struct libmnt_cache *cache) +{ + char *cn; + const char *src, *t, *v; + + if (!fs) + return 0; + + /* 1) native paths... */ + if (mnt_fs_streq_srcpath(fs, source) == 1) + return 1; + + if (!source || !fs->source) + return 0; + + /* ... and tags */ + if (fs->tagname && strcmp(source, fs->source) == 0) + return 1; + + if (!cache) + return 0; + if (fs->flags & (MNT_FS_NET | MNT_FS_PSEUDO)) + return 0; + + cn = mnt_resolve_spec(source, cache); + if (!cn) + return 0; + + /* 2) canonicalized and native */ + src = mnt_fs_get_srcpath(fs); + if (src && mnt_fs_streq_srcpath(fs, cn)) + return 1; + + /* 3) canonicalized and canonicalized */ + if (src) { + src = mnt_resolve_path(src, cache); + if (src && !strcmp(cn, src)) + return 1; + } + if (src || mnt_fs_get_tag(fs, &t, &v)) + /* src path does not match and the tag is not defined */ + return 0; + + /* read @source's tags to the cache */ + if (mnt_cache_read_tags(cache, cn) < 0) { + if (errno == EACCES) { + /* we don't have permissions to read TAGs from + * @source, but can translate the @fs tag to devname. + * + * (because libblkid uses udev symlinks and this is + * accessible for non-root uses) + */ + char *x = mnt_resolve_tag(t, v, cache); + if (x && !strcmp(x, cn)) + return 1; + } + return 0; + } + + /* 4) has the @source a tag that matches with the tag from @fs ? */ + if (mnt_cache_device_has_tag(cache, cn, t, v)) + return 1; + + return 0; +} + +/** + * mnt_fs_match_fstype: + * @fs: filesystem + * @types: filesystem name or comma delimited list of filesystems + * + * For more details see mnt_match_fstype(). + * + * Returns: 1 if @fs type is matching to @types, else 0. The function returns + * 0 when types is NULL. + */ +int mnt_fs_match_fstype(struct libmnt_fs *fs, const char *types) +{ + return mnt_match_fstype(fs->fstype, types); +} + +/** + * mnt_fs_match_options: + * @fs: filesystem + * @options: comma delimited list of options (and nooptions) + * + * For more details see mnt_match_options(). + * + * Returns: 1 if @fs type is matching to @options, else 0. The function returns + * 0 when types is NULL. + */ +int mnt_fs_match_options(struct libmnt_fs *fs, const char *options) +{ + return mnt_match_options(mnt_fs_get_options(fs), options); +} + +/** + * mnt_fs_print_debug + * @fs: fstab/mtab/mountinfo entry + * @file: file stream + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file) +{ + if (!fs || !file) + return -EINVAL; + + if (fs->optlist) + sync_opts_from_optlist(fs, fs->optlist); + + fprintf(file, "------ fs:\n"); + fprintf(file, "source: %s\n", mnt_fs_get_source(fs)); + fprintf(file, "target: %s\n", mnt_fs_get_target(fs)); + fprintf(file, "fstype: %s\n", mnt_fs_get_fstype(fs)); + + if (mnt_fs_get_options(fs)) + fprintf(file, "optstr: %s\n", mnt_fs_get_options(fs)); + if (mnt_fs_get_vfs_options(fs)) + fprintf(file, "VFS-optstr: %s\n", mnt_fs_get_vfs_options(fs)); + if (mnt_fs_get_fs_options(fs)) + fprintf(file, "FS-opstr: %s\n", mnt_fs_get_fs_options(fs)); + if (mnt_fs_get_user_options(fs)) + fprintf(file, "user-optstr: %s\n", mnt_fs_get_user_options(fs)); + if (mnt_fs_get_optional_fields(fs)) + fprintf(file, "optional-fields: '%s'\n", mnt_fs_get_optional_fields(fs)); + if (mnt_fs_get_attributes(fs)) + fprintf(file, "attributes: %s\n", mnt_fs_get_attributes(fs)); + + if (mnt_fs_get_root(fs)) + fprintf(file, "root: %s\n", mnt_fs_get_root(fs)); + + if (mnt_fs_get_swaptype(fs)) + fprintf(file, "swaptype: %s\n", mnt_fs_get_swaptype(fs)); + if (mnt_fs_get_size(fs)) + fprintf(file, "size: %jd\n", mnt_fs_get_size(fs)); + if (mnt_fs_get_usedsize(fs)) + fprintf(file, "usedsize: %jd\n", mnt_fs_get_usedsize(fs)); + if (mnt_fs_get_priority(fs)) + fprintf(file, "priority: %d\n", mnt_fs_get_priority(fs)); + + if (mnt_fs_get_bindsrc(fs)) + fprintf(file, "bindsrc: %s\n", mnt_fs_get_bindsrc(fs)); + if (mnt_fs_get_freq(fs)) + fprintf(file, "freq: %d\n", mnt_fs_get_freq(fs)); + if (mnt_fs_get_passno(fs)) + fprintf(file, "pass: %d\n", mnt_fs_get_passno(fs)); + if (mnt_fs_get_id(fs)) + fprintf(file, "id: %d\n", mnt_fs_get_id(fs)); + if (mnt_fs_get_parent_id(fs)) + fprintf(file, "parent: %d\n", mnt_fs_get_parent_id(fs)); + if (mnt_fs_get_devno(fs)) + fprintf(file, "devno: %d:%d\n", major(mnt_fs_get_devno(fs)), + minor(mnt_fs_get_devno(fs))); + if (mnt_fs_get_tid(fs)) + fprintf(file, "tid: %d\n", mnt_fs_get_tid(fs)); + if (mnt_fs_get_comment(fs)) + fprintf(file, "comment: '%s'\n", mnt_fs_get_comment(fs)); + + return 0; +} + +/** + * mnt_free_mntent: + * @mnt: mount entry + * + * Deallocates the "mntent.h" mount entry. + */ +void mnt_free_mntent(struct mntent *mnt) +{ + if (mnt) { + free(mnt->mnt_fsname); + free(mnt->mnt_dir); + free(mnt->mnt_type); + free(mnt->mnt_opts); + free(mnt); + } +} + +/** + * mnt_fs_to_mntent: + * @fs: filesystem + * @mnt: mount description (as described in mntent.h) + * + * Copies the information from @fs to struct mntent @mnt. If @mnt is already set, + * then the struct mntent items are reallocated and updated. See also + * mnt_free_mntent(). + * + * Returns: 0 on success and a negative number in case of error. + */ +int mnt_fs_to_mntent(struct libmnt_fs *fs, struct mntent **mnt) +{ + int rc; + struct mntent *m; + + if (!fs || !mnt) + return -EINVAL; + + m = *mnt; + if (!m) { + m = calloc(1, sizeof(*m)); + if (!m) + return -ENOMEM; + } + + if ((rc = update_str(&m->mnt_fsname, mnt_fs_get_source(fs)))) + goto err; + if ((rc = update_str(&m->mnt_dir, mnt_fs_get_target(fs)))) + goto err; + if ((rc = update_str(&m->mnt_type, mnt_fs_get_fstype(fs)))) + goto err; + + errno = 0; + m->mnt_opts = mnt_fs_strdup_options(fs); + if (!m->mnt_opts && errno) { + rc = -errno; + goto err; + } + + m->mnt_freq = mnt_fs_get_freq(fs); + m->mnt_passno = mnt_fs_get_passno(fs); + + if (!m->mnt_fsname) { + m->mnt_fsname = strdup("none"); + if (!m->mnt_fsname) + goto err; + } + *mnt = m; + + return 0; +err: + if (m != *mnt) + mnt_free_mntent(m); + return rc; +} diff --git a/libmount/src/fuzz.c b/libmount/src/fuzz.c new file mode 100644 index 0000000..2c84714 --- /dev/null +++ b/libmount/src/fuzz.c @@ -0,0 +1,35 @@ +#include "fuzz.h" +#include "xalloc.h" +#include "mountP.h" + +#include <stdlib.h> +#include <stddef.h> +#include <stdint.h> + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + struct libmnt_table *tb = NULL; + FILE *f = NULL; + + if (size == 0) + return 0; + + // 128Kb should be enough to trigger all the issues we're interested in + if (size > 131072) + return 0; + + tb = mnt_new_table(); + if (!tb) + err_oom(); + + f = fmemopen((char*) data, size, "re"); + if (!f) + err(EXIT_FAILURE, "fmemopen() failed"); + + mnt_table_enable_comments(tb, TRUE); + (void) mnt_table_parse_stream(tb, f, "mountinfo"); + + mnt_unref_table(tb); + fclose(f); + + return 0; +} diff --git a/libmount/src/hook_idmap.c b/libmount/src/hook_idmap.c new file mode 100644 index 0000000..9b2425a --- /dev/null +++ b/libmount/src/hook_idmap.c @@ -0,0 +1,521 @@ +/* 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> + * Copyright (C) 2022 Christian Brauner (Microsoft) <brauner@kernel.org> + * + * 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. + * + * + * This is X-mount.idmap= implementation. + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ +#include <stdbool.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <inttypes.h> + +#include "strutils.h" +#include "all-io.h" +#include "namespace.h" +#include "mount-api-utils.h" + +#include "mountP.h" + +#ifdef HAVE_LINUX_NSFS_H +# include <linux/nsfs.h> +#endif + +#if defined(HAVE_MOUNTFD_API) && defined(HAVE_LINUX_MOUNT_H) + +typedef enum idmap_type_t { + ID_TYPE_UID, /* uidmap entry */ + ID_TYPE_GID, /* gidmap entry */ + ID_TYPE_UIDGID, /* uidmap and gidmap entry */ +} idmap_type_t; + +struct id_map { + idmap_type_t map_type; + uint32_t nsid; + uint32_t hostid; + uint32_t range; + struct list_head map_head; +}; + +struct hook_data { + int userns_fd; + struct list_head id_map; +}; + +static inline struct hook_data *new_hook_data(void) +{ + struct hook_data *hd = calloc(1, sizeof(*hd)); + + if (!hd) + return NULL; + + INIT_LIST_HEAD(&hd->id_map); + hd->userns_fd = -1; + return hd; +} + +static inline void free_hook_data(struct hook_data *hd) +{ + struct list_head *p, *pnext; + struct id_map *idmap; + + if (!hd) + return; + + if (hd->userns_fd >= 0) { + close(hd->userns_fd); + hd->userns_fd = -1; + } + + list_for_each_safe(p, pnext, &hd->id_map) { + idmap = list_entry(p, struct id_map, map_head); + list_del(&idmap->map_head); + free(idmap); + } + INIT_LIST_HEAD(&hd->id_map); + free(hd); +} + +static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, + size_t buf_size) +{ + int fd = -1, rc = -1, setgroups_fd = -1; + char path[PATH_MAX]; + + if (geteuid() != 0 && map_type == ID_TYPE_GID) { + snprintf(path, sizeof(path), "/proc/%d/setgroups", pid); + + setgroups_fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY); + if (setgroups_fd < 0 && errno != ENOENT) + goto err; + + if (setgroups_fd >= 0) { + rc = write_all(setgroups_fd, "deny\n", strlen("deny\n")); + if (rc) + goto err; + } + } + + snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, + map_type == ID_TYPE_UID ? 'u' : 'g'); + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + goto err; + + rc = write_all(fd, buf, buf_size); + +err: + if (fd >= 0) + close(fd); + if (setgroups_fd >= 0) + close(setgroups_fd); + + return rc; +} + +static int map_ids(struct list_head *idmap, pid_t pid) +{ + int fill, left; + char *pos; + int rc = 0; + char mapbuf[4096] = {}; + struct list_head *p; + + for (idmap_type_t type = ID_TYPE_UID; type <= ID_TYPE_GID; type++) { + bool had_entry = false; + + pos = mapbuf; + list_for_each(p, idmap) { + struct id_map *map = list_entry(p, struct id_map, map_head); + + /* + * If the map type is ID_TYPE_UIDGID we need to include + * it in both gid- and uidmap. + */ + if (map->map_type != ID_TYPE_UIDGID && map->map_type != type) + continue; + + had_entry = true; + + left = sizeof(mapbuf) - (pos - mapbuf); + fill = snprintf(pos, left, + "%" PRIu32 " %" PRIu32 " %" PRIu32 "\n", + map->nsid, map->hostid, map->range); + /* + * The kernel only takes <= 4k for writes to + * /proc/<pid>/{g,u}id_map + */ + if (fill <= 0) + return errno = EINVAL, -1; + + pos += fill; + } + if (!had_entry) + continue; + + rc = write_id_mapping(type, pid, mapbuf, pos - mapbuf); + if (rc < 0) + return -1; + + memset(mapbuf, 0, sizeof(mapbuf)); + } + + return 0; +} + +static int wait_for_pid(pid_t pid) +{ + int status, rc; + + do { + rc = waitpid(pid, &status, 0); + } while (rc < 0 && errno == EINTR); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return -1; + + return 0; +} + +static int get_userns_fd_from_idmap(struct list_head *idmap) +{ + int fd_userns = -1; + ssize_t rc = -1; + char c = '1'; + pid_t pid; + int sock_fds[2]; + char path[PATH_MAX]; + + rc = socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, sock_fds); + if (rc < 0) + return -errno; + + pid = fork(); + if (pid < 0) + goto err_close_sock; + + if (pid == 0) { + close(sock_fds[1]); + + rc = unshare(CLONE_NEWUSER); + if (rc < 0) + _exit(EXIT_FAILURE); + + /* Let parent know we're ready to have the idmapping written. */ + rc = write_all(sock_fds[0], &c, 1); + if (rc) + _exit(EXIT_FAILURE); + + /* Hang around until the parent has persisted our namespace. */ + rc = read_all(sock_fds[0], &c, 1); + if (rc != 1) + _exit(EXIT_FAILURE); + + close(sock_fds[0]); + + _exit(EXIT_SUCCESS); + } + close(sock_fds[0]); + sock_fds[0] = -1; + + /* Wait for child to set up a new namespace. */ + rc = read_all(sock_fds[1], &c, 1); + if (rc != 1) { + kill(pid, SIGKILL); + goto err_wait; + } + + rc = map_ids(idmap, pid); + if (rc < 0) { + kill(pid, SIGKILL); + goto err_wait; + } + + snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); + fd_userns = open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY); + + /* Let child know we've persisted its namespace. */ + (void)write_all(sock_fds[1], &c, 1); + +err_wait: + rc = wait_for_pid(pid); + +err_close_sock: + if (sock_fds[0] > 0) + close(sock_fds[0]); + close(sock_fds[1]); + + if (rc < 0 && fd_userns >= 0) { + close(fd_userns); + fd_userns = -1; + } + + return fd_userns; +} + +static int open_userns(const char *path) +{ + + int userns_fd; + + userns_fd = open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (userns_fd < 0) + return -1; + +#if defined(NS_GET_OWNER_UID) + /* + * We use NS_GET_OWNER_UID to verify that this is a user namespace. + * This is on a best-effort basis. If this isn't a userns then + * mount_setattr() will tell us to go away later. + */ + if (ioctl(userns_fd, NS_GET_OWNER_UID, &(uid_t){-1}) < 0) { + close(userns_fd); + return -1; + } +#endif + return userns_fd; +} + +/* + * Create an idmapped mount based on context target, unmounting the + * non-idmapped target mount and attaching the detached idmapped mount target. + */ +static int hook_mount_post( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data) +{ + struct hook_data *hd = (struct hook_data *) data; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = hd->userns_fd + }; + const int recursive = mnt_optlist_is_recursive(cxt->optlist); + const char *target = mnt_fs_get_target(cxt->fs); + int fd_tree = -1; + int rc, is_private = 1; + + assert(hd); + assert(target); + assert(hd->userns_fd >= 0); + + DBG(HOOK, ul_debugobj(hs, " attaching namespace to %s", target)); + + /* + * Once a mount has been attached to the filesystem it can't be + * idmapped anymore. So create a new detached mount. + */ +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + { + struct libmnt_sysapi *api = mnt_context_get_sysapi(cxt); + + if (api && api->fd_tree >= 0) { + fd_tree = api->fd_tree; + is_private = 0; + DBG(HOOK, ul_debugobj(hs, " reuse tree FD")); + } + } +#endif + if (fd_tree < 0) + fd_tree = open_tree(-1, target, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | + (recursive ? AT_RECURSIVE : 0)); + if (fd_tree < 0) { + DBG(HOOK, ul_debugobj(hs, " failed to open tree")); + return -MNT_ERR_IDMAP; + } + + /* Attach the idmapping to the mount. */ + rc = mount_setattr(fd_tree, "", + AT_EMPTY_PATH | (recursive ? AT_RECURSIVE : 0), + &attr, sizeof(attr)); + if (rc < 0) { + DBG(HOOK, ul_debugobj(hs, " failed to set attributes")); + goto done; + } + + /* Attach the idmapped mount. */ + if (is_private) { + /* Unmount the old, non-idmapped mount we just cloned and idmapped. */ + umount2(target, MNT_DETACH); + + rc = move_mount(fd_tree, "", -1, target, MOVE_MOUNT_F_EMPTY_PATH); + if (rc) + DBG(HOOK, ul_debugobj(hs, " failed to set move mount")); + } +done: + if (is_private) + close(fd_tree); + if (rc < 0) + return -MNT_ERR_IDMAP; + + return 0; +} + +/* + * Process X-mount.idmap= mount option + */ +static int hook_prepare_options( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct hook_data *hd = NULL; + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + int rc; + const char *value = NULL; + char *saveptr = NULL, *tok, *buf = NULL; + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return 0; + + opt = mnt_optlist_get_named(ol, "X-mount.idmap", cxt->map_userspace); + if (!opt) + return 0; + + value = mnt_opt_get_value(opt); + if (value) + value = skip_blank(value); + if (!value || !*value) + return errno = EINVAL, -MNT_ERR_MOUNTOPT; + + hd = new_hook_data(); + if (!hd) + return -ENOMEM; + + /* Has the user given us a path to a user namespace? */ + if (*value == '/') { + hd->userns_fd = open_userns(value); + if (hd->userns_fd < 0) + goto err; + goto done; + } + + buf = strdup(value); + if (!buf) + goto err; + + /* + * This is an explicit ID-mapping list of the form: + * [id-type]:id-mount:id-host:id-range [...] + * + * We split the list into separate ID-mapping entries. The individual + * ID-mapping entries are separated by ' '. + * + * A long while ago I made the kernel support up to 340 individual + * ID-mappings. So users have quite a bit of freedom here. + */ + for (tok = strtok_r(buf, " ", &saveptr); tok; + tok = strtok_r(NULL, " ", &saveptr)) { + struct id_map *idmap; + idmap_type_t map_type; + uint32_t nsid = UINT_MAX, hostid = UINT_MAX, range = UINT_MAX; + + if (startswith(tok, "b:")) { + /* b:id-mount:id-host:id-range */ + map_type = ID_TYPE_UIDGID; + tok += 2; + } else if (startswith(tok, "g:")) { + /* g:id-mount:id-host:id-range */ + map_type = ID_TYPE_GID; + tok += 2; + } else if (startswith(tok, "u:")) { + /* u:id-mount:id-host:id-range */ + map_type = ID_TYPE_UID; + tok += 2; + } else { + /* + * id-mount:id-host:id-range + * + * If the user didn't specify it explicitly then they + * want this to be both a gid- and uidmap. + */ + map_type = ID_TYPE_UIDGID; + } + + /* id-mount:id-host:id-range */ + rc = sscanf(tok, "%" PRIu32 ":%" PRIu32 ":%" PRIu32, &nsid, + &hostid, &range); + if (rc != 3) + goto err; + + idmap = calloc(1, sizeof(*idmap)); + if (!idmap) + goto err; + + idmap->map_type = map_type; + idmap->nsid = nsid; + idmap->hostid = hostid; + idmap->range = range; + INIT_LIST_HEAD(&idmap->map_head); + list_add_tail(&idmap->map_head, &hd->id_map); + } + + hd->userns_fd = get_userns_fd_from_idmap(&hd->id_map); + if (hd->userns_fd < 0) + goto err; + +done: + /* define post-mount hook to enter the namespace */ + DBG(HOOK, ul_debugobj(hs, " wanted new user namespace")); + cxt->force_clone = 1; /* require OPEN_TREE_CLONE */ + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + hd, hook_mount_post); + if (rc < 0) + goto err; + + free(buf); + return 0; + +err: + DBG(HOOK, ul_debugobj(hs, " failed to setup idmap")); + free_hook_data(hd); + free(buf); + return -MNT_ERR_MOUNTOPT; +} + + +/* de-initiallize this module */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks and free hook data */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + if (data) + free_hook_data((struct hook_data *) data); + data = NULL; + } + + return 0; +} + +const struct libmnt_hookset hookset_idmap = +{ + .name = "__idmap", + + .firststage = MNT_STAGE_PREP_OPTIONS, + .firstcall = hook_prepare_options, + + .deinit = hookset_deinit +}; + +#endif /* HAVE_MOUNTFD_API && HAVE_LINUX_MOUNT_H */ diff --git a/libmount/src/hook_loopdev.c b/libmount/src/hook_loopdev.c new file mode 100644 index 0000000..8c8f7f2 --- /dev/null +++ b/libmount/src/hook_loopdev.c @@ -0,0 +1,530 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2011-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. + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ +#include <blkid.h> +#include <stdbool.h> + +#include "mountP.h" +#include "loopdev.h" +#include "strutils.h" +#include "linux_version.h" + +struct hook_data { + int loopdev_fd; +}; + +/* de-initiallize this module */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + free(data); + data = NULL; + } + + return 0; +} + +static inline struct hook_data *new_hook_data(void) +{ + struct hook_data *hd = calloc(1, sizeof(*hd)); + + if (!hd) + return NULL; + + hd->loopdev_fd = -1; + return hd; +} + +/* Check if there already exists a mounted loop device on the mountpoint node + * with the same parameters. + */ +static int __attribute__((nonnull)) +is_mounted_same_loopfile(struct libmnt_context *cxt, + const char *target, + const char *backing_file, + uint64_t offset) +{ + struct libmnt_table *tb = NULL; + struct libmnt_iter itr; + struct libmnt_fs *fs; + struct libmnt_cache *cache; + const char *bf; + int rc = 0; + struct libmnt_ns *ns_old; + unsigned long flags = 0; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (mnt_context_get_mountinfo(cxt, &tb)) + return 0; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + DBG(LOOP, ul_debugobj(cxt, "checking if %s mounted on %s", + backing_file, target)); + + rc = mnt_context_get_user_mflags(cxt, &flags); + if (rc) + return 0; + + cache = mnt_context_get_cache(cxt); + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + bf = cache ? mnt_resolve_path(backing_file, cache) : backing_file; + + /* Search for a mountpoint node in mountinfo, proceed if any of these have the + * loop option set or the device is a loop device + */ + while (rc == 0 && mnt_table_next_fs(tb, &itr, &fs) == 0) { + const char *src = mnt_fs_get_source(fs); + const char *opts = mnt_fs_get_user_options(fs); + char *val; + size_t len; + + if (!src || !mnt_fs_match_target(fs, target, cache)) + continue; + + rc = 0; + + if (strncmp(src, "/dev/loop", 9) == 0) { + rc = loopdev_is_used((char *) src, bf, offset, 0, LOOPDEV_FL_OFFSET); + + } else if (opts && (flags & MNT_MS_LOOP) && + mnt_optstr_get_option(opts, "loop", &val, &len) == 0 && val) { + + val = strndup(val, len); + rc = loopdev_is_used((char *) val, bf, offset, 0, LOOPDEV_FL_OFFSET); + free(val); + } + } + if (rc) + DBG(LOOP, ul_debugobj(cxt, "%s already mounted", backing_file)); + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; +} + +static int setup_loopdev(struct libmnt_context *cxt, + struct libmnt_optlist *ol, struct hook_data *hd) +{ + const char *backing_file, *loopdev = NULL; + struct loopdev_cxt lc; + int rc = 0, lo_flags = 0; + uint64_t offset = 0, sizelimit = 0; + bool reuse = FALSE; + struct libmnt_opt *opt, *loopopt = NULL; + + backing_file = mnt_fs_get_srcpath(cxt->fs); + if (!backing_file) + return -EINVAL; + + DBG(LOOP, ul_debugobj(cxt, "trying to setup device for %s", backing_file)); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + if (mnt_optlist_is_rdonly(ol)) { + DBG(LOOP, ul_debugobj(cxt, "enabling READ-ONLY flag")); + lo_flags |= LO_FLAGS_READ_ONLY; + } + + /* + * loop= + */ + if (!rc) + loopopt = mnt_optlist_get_opt(ol, MNT_MS_LOOP, cxt->map_userspace); + + /* + * offset= + */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_OFFSET, cxt->map_userspace)) + && mnt_opt_has_value(opt)) { + if (strtosize(mnt_opt_get_value(opt), &offset)) { + DBG(LOOP, ul_debugobj(cxt, "failed to parse offset=")); + rc = -MNT_ERR_MOUNTOPT; + } + } + + /* + * sizelimit= + */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_SIZELIMIT, cxt->map_userspace)) + && mnt_opt_has_value(opt)) { + if (strtosize(mnt_opt_get_value(opt), &sizelimit)) { + DBG(LOOP, ul_debugobj(cxt, "failed to parse sizelimit=")); + rc = -MNT_ERR_MOUNTOPT; + } + } + + /* + * encryption= + */ + if (!rc && mnt_optlist_get_opt(ol, MNT_MS_ENCRYPTION, cxt->map_userspace)) { + DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported")); + rc = -MNT_ERR_MOUNTOPT; + } + + if (!rc && is_mounted_same_loopfile(cxt, + mnt_context_get_target(cxt), + backing_file, offset)) + rc = -EBUSY; + + if (rc) + goto done_no_deinit; + + /* It is possible to mount the same file more times. If we set more + * than one loop device referring to the same file, kernel has no + * mechanism to detect it. To prevent data corruption, the same loop + * device has to be recycled. + */ + if (backing_file) { + rc = loopcxt_init(&lc, 0); + if (rc) + goto done_no_deinit; + + rc = loopcxt_find_overlap(&lc, backing_file, offset, sizelimit); + switch (rc) { + case 0: /* not found */ + DBG(LOOP, ul_debugobj(cxt, "not found overlapping loopdev")); + loopcxt_deinit(&lc); + break; + + case 1: /* overlap */ + DBG(LOOP, ul_debugobj(cxt, "overlapping %s detected", + loopcxt_get_device(&lc))); + rc = -MNT_ERR_LOOPOVERLAP; + goto done; + + case 2: /* overlap -- full size and offset match (reuse) */ + { + uint32_t lc_encrypt_type = 0; + + DBG(LOOP, ul_debugobj(cxt, "re-using existing loop device %s", + loopcxt_get_device(&lc))); + + /* Open loop device to block device autoclear... */ + if (loopcxt_get_fd(&lc) < 0) { + DBG(LOOP, ul_debugobj(cxt, "failed to get loopdev FD")); + rc = -errno; + goto done; + } + + /* + * Now that we certainly have the loop device open, + * verify the loop device was not autocleared in the + * mean time. + */ + if (!loopcxt_get_info(&lc)) { + DBG(LOOP, ul_debugobj(cxt, "lost race with %s teardown", + loopcxt_get_device(&lc))); + loopcxt_deinit(&lc); + break; + } + + /* Once a loop is initialized RO, there is no + * way to change its parameters. */ + if (loopcxt_is_readonly(&lc) + && !(lo_flags & LO_FLAGS_READ_ONLY)) { + DBG(LOOP, ul_debugobj(cxt, "%s is read-only", + loopcxt_get_device(&lc))); + rc = -EROFS; + goto done; + } + + /* This is no more supported, but check to be safe. */ + if (loopcxt_get_encrypt_type(&lc, &lc_encrypt_type) == 0 + && lc_encrypt_type != LO_CRYPT_NONE) { + DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported for device %s", + loopcxt_get_device(&lc))); + rc = -MNT_ERR_LOOPOVERLAP; + goto done; + } + rc = 0; + /* loop= used with argument. Conflict will occur. */ + if (mnt_opt_has_value(loopopt)) { + rc = -MNT_ERR_LOOPOVERLAP; + goto done; + } else { + reuse = TRUE; + goto success; + } + } + default: /* error */ + goto done; + } + } + + DBG(LOOP, ul_debugobj(cxt, "not found; create a new loop device")); + rc = loopcxt_init(&lc, 0); + if (rc) + goto done_no_deinit; + if (mnt_opt_has_value(loopopt)) { + rc = loopcxt_set_device(&lc, mnt_opt_get_value(loopopt)); + if (rc == 0) + loopdev = loopcxt_get_device(&lc); + } + if (rc) + goto done; + + /* since 2.6.37 we don't have to store backing filename to mountinfo + * because kernel provides the name in /sys. + */ + if (get_linux_version() >= KERNEL_VERSION(2, 6, 37)) { + DBG(LOOP, ul_debugobj(cxt, "enabling AUTOCLEAR flag")); + lo_flags |= LO_FLAGS_AUTOCLEAR; + } + + do { + /* found free device */ + if (!loopdev) { + rc = loopcxt_find_unused(&lc); + if (rc) + goto done; + DBG(LOOP, ul_debugobj(cxt, "trying to use %s", + loopcxt_get_device(&lc))); + } + + /* set device attributes + * -- note that loopcxt_find_unused() resets "lc" + */ + rc = loopcxt_set_backing_file(&lc, backing_file); + + if (!rc && offset) + rc = loopcxt_set_offset(&lc, offset); + if (!rc && sizelimit) + rc = loopcxt_set_sizelimit(&lc, sizelimit); + if (!rc) + loopcxt_set_flags(&lc, lo_flags); + if (rc) { + DBG(LOOP, ul_debugobj(cxt, "failed to set loop attributes")); + goto done; + } + + /* setup the device */ + rc = loopcxt_setup_device(&lc); + if (!rc) + break; /* success */ + + if (loopdev || rc != -EBUSY) { + DBG(LOOP, ul_debugobj(cxt, "failed to setup device")); + rc = -MNT_ERR_LOOPDEV; + goto done; + } + DBG(LOOP, ul_debugobj(cxt, "device stolen...trying again")); + } while (1); + +success: + if (!rc) + rc = mnt_fs_set_source(cxt->fs, loopcxt_get_device(&lc)); + + if (!rc) { + if (loopopt && (reuse || loopcxt_is_autoclear(&lc))) { + /* + * autoclear flag accepted by the kernel, don't store + * the "loop=" option to utab. + */ + DBG(LOOP, ul_debugobj(cxt, "removing unnecessary loop= from utab")); + mnt_optlist_remove_opt(ol, loopopt); + loopopt = NULL; + } + + if (!mnt_optlist_is_rdonly(ol) && loopcxt_is_readonly(&lc)) + /* + * mount planned read-write, but loopdev is read-only, + * let's fix mount options... + */ + mnt_optlist_append_flags(ol, MS_RDONLY, cxt->map_linux); + + /* we have to keep the device open until mount(1), + * otherwise it will be auto-cleared by kernel + */ + hd->loopdev_fd = loopcxt_get_fd(&lc); + if (hd->loopdev_fd < 0) { + DBG(LOOP, ul_debugobj(cxt, "failed to get loopdev FD")); + rc = -errno; + } else + loopcxt_set_fd(&lc, -1, 0); + } +done: + loopcxt_deinit(&lc); +done_no_deinit: + return rc; +} + +static int delete_loopdev(struct libmnt_context *cxt, struct hook_data *hd) +{ + const char *src; + int rc; + + assert(cxt); + assert(cxt->fs); + + src = mnt_fs_get_srcpath(cxt->fs); + if (!src) + return -EINVAL; + + if (hd && hd->loopdev_fd > -1) { + close(hd->loopdev_fd); + hd->loopdev_fd = -1; + } + + rc = loopdev_delete(src); /* see lib/loopdev.c */ + + DBG(LOOP, ul_debugobj(cxt, "deleted [rc=%d]", rc)); + return rc; +} + +/* Now used by umount until context_umount.c will use hooks toosee */ +int mnt_context_delete_loopdev(struct libmnt_context *cxt) +{ + return delete_loopdev(cxt, NULL); +} + +static int is_loopdev_required(struct libmnt_context *cxt, struct libmnt_optlist *ol) +{ + const char *src, *type; + unsigned long flags = 0; + + if (cxt->action != MNT_ACT_MOUNT) + return 0; + if (!cxt->fs) + return 0; + if (mnt_optlist_is_bind(ol) + || mnt_optlist_is_move(ol) + || mnt_context_propagation_only(cxt)) + return 0; + + src = mnt_fs_get_srcpath(cxt->fs); + if (!src) + return 0; /* backing file not set */ + + /* userspace flags */ + if (mnt_context_get_user_mflags(cxt, &flags)) + return 0; + + if (flags & (MNT_MS_LOOP | MNT_MS_OFFSET | MNT_MS_SIZELIMIT)) { + DBG(LOOP, ul_debugobj(cxt, "loopdev specific options detected")); + return 1; + } + + /* Automatically create a loop device from a regular file if a + * filesystem is not specified or the filesystem is known for libblkid + * (these filesystems work with block devices only). The file size + * should be at least 1KiB, otherwise we will create an empty loopdev with + * no mountable filesystem... + * + * Note that there is no restriction (on kernel side) that would prevent a regular + * file as a mount(2) source argument. A filesystem that is able to mount + * regular files could be implemented. + */ + type = mnt_fs_get_fstype(cxt->fs); + + if (mnt_fs_is_regularfs(cxt->fs) && + (!type || strcmp(type, "auto") == 0 || blkid_known_fstype(type))) { + struct stat st; + + if (stat(src, &st) == 0 && S_ISREG(st.st_mode) && + st.st_size > 1024) { + + DBG(LOOP, ul_debugobj(cxt, "automatically enabling loop= option")); + mnt_optlist_append_flags(ol, MNT_MS_LOOP, cxt->map_userspace); + return 1; + } + } + + return 0; +} + +/* call after mount(2) */ +static int hook_cleanup_loopdev( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs __attribute__((__unused__)), + void *data) +{ + struct hook_data *hd = (struct hook_data *) data; + + if (!hd || hd->loopdev_fd < 0) + return 0; + + if (mnt_context_get_status(cxt) == 0) { + /* + * mount(2) failed, delete loopdev + */ + delete_loopdev(cxt, hd); + + } else { + /* + * mount(2) success, close the device + */ + DBG(LOOP, ul_debugobj(cxt, "closing FD")); + close(hd->loopdev_fd); + hd->loopdev_fd = -1; + } + + return 0; +} + +/* call to prepare mount source */ +static int hook_prepare_loopdev( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_optlist *ol; + struct hook_data *hd; + int rc; + + assert(cxt); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + if (!is_loopdev_required(cxt, ol)) + return 0; + hd = new_hook_data(); + if (!hd) + return -ENOMEM; + + rc = setup_loopdev(cxt, ol, hd); + if (!rc) + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + hd, hook_cleanup_loopdev); + if (rc) { + delete_loopdev(cxt, hd); + free(hd); + } + return rc; +} + + +const struct libmnt_hookset hookset_loopdev = +{ + .name = "__loopdev", + + .firststage = MNT_STAGE_PREP_SOURCE, + .firstcall = hook_prepare_loopdev, + + .deinit = hookset_deinit +}; diff --git a/libmount/src/hook_mkdir.c b/libmount/src/hook_mkdir.c new file mode 100644 index 0000000..da9c407 --- /dev/null +++ b/libmount/src/hook_mkdir.c @@ -0,0 +1,139 @@ +/* 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. + * + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ +#include "mountP.h" +#include "fileutils.h" + +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data = NULL; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks and free hook data */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + if (data) + free(data); + data = NULL; + } + + return 0; +} + +static int is_mkdir_required(struct libmnt_context *cxt, const char *tgt, mode_t *mode, int *rc) +{ + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + const char *mstr = NULL; + + assert(cxt); + assert(cxt->map_userspace); + assert(tgt); + assert(mode); + assert(rc); + + *mode = 0; + *rc = 0; + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + opt = mnt_optlist_get_named(ol, "X-mount.mkdir", cxt->map_userspace); + if (!opt) + opt = mnt_optlist_get_named(ol, "x-mount.mkdir", cxt->map_userspace); + if (!opt) + return 0; + + if (mnt_is_path(tgt)) + return 0; + + mstr = mnt_opt_get_value(opt); + + if (mstr && *mstr) { + char *end = NULL; + + if (*mstr == '"') + mstr++; + + errno = 0; + *mode = strtol(mstr, &end, 8); + + if (errno || !end || !(*end == '"' || *end == '\0')) { + DBG(HOOK, ul_debug("failed to parse mkdir mode '%s'", mstr)); + *rc = -MNT_ERR_MOUNTOPT; + return 0; + } + } + + if (!*mode) + *mode = S_IRWXU | /* 0755 */ + S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; + + DBG(HOOK, ul_debug("mkdir %s (%o) wanted", tgt, *mode)); + + return 1; +} + +static int hook_prepare_target( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + int rc = 0; + mode_t mode = 0; + const char *tgt; + + assert(cxt); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + + if (cxt->action == MNT_ACT_MOUNT + && is_mkdir_required(cxt, tgt, &mode, &rc)) { + + struct libmnt_cache *cache; + + /* supported only for root or non-suid mount(8) */ + if (!mnt_context_is_restricted(cxt)) { + rc = ul_mkdir_p(tgt, mode); + if (rc) + DBG(HOOK, ul_debugobj(hs, "mkdir %s failed: %m", tgt)); + } else + rc = -EPERM; + + if (rc == 0) { + cache = mnt_context_get_cache(cxt); + if (cache) { + char *path = mnt_resolve_path(tgt, cache); + if (path && strcmp(path, tgt) != 0) + rc = mnt_fs_set_target(cxt->fs, path); + } + } + } + + return rc; +} + +const struct libmnt_hookset hookset_mkdir = +{ + .name = "__mkdir", + + .firststage = MNT_STAGE_PREP_TARGET, + .firstcall = hook_prepare_target, + + .deinit = hookset_deinit +}; diff --git a/libmount/src/hook_mount.c b/libmount/src/hook_mount.c new file mode 100644 index 0000000..dc3dfa7 --- /dev/null +++ b/libmount/src/hook_mount.c @@ -0,0 +1,790 @@ +/* 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. + * + * + * This is fsconfig/fsopen based mount. + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + * + * Operations: functions and STAGE, all is prepared in hook_prepare(): + * + * mount: + * - fsopen PRE + * - fsmount MOUNT + * - mount_setattr MOUNT (VFS flags) + * - mount_move POST + * - mount_setattr POST (propagation) + * + * remount: + * - open_tree PRE + * - fsconfig MOUNT (FS reconfigure) + * - mount_setattr MOUNT (VFS flags) + * - mount_setattr POST (propagation) + * + * propagation-only: + * - open_tree PRE + * - mount_setattr POST (propagation) + * + * move: + * - open_tree PRE + * - mount_move POST + * + * bind: + * - open_tree PRE (clone) + * - mount_setattr MOUNT (VFS flags) + * - mount_move POST + */ + +#include "mountP.h" +#include "fileutils.h" /* statx() fallback */ +#include "strutils.h" +#include "mount-api-utils.h" +#include "linux_version.h" + +#include <inttypes.h> + +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + +#define get_sysapi(_cxt) mnt_context_get_sysapi(_cxt) + +static void close_sysapi_fds(struct libmnt_sysapi *api) +{ + if (api->fd_fs >= 0) + close(api->fd_fs); + if (api->fd_tree >= 0) + close(api->fd_tree); + + api->fd_tree = api->fd_fs = -1; +} + +/* + * This hookset uses 'struct libmnt_sysapi' (mountP.h) as hookset data. + */ +static void free_hookset_data( struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct libmnt_sysapi *api = mnt_context_get_hookset_data(cxt, hs); + + if (!api) + return; + + close_sysapi_fds(api); + + free(api); + mnt_context_set_hookset_data(cxt, hs, NULL); +} + +/* global data, used by all callbacks */ +static struct libmnt_sysapi *new_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct libmnt_sysapi *api = calloc(1, sizeof(struct libmnt_sysapi)); + + if (!api) + return NULL; + api->fd_fs = api->fd_tree = -1; + + if (mnt_context_set_hookset_data(cxt, hs, api) != 0) { + /* probably ENOMEM problem */ + free(api); + api = NULL; + } + return api; +} + +/* de-initiallize this module */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks */ + while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0); + + /* free and remove global hookset data */ + free_hookset_data(cxt, hs); + + return 0; +} + +static inline int fsconfig_set_value( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int fd, + const char *name, const char *value) +{ + int rc; + char *p = NULL; + + if (value && strstr(value, "\\,")) { + p = strdup(value); + if (!p) + return -EINVAL; + + strrem(p, '\\'); + value = p; + } + + DBG(HOOK, ul_debugobj(hs, " fsconfig(name=\"%s\" value=\"%s\")", name, + value ? : "")); + if (value) { + rc = fsconfig(fd, FSCONFIG_SET_STRING, name, value, 0); + free(p); + } else + rc = fsconfig(fd, FSCONFIG_SET_FLAG, name, NULL, 0); + + set_syscall_status(cxt, "fsconfig", rc == 0); + return rc; +} + +static int configure_superblock(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int fd, int force_rwro) +{ + struct libmnt_optlist *ol; + struct libmnt_iter itr; + struct libmnt_opt *opt; + int rc = 0, has_rwro = 0; + + DBG(HOOK, ul_debugobj(hs, " config FS")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { + const char *name = mnt_opt_get_name(opt); + const char *value = mnt_opt_get_value(opt); + const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt); + const int is_linux = ent && mnt_opt_get_map(opt) == cxt->map_linux; + + if (is_linux && ent->id == MS_RDONLY) { + /* Use ro/rw for superblock (for backward compatibility) */ + value = NULL; + has_rwro = 1; + + } else if (is_linux && ent->mask & MNT_SUPERBLOCK) { + /* Use some old MS_* (VFS) flags as superblock flags */ + ; + + } else if (!name || mnt_opt_get_map(opt) || mnt_opt_is_external(opt)) + /* Ignore VFS flags, userspace and external options */ + continue; + + rc = fsconfig_set_value(cxt, hs, fd, name, value); + if (rc != 0) + goto done; + } + + if (force_rwro && !has_rwro) + rc = fsconfig_set_value(cxt, hs, fd, "rw", NULL); + +done: + DBG(HOOK, ul_debugobj(hs, " config done [rc=%d]", rc)); + return rc != 0 && errno ? -errno : rc; +} + +static int open_fs_configuration_context(struct libmnt_context *cxt, + struct libmnt_sysapi *api, + const char *type) +{ + DBG(HOOK, ul_debug(" new FS '%s'", type)); + + if (!type) + return -EINVAL; + + DBG(HOOK, ul_debug(" fsopen(%s)", type)); + + api->fd_fs = fsopen(type, FSOPEN_CLOEXEC); + set_syscall_status(cxt, "fsopen", api->fd_fs >= 0); + if (api->fd_fs < 0) + return -errno; + api->is_new_fs = 1; + return api->fd_fs; +} + +static int open_mount_tree(struct libmnt_context *cxt, const char *path, unsigned long mflg) +{ + unsigned long oflg = OPEN_TREE_CLOEXEC; + int rc = 0, fd = -1; + + if (mflg == (unsigned long) -1) { + rc = mnt_optlist_get_flags(cxt->optlist, &mflg, cxt->map_linux, 0); + if (rc) + return rc; + } + if (!path) { + path = mnt_fs_get_target(cxt->fs); + if (!path) + return -EINVAL; + } + + /* Classic -oremount,bind,ro is not bind operation, it's just + * VFS flags update only */ + if ((mflg & MS_BIND) && !(mflg & MS_REMOUNT)) { + oflg |= OPEN_TREE_CLONE; + + if (mnt_optlist_is_rbind(cxt->optlist)) + oflg |= AT_RECURSIVE; + } + + if (cxt->force_clone) + oflg |= OPEN_TREE_CLONE; + + DBG(HOOK, ul_debug("open_tree(path=%s%s%s)", path, + oflg & OPEN_TREE_CLONE ? " clone" : "", + oflg & AT_RECURSIVE ? " recursive" : "")); + fd = open_tree(AT_FDCWD, path, oflg); + set_syscall_status(cxt, "open_tree", fd >= 0); + + return fd; +} + +static int hook_create_mount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_sysapi *api; + const char *src; + int rc = 0; + + assert(cxt); + + if (mnt_context_helper_executed(cxt)) + return 0; + + assert(cxt->fs); + + api = get_sysapi(cxt); + assert(api); + + if (api->fd_fs < 0) { + const char *type = mnt_fs_get_fstype(cxt->fs); + + rc = open_fs_configuration_context(cxt, api, type); + if (rc < 0) { + rc = api->fd_fs; + goto done; + } + } + + src = mnt_fs_get_srcpath(cxt->fs); + if (!src) + return -EINVAL; + + DBG(HOOK, ul_debugobj(hs, "init FS")); + + rc = fsconfig(api->fd_fs, FSCONFIG_SET_STRING, "source", src, 0); + set_syscall_status(cxt, "fsconfig", rc == 0); + + if (!rc) + rc = configure_superblock(cxt, hs, api->fd_fs, 0); + if (!rc) { + DBG(HOOK, ul_debugobj(hs, "create FS")); + rc = fsconfig(api->fd_fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + set_syscall_status(cxt, "fsconfig", rc == 0); + } + + if (!rc) { + api->fd_tree = fsmount(api->fd_fs, FSMOUNT_CLOEXEC, 0); + set_syscall_status(cxt, "fsmount", api->fd_tree >= 0); + if (api->fd_tree < 0) + rc = -errno; + } + + if (rc) + /* cleanup after fail (libmount may only try the FS type) */ + close_sysapi_fds(api); + +#if defined(HAVE_STATX) && defined(HAVE_STRUCT_STATX) && defined(HAVE_STRUCT_STATX_STX_MNT_ID) + if (!rc && cxt->fs) { + struct statx st; + + rc = statx(api->fd_tree, "", AT_EMPTY_PATH, STATX_MNT_ID, &st); + if (rc == 0) { + cxt->fs->id = (int) st.stx_mnt_id; + if (cxt->update) { + struct libmnt_fs *fs = mnt_update_get_fs(cxt->update); + if (fs) + fs->id = cxt->fs->id; + } + } + } +#endif + +done: + DBG(HOOK, ul_debugobj(hs, "create FS done [rc=%d, id=%d]", rc, cxt->fs ? cxt->fs->id : -1)); + return rc; +} + +static int hook_reconfigure_mount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_sysapi *api; + int rc = 0; + + assert(cxt); + + if (mnt_context_helper_executed(cxt)) + return 0; + + api = get_sysapi(cxt); + assert(api); + assert(api->fd_tree >= 0); + + if (api->fd_fs < 0) { + api->fd_fs = fspick(api->fd_tree, "", FSPICK_EMPTY_PATH | + FSPICK_NO_AUTOMOUNT); + set_syscall_status(cxt, "fspick", api->fd_fs >= 0); + if (api->fd_fs < 0) + return -errno; + } + + rc = configure_superblock(cxt, hs, api->fd_fs, 1); + if (!rc) { + DBG(HOOK, ul_debugobj(hs, "re-configurate FS")); + rc = fsconfig(api->fd_fs, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0); + set_syscall_status(cxt, "fsconfig", rc == 0); + } + + DBG(HOOK, ul_debugobj(hs, "reconf FS done [rc=%d]", rc)); + return rc; +} + +static int set_vfsflags(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + uint64_t set, uint64_t clr, int recursive) +{ + struct libmnt_sysapi *api; + struct mount_attr attr = { .attr_clr = 0 }; + unsigned int callflags = AT_EMPTY_PATH; + int rc; + + api = get_sysapi(cxt); + assert(api); + + /* fallback only; necessary when init_sysapi() during preparation + * cannot open the tree -- for example when we call /sbin/mount.<type> */ + if (api->fd_tree < 0 && mnt_fs_get_target(cxt->fs)) { + rc = api->fd_tree = open_mount_tree(cxt, NULL, (unsigned long) -1); + if (rc < 0) + return rc; + rc = 0; + } + + if (recursive) + callflags |= AT_RECURSIVE; + + DBG(HOOK, ul_debugobj(hs, + "mount_setattr(set=0x%08" PRIx64" clr=0x%08" PRIx64")", set, clr)); + attr.attr_set = set; + attr.attr_clr = clr; + + errno = 0; + rc = mount_setattr(api->fd_tree, "", callflags, &attr, sizeof(attr)); + set_syscall_status(cxt, "mount_setattr", rc == 0); + + if (rc && errno == EINVAL) + return -MNT_ERR_APPLYFLAGS; + + return rc == 0 ? 0 : -errno; +} + +static int hook_set_vfsflags(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_optlist *ol; + uint64_t set = 0, clr = 0; + int rc = 0; + + if (mnt_context_helper_executed(cxt)) + return 0; + + DBG(HOOK, ul_debugobj(hs, "setting VFS flags")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + /* normal flags */ + rc = mnt_optlist_get_attrs(ol, &set, &clr, MNT_OL_NOREC); + if (!rc && (set || clr)) + rc = set_vfsflags(cxt, hs, set, clr, 0); + + /* recursive flags */ + set = clr = 0; + if (!rc) + rc = mnt_optlist_get_attrs(ol, &set, &clr, MNT_OL_REC); + if (!rc && (set || clr)) + rc = set_vfsflags(cxt, hs, set, clr, 1); + + return rc; +} + +static int hook_set_propagation(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_sysapi *api; + struct libmnt_optlist *ol; + struct libmnt_iter itr; + struct libmnt_opt *opt; + int rc = 0; + + DBG(HOOK, ul_debugobj(hs, "setting propagation")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + api = get_sysapi(cxt); + assert(api); + + /* fallback only; necessary when init_sysapi() during preparation + * cannot open the tree -- for example when we call /sbin/mount.<type> */ + if (api->fd_tree < 0 && mnt_fs_get_target(cxt->fs)) { + rc = api->fd_tree = open_mount_tree(cxt, NULL, (unsigned long) -1); + if (rc < 0) + goto done; + rc = 0; + } + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { + const struct libmnt_optmap *map = mnt_opt_get_map(opt); + const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt); + struct mount_attr attr = { .attr_clr = 0 }; + unsigned int flgs = AT_EMPTY_PATH; + + if (cxt->map_linux != map) + continue; + if (mnt_opt_is_external(opt)) + continue; + if (!ent || !ent->id || !(ent->id & MS_PROPAGATION)) + continue; + + attr.propagation = ent->id & MS_PROPAGATION; + if (ent->id & MS_REC) + flgs |= AT_RECURSIVE; + + DBG(HOOK, ul_debugobj(hs, + "mount_setattr(propagation=0x%08" PRIx64")", + (uint64_t) attr.propagation)); + + rc = mount_setattr(api->fd_tree, "", flgs, &attr, sizeof(attr)); + set_syscall_status(cxt, "mount_setattr", rc == 0); + + if (rc && errno == EINVAL) + return -MNT_ERR_APPLYFLAGS; + if (rc != 0) + break; + } +done: + return rc == 0 ? 0 : -errno; +} + +static int hook_attach_target(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_sysapi *api; + const char *target; + int rc = 0; + + if (mnt_context_helper_executed(cxt)) + return 0; + + target = mnt_fs_get_target(cxt->fs); + if (!target) + return -EINVAL; + + api = get_sysapi(cxt); + assert(api); + assert(api->fd_tree >= 0); + + DBG(HOOK, ul_debugobj(hs, "move_mount(to=%s)", target)); + + /* umount old target if we created a clone */ + if (cxt->force_clone + && !api->is_new_fs + && !mnt_optlist_is_bind(cxt->optlist)) { + + DBG(HOOK, ul_debugobj(hs, "remove expired target")); + umount2(target, MNT_DETACH); + } + + rc = move_mount(api->fd_tree, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + set_syscall_status(cxt, "move_mount", rc == 0); + + return rc == 0 ? 0 : -errno; +} + +static inline int fsopen_is_supported(void) +{ + int dummy, rc = 1; + + errno = 0; + dummy = fsopen(NULL, FSOPEN_CLOEXEC); + + if (errno == ENOSYS) + rc = 0; + if (dummy >= 0) + close(dummy); + return rc; +} + +static inline int mount_setattr_is_supported(void) +{ + int rc; + + errno = 0; + rc = mount_setattr(-1, NULL, 0, NULL, 0); + return !(rc == -1 && errno == ENOSYS); +} + +/* + * open_tree() and fsopen() + */ +static int init_sysapi(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + unsigned long flags) +{ + struct libmnt_sysapi *api; + const char *path = NULL; + + assert(cxt); + assert(hs); + + DBG(HOOK, ul_debugobj(hs, "initialize API fds")); + + /* A) tree based operation -- the tree is mount point */ + if ((flags & MS_REMOUNT) + || mnt_context_propagation_only(cxt)) { + DBG(HOOK, ul_debugobj(hs, " REMOUNT/propagation")); + path = mnt_fs_get_target(cxt->fs); + if (!path) + return -EINVAL; + + /* B) tree based operation -- the tree is mount source */ + } else if ((flags & MS_BIND) + || (flags & MS_MOVE)) { + DBG(HOOK, ul_debugobj(hs, " BIND/MOVE")); + path = mnt_fs_get_srcpath(cxt->fs); + if (!path) + return -EINVAL; + } + + api = new_hookset_data(cxt, hs); + if (!api) + return -ENOMEM; + + if (path) { + api->fd_tree = open_mount_tree(cxt, path, flags); + if (api->fd_tree < 0) + goto fail; + + /* C) FS based operation + * + * Note, fstype is optinal and may be specified later if mount by + * list of FS types (mount -t foo,bar,ext4). In this case fsopen() + * is called later in hook_create_mount(). */ + } else { + const char *type = mnt_fs_get_fstype(cxt->fs); + int rc = 0; + + /* fsopen() to create a superblock */ + if (cxt->helper == NULL && type && !strchr(type, ',')) + rc = open_fs_configuration_context(cxt, api, type); + + /* dummy fsopen() to test if API is available */ + else if (!fsopen_is_supported()) { + errno = ENOSYS; + rc = -errno; + set_syscall_status(cxt, "fsopen", rc == 0); + } + if (rc < 0) + goto fail; + } + + return 0; +fail: + DBG(HOOK, ul_debugobj(hs, "init fs/tree failed [errno=%d %m]", errno)); + return -errno; +} + +static int force_classic_mount(struct libmnt_context *cxt) +{ + const char *env = getenv("LIBMOUNT_FORCE_MOUNT2"); + + if (env) { + if (strcmp(env, "always") == 0) + return 1; + if (strcmp(env, "never") == 0) + return 0; + } + + /* "auto" (default) -- try to be smart */ + + /* For external /sbin/mount.<type> helpers we use the new API only for + * propagation setting. In this case, the usability of mount_setattr() + * will be verified later */ + if (cxt->helper) + return 0; + + /* + * The current kernel btrfs driver does not completely implement + * fsconfig() as it does not work with selinux stuff. + * + * Don't use the new mount API in this situation. Let's hope this issue + * is temporary. + */ + { + const char *type = mnt_fs_get_fstype(cxt->fs); + + if (type && strcmp(type, "btrfs") == 0 && cxt->has_selinux_opt) + return 1; + } + + return 0; +} + + +/* + * Analyze library context and register hook to call mount-like syscalls. + * + * Note that this function interprets classic MS_* flags by new Linux mount FD + * based API. + * + * Returns: 0 on success, <0 on error, >0 on recover-able error + */ +static int hook_prepare(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_optlist *ol; + unsigned long flags = 0; + uint64_t set = 0, clr = 0; + int rc = 0; + + assert(cxt); + assert(hs == &hookset_mount); + + if (force_classic_mount(cxt)) { + DBG(HOOK, ul_debugobj(hs, "new API disabled")); + return 0; + } + + DBG(HOOK, ul_debugobj(hs, "prepare mount")); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + /* classic MS_* flags (include oprations like MS_REMOUNT, etc) */ + rc = mnt_optlist_get_flags(ol, &flags, cxt->map_linux, 0); + + /* MOUNT_ATTR_* flags for mount_setattr() */ + if (!rc) + rc = mnt_optlist_get_attrs(ol, &set, &clr, 0); + + /* open_tree() or fsopen() */ + if (!rc) { + rc = init_sysapi(cxt, hs, flags); + if (rc && cxt->syscall_status == -ENOSYS) + goto enosys; + } + + /* check mutually exclusive operations */ + if (!rc && (flags & MS_BIND) && (flags & MS_MOVE)) + return -EINVAL; + if (!rc && (flags & MS_MOVE) && (flags & MS_REMOUNT)) + return -EINVAL; + + /* classic remount (note -oremount,bind,ro is not superblock reconfiguration) */ + if (!rc + && cxt->helper == NULL + && (flags & MS_REMOUNT) + && !(flags & MS_BIND)) + rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL, + hook_reconfigure_mount); + + /* create a new FS instance */ + else if (!rc + && cxt->helper == NULL + && !(flags & MS_BIND) + && !(flags & MS_MOVE) + && !(flags & MS_REMOUNT) + && !mnt_context_propagation_only(cxt)) + rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL, + hook_create_mount); + + /* call mount_setattr() */ + if (!rc + && cxt->helper == NULL + && (set != 0 || clr != 0 || (flags & MS_REMOUNT))) { + /* + * mount_setattr() supported, but not usable for remount + * https://github.com/torvalds/linux/commit/dd8b477f9a3d8edb136207acb3652e1a34a661b7 + */ + if (get_linux_version() < KERNEL_VERSION(5, 14, 0)) + goto enosys; + + if (!mount_setattr_is_supported()) + goto enosys; + + rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL, + hook_set_vfsflags); + } + + /* call move_mount() to attach target */ + if (!rc + && cxt->helper == NULL + && (cxt->force_clone || + (!(flags & MS_REMOUNT) && !mnt_context_propagation_only(cxt)))) + rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT_POST, NULL, + hook_attach_target); + + /* set propagation (has to be attached to VFS) */ + if (!rc && mnt_optlist_get_propagation(ol)) { + if (!mount_setattr_is_supported()) + goto enosys; + + rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT_POST, NULL, + hook_set_propagation); + } + + DBG(HOOK, ul_debugobj(hs, "prepare mount done [rc=%d]", rc)); + return rc; + +enosys: + /* we need to recover from this error, so hook_mount_legacy.c + * can try to continue */ + DBG(HOOK, ul_debugobj(hs, "failed to init new API")); + reset_syscall_status(cxt); + hookset_deinit(cxt, hs); + return 1; +} + +const struct libmnt_hookset hookset_mount = +{ + .name = "__mount", + + .firststage = MNT_STAGE_PREP, + .firstcall = hook_prepare, + + .deinit = hookset_deinit +}; +#endif /* USE_LIBMOUNT_MOUNTFD_SUPPORT */ diff --git a/libmount/src/hook_mount_legacy.c b/libmount/src/hook_mount_legacy.c new file mode 100644 index 0000000..7e62864 --- /dev/null +++ b/libmount/src/hook_mount_legacy.c @@ -0,0 +1,310 @@ +/* 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. + * + * + * This is classic mount(2) based mount. + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ + +#include "mountP.h" + +/* mount(2) flags for additional propagation changes etc. */ +struct hook_data { + unsigned long flags; +}; + +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data = NULL; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks and free hook data */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + if (data) + free(data); + data = NULL; + } + + return 0; +} + +static struct hook_data *new_hook_data(void) +{ + return calloc(1, sizeof(struct hook_data)); +} + +/* call mount(2) for propagation flags */ +static int hook_propagation(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data) +{ + int rc; + struct hook_data *hd = (struct hook_data *) data; + unsigned long extra = 0; + + assert(hd); + assert(cxt); + assert(cxt->fs); + assert(cxt->optlist); + + DBG(HOOK, ul_debugobj(hs, " calling mount(2) for propagation: 0x%08lx %s", + hd->flags, + hd->flags & MS_REC ? " (recursive)" : "")); + + /* + * hd->flags are propagation flags as set in prepare_propagation() + * + * @cxt contains global mount flags, may be modified after preparation + * stage (for example when libmount blindly tries FS type then it uses + * MS_SILENT) + */ + if (mnt_optlist_is_silent(cxt->optlist)) + extra |= MS_SILENT; + + rc = mount("none", mnt_fs_get_target(cxt->fs), NULL, + hd->flags | extra, NULL); + + if (rc) { + /* Update global syscall status if only this function called */ + if (mnt_context_propagation_only(cxt)) { + cxt->syscall_status = -errno; + cxt->syscall_name = "mount"; + } + + DBG(HOOK, ul_debugobj(hs, " mount(2) failed [errno=%d %m]", errno)); + rc = -MNT_ERR_APPLYFLAGS; + } + return rc; +} + +/* + * add additional mount(2) syscalls to set propagation flags after regular mount(2). + */ +static int prepare_propagation(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct libmnt_optlist *ol; + struct libmnt_iter itr; + struct libmnt_opt *opt; + + assert(cxt); + assert(cxt->fs); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { + int rc; + struct hook_data *data; + const struct libmnt_optmap *map = mnt_opt_get_map(opt); + const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt); + + if (!map || map != cxt->map_linux) + continue; + if (!(ent->id & MS_PROPAGATION)) + continue; + + data = new_hook_data(); + if (!data) + return -ENOMEM; + + data->flags = ent->id; + + DBG(HOOK, ul_debugobj(hs, " adding mount(2) call for %s", ent->name)); + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + data, + hook_propagation); + if (rc) + return rc; + + DBG(HOOK, ul_debugobj(hs, " removing '%s' flag from primary mount(2)", ent->name)); + mnt_optlist_remove_opt(ol, opt); + } + + return 0; +} + +/* call mount(2) for bind,remount */ +static int hook_bindremount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, void *data) +{ + int rc; + struct hook_data *hd = (struct hook_data *) data; + unsigned long extra = 0; + + DBG(HOOK, ul_debugobj(hs, " mount(2) for bind-remount: 0x%08lx %s", + hd->flags, + hd->flags & MS_REC ? " (recursive)" : "")); + + if (mnt_optlist_is_silent(cxt->optlist)) + extra |= MS_SILENT; + + /* for the flags see comment in hook_propagation() */ + rc = mount("none", mnt_fs_get_target(cxt->fs), NULL, + hd->flags | extra, NULL); + + if (rc) + DBG(HOOK, ul_debugobj(hs, " mount(2) failed" + " [rc=%d errno=%d %m]", rc, errno)); + return rc; +} + +/* + * add additional mount(2) syscall request to implement "bind,<flags>", the first regular + * mount(2) is the "bind" operation, the second is "remount,bind,<flags>" call. + */ +static int prepare_bindremount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hook_data *data; + int rc; + + assert(cxt); + + DBG(HOOK, ul_debugobj(hs, " adding mount(2) call for bint-remount")); + + data = new_hook_data(); + if (!data) + return -ENOMEM; + + mnt_context_get_mflags(cxt, &data->flags); + + assert(data->flags & MS_BIND); + assert(!(data->flags & MS_REMOUNT)); + + data->flags |= (MS_REMOUNT | MS_BIND); + + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, data, hook_bindremount); + return rc; +} + + + + +/* call mount(2) for regular FS mount, mount flags and options are read from + * library context struct. There are no private hook data. + */ +static int hook_mount(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + int rc = 0; + unsigned long flags = 0; + struct libmnt_optlist *ol = NULL; + const char *src, *target, *type, *options = NULL; + + src = mnt_fs_get_srcpath(cxt->fs); + target = mnt_fs_get_target(cxt->fs); + type = mnt_fs_get_fstype(cxt->fs); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + if (!target) + return -EINVAL; + if (!src) + src = "none"; + + /* FS specific mount options/data */ + if (cxt->flags & MNT_FL_MOUNTDATA) + options = cxt->mountdata; + else + rc = mnt_optlist_get_optstr(ol, &options, + NULL, MNT_OL_FLTR_UNKNOWN); + /* mount flags */ + if (!rc) + rc = mnt_optlist_get_flags(ol, &flags, + mnt_get_builtin_optmap(MNT_LINUX_MAP), 0); + if (rc) + return rc; + + DBG(HOOK, ul_debugobj(hs, " mount(2) " + "[source=%s, target=%s, type=%s, flags=0x%08lx, options=%s]", + src, target, type, flags, + options ? (cxt->flags & MNT_FL_MOUNTDATA) ? "binary" : + options : "<none>")); + + if (mount(src, target, type, flags, options)) { + cxt->syscall_status = -errno; + cxt->syscall_name = "mount"; + DBG(HOOK, ul_debugobj(hs, " mount(2) failed [errno=%d %m]", + -cxt->syscall_status)); + rc = -cxt->syscall_status; + return rc; + } + + cxt->syscall_status = 0; + return rc; +} + +/* + * analyze library context and register hooks to call one or more mount(2) syscalls + */ +static int hook_prepare(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + int rc = 0; + unsigned long flags = 0; + + assert(cxt); + assert(hs == &hookset_mount_legacy); + +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + /* do nothing when a new __mount succesfully registred */ + if (mnt_context_has_hook(cxt, &hookset_mount, 0, NULL)) + return 0; +#endif + /* append regular FS mount(2) */ + if (!mnt_context_propagation_only(cxt) && !cxt->helper) + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT, NULL, hook_mount); + + if (!rc) + rc = mnt_context_get_mflags(cxt, &flags); + if (rc) + return rc; + + /* add extra mount(2) calls for each propagation flag */ + if (flags & MS_PROPAGATION) { + rc = prepare_propagation(cxt, hs); + if (rc) + return rc; + } + + /* add extra mount(2) call to implement "bind,remount,<flags>" */ + if ((flags & MS_BIND) + && (flags & MNT_BIND_SETTABLE) + && !(flags & MS_REMOUNT)) { + rc = prepare_bindremount(cxt, hs); + if (rc) + return rc; + } + + return rc; +} + +const struct libmnt_hookset hookset_mount_legacy = +{ + .name = "__legacy-mount", + + .firststage = MNT_STAGE_PREP, + .firstcall = hook_prepare, + + .deinit = hookset_deinit +}; diff --git a/libmount/src/hook_owner.c b/libmount/src/hook_owner.c new file mode 100644 index 0000000..11b238c --- /dev/null +++ b/libmount/src/hook_owner.c @@ -0,0 +1,175 @@ +/* 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. + * + * + * This is X-mount.owner=, X-mount.group= and X-mount.mode= implementation. + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ +#include <sched.h> + +#include "mountP.h" +#include "fileutils.h" + +struct hook_data { + uid_t owner; + gid_t group; + mode_t mode; +}; + +/* de-initiallize this module */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks and free hook data */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + free(data); + data = NULL; + } + + return 0; +} + +static int hook_post( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs __attribute__((__unused__)), + void *data) +{ + struct hook_data *hd = (struct hook_data *) data; + const char *target; + int rc = 0; + + assert(cxt); + + if (!hd || !cxt->fs) + return 0; + + target = mnt_fs_get_target(cxt->fs); + if (!target) + return 0; + + if (hd->owner != (uid_t) -1 || hd->group != (uid_t) -1) { + DBG(CXT, ul_debugobj(cxt, " lchown(%s, %u, %u)", target, hd->owner, hd->group)); + if (lchown(target, hd->owner, hd->group) == -1) + return -MNT_ERR_CHOWN; + } + + if (hd->mode != (mode_t) -1) { + DBG(CXT, ul_debugobj(cxt, " chmod(%s, %04o)", target, hd->mode)); + if (chmod(target, hd->mode) == -1) + return -MNT_ERR_CHMOD; + } + + return rc; +} + +static inline struct hook_data *new_hook_data(void) +{ + struct hook_data *hd = calloc(1, sizeof(*hd)); + + if (!hd) + return NULL; + + hd->owner = (uid_t) -1; + hd->group = (gid_t) -1; + hd->mode = (mode_t) -1; + return hd; +} + +static int hook_prepare_options( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct hook_data *hd = NULL; + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + int rc = 0; + + assert(cxt); + assert(cxt->map_userspace); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + opt = mnt_optlist_get_named(ol, "X-mount.owner", cxt->map_userspace); + if (opt) { + const char *value = mnt_opt_get_value(opt); + if (!value) + goto fail; + if (!hd) { + hd = new_hook_data(); + if (!hd) + goto fail; + } + if (mnt_parse_uid(value, strlen(value), &hd->owner)) + goto fail; + } + + opt = mnt_optlist_get_named(ol, "X-mount.group", cxt->map_userspace); + if (opt) { + const char *value = mnt_opt_get_value(opt); + if (!value) + goto fail; + if (!hd) { + hd = new_hook_data(); + if (!hd) + goto fail; + } + if (mnt_parse_gid(value, strlen(value), &hd->group)) + goto fail; + } + + opt = mnt_optlist_get_named(ol, "X-mount.mode", cxt->map_userspace); + if (opt) { + const char *value = mnt_opt_get_value(opt); + if (!value) + goto fail; + if (!hd) { + hd = new_hook_data(); + if (!hd) + goto fail; + } + if (mnt_parse_mode(value, strlen(value), &hd->mode)) + goto fail; + } + + if (hd) { + DBG(CXT, ul_debugobj(cxt, " wanted ownership %d:%d, mode %04o", + hd->owner, hd->group, hd->mode)); + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_POST, + hd, hook_post); + if (rc < 0) + goto fail; + } + return 0; +fail: + if (rc == 0) + rc = -MNT_ERR_MOUNTOPT; + free(hd); + return rc; +} + + +const struct libmnt_hookset hookset_owner = +{ + .name = "__owner", + + .firststage = MNT_STAGE_PREP_OPTIONS, + .firstcall = hook_prepare_options, + + .deinit = hookset_deinit +}; diff --git a/libmount/src/hook_selinux.c b/libmount/src/hook_selinux.c new file mode 100644 index 0000000..d6bae95 --- /dev/null +++ b/libmount/src/hook_selinux.c @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2023 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. + * + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ +#ifdef HAVE_LIBSELINUX +#include <selinux/selinux.h> +#include <selinux/context.h> + +#include "mountP.h" +#include "fileutils.h" +#include "linux_version.h" + +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + void *data = NULL; + + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks and free hook data */ + while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) { + if (data) + free(data); + data = NULL; + } + + return 0; +} + +static inline int is_option(const char *name, const char *const *names) +{ + const char *const *p; + + for (p = names; p && *p; p++) { + if (strcmp(name, *p) == 0) + return 1; + } + return 0; +} + +/* Converts rootcontext=@target to the real selinxu context + */ +static int hook_selinux_target( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + const char *tgt, *val; + char *raw = NULL; + int rc = 0; + + assert(cxt); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + if (cxt->action != MNT_ACT_MOUNT) + return 0; + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -EINVAL; + + opt = mnt_optlist_get_named(ol, "rootcontext", NULL); + if (!opt) + return 0; + + val = mnt_opt_get_value(opt); + if (!val || strcmp(val, "@target") != 0) + return 0; + + + rc = getfilecon_raw(tgt, &raw); + if (rc <= 0 || !raw) { + rc = errno ? -errno : -EINVAL; + DBG(HOOK, ul_debugobj(hs, " SELinux fix @target failed [rc=%d]", rc)); + } else { + DBG(HOOK, ul_debugobj(hs, " SELinux fix @target to %s", raw)); + rc = 0; /* getfilecon_raw(3) returns the size of the extended attribute value */ + } + if (!rc) + rc = mnt_opt_set_quoted_value(opt, raw); + if (raw) + freecon(raw); + + return rc != 0 ? -MNT_ERR_MOUNTOPT : 0; +} + +static int hook_prepare_options( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + int rc = 0, se_fix = 0, se_rem = 0; + struct libmnt_optlist *ol; + + assert(cxt); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -EINVAL; + + if (!is_selinux_enabled()) + /* Always remove SELinux garbage if SELinux disabled */ + se_rem = 1; + else if (mnt_optlist_is_remount(ol)) + /* + * Linux kernel < 2.6.39 does not support remount operation + * with any selinux specific mount options. + * + * Kernel 2.6.39 commits: ff36fe2c845cab2102e4826c1ffa0a6ebf487c65 + * 026eb167ae77244458fa4b4b9fc171209c079ba7 + * fix this odd behavior, so we don't have to care about it in + * userspace. + */ + se_rem = get_linux_version() < KERNEL_VERSION(2, 6, 39); + else + /* For normal mount, contexts are translated */ + se_fix = 1; + + DBG(HOOK, ul_debugobj(hs, " SELinux fix options")); + + /* Fix SELinux contexts */ + if (se_rem || se_fix) { + static const char *const selinux_options[] = { + "context", + "fscontext", + "defcontext", + "rootcontext", + "seclabel", + NULL + }; + struct libmnt_iter itr; + struct libmnt_opt *opt; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) { + const char *opt_name = mnt_opt_get_name(opt); + + if (!is_option(opt_name, selinux_options)) + continue; + if (se_rem) + rc = mnt_optlist_remove_opt(ol, opt); + else if (se_fix && mnt_opt_has_value(opt)) { + const char *val = mnt_opt_get_value(opt); + char *raw = NULL; + + /* @target placeholder is replaced later when target + * is already avalable. The mountpoint does not have to exist + * yet (for example "-o X-mount.mkdir=" or --target-prefix). + */ + if (strcmp(opt_name, "rootcontext") == 0 && + strcmp(val, "@target") == 0) { + rc = mnt_context_insert_hook(cxt, "__mkdir", + hs, MNT_STAGE_PREP_TARGET, NULL, + hook_selinux_target); + continue; + } else { + rc = selinux_trans_to_raw_context(val, &raw); + if (rc == -1 || !raw) + rc = -EINVAL; + } + if (!rc) { + DBG(HOOK, ul_debugobj(hs, " %s: %s to %s", + opt_name, val, raw)); + rc = mnt_opt_set_quoted_value(opt, raw); + } + if (raw) + freecon(raw); + + /* temporary for broken fsconfig() syscall */ + cxt->has_selinux_opt = 1; + } + if (rc) + break; + } + } + + return rc != 0 ? -MNT_ERR_MOUNTOPT : 0; +} + +const struct libmnt_hookset hookset_selinux = +{ + .name = "__selinux", + + .firststage = MNT_STAGE_PREP_OPTIONS, + .firstcall = hook_prepare_options, + + .deinit = hookset_deinit +}; + +#endif /* HAVE_LIBSELINUX */ diff --git a/libmount/src/hook_subdir.c b/libmount/src/hook_subdir.c new file mode 100644 index 0000000..7da563b --- /dev/null +++ b/libmount/src/hook_subdir.c @@ -0,0 +1,389 @@ +/* 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. + * + * + * This is X-mount.subdir= implementation. The code uses global hookset data + * rather than per-callback (hook) data. + * + * Please, see the comment in libmount/src/hooks.c to understand how hooks work. + */ +#include <sched.h> + +#include "mountP.h" +#include "fileutils.h" +#include "mount-api-utils.h" + +struct hookset_data { + char *subdir; + char *org_target; + int old_ns_fd; + int new_ns_fd; + unsigned int tmp_umounted : 1; +}; + +static int tmptgt_cleanup(struct hookset_data *); + +static void free_hookset_data( struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hsd = mnt_context_get_hookset_data(cxt, hs); + + if (!hsd) + return; + if (hsd->old_ns_fd >= 0) + tmptgt_cleanup(hsd); + + free(hsd->org_target); + free(hsd->subdir); + free(hsd); + + mnt_context_set_hookset_data(cxt, hs, NULL); +} + +/* global data, used by all callbacks */ +static struct hookset_data *new_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hsd = calloc(1, sizeof(struct hookset_data)); + + if (hsd && mnt_context_set_hookset_data(cxt, hs, hsd) != 0) { + /* probably ENOMEM problem */ + free(hsd); + hsd = NULL; + } + return hsd; +} + +/* de-initiallize this module */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks */ + while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0); + + /* free and remove global hookset data */ + free_hookset_data(cxt, hs); + + return 0; +} + +/* + * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and + * mark (bind mount) the directory as private. + */ +static int tmptgt_unshare(struct hookset_data *hsd) +{ +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + int rc = 0; + + hsd->old_ns_fd = hsd->new_ns_fd = -1; + + /* create directory */ + rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU); + if (rc) + goto fail; + + /* remember the current namespace */ + hsd->old_ns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (hsd->old_ns_fd < 0) + goto fail; + + /* create new namespace */ + if (unshare(CLONE_NEWNS) != 0) + goto fail; + + /* try to set top-level directory as private, this is possible if + * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */ + if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) { + + /* failed; create a mountpoint from MNT_PATH_TMPTGT */ + if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0) + goto fail; + if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0) + goto fail; + } + + /* remember the new namespace */ + hsd->new_ns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (hsd->new_ns_fd < 0) + goto fail; + + DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared")); + return 0; +fail: + if (rc == 0) + rc = errno ? -errno : -EINVAL; + + tmptgt_cleanup(hsd); + DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed")); + return rc; +#else + return -ENOSYS; +#endif +} + +/* + * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace + */ +static int tmptgt_cleanup(struct hookset_data *hsd) +{ +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + if (!hsd->tmp_umounted) { + umount(MNT_PATH_TMPTGT); + hsd->tmp_umounted = 1; + } + + if (hsd->new_ns_fd >= 0) + close(hsd->new_ns_fd); + + if (hsd->old_ns_fd >= 0) { + setns(hsd->old_ns_fd, CLONE_NEWNS); + close(hsd->old_ns_fd); + } + + hsd->new_ns_fd = hsd->old_ns_fd = -1; + DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done")); + return 0; +#else + return -ENOSYS; +#endif +} + +/* + * Attach (move) MNT_PATH_TMPTGT/subdir to the parental namespace. + */ +static int do_mount_subdir( + struct libmnt_context *cxt, + struct hookset_data *hsd, + const char *root, + const char *target) +{ + int rc = 0; + const char *subdir = hsd->subdir; + +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + struct libmnt_sysapi *api; + + api = mnt_context_get_sysapi(cxt); + if (api) { + /* FD based way - unfortunately, it's impossible to open + * sub-directory on not-yet attached mount. It means + * hook_mount.c attaches FS to temporary directory, and we + * clone and move the subdir, and umount the old unshared + * temporary tree. + * + * The old mount(2) way does the same, but by BIND. + */ + int fd; + + DBG(HOOK, ul_debug("attach subdir %s", subdir)); + fd = open_tree(api->fd_tree, subdir, + OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); + set_syscall_status(cxt, "open_tree", fd >= 0); + if (fd < 0) + rc = -errno; + + if (!rc) { + /* Note that the original parental namespace could be + * private, in this case, it will not see our final mount, + * so we need to move the the orignal namespace. + */ + setns(hsd->old_ns_fd, CLONE_NEWNS); + + rc = move_mount(fd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + set_syscall_status(cxt, "move_mount", rc == 0); + if (rc) + rc = -errno; + + /* And move back to our private namespace to cleanup */ + setns(hsd->new_ns_fd, CLONE_NEWNS); + } + if (!rc) { + close(api->fd_tree); + api->fd_tree = fd; + } + } else +#endif + { + char *src = NULL; + + if (asprintf(&src, "%s/%s", root, subdir) < 0) + return -ENOMEM; + + /* Classic mount(2) based way */ + DBG(HOOK, ul_debug("mount subdir %s to %s", src, target)); + rc = mount(src, target, NULL, MS_BIND, NULL); + + set_syscall_status(cxt, "mount", rc == 0); + if (rc) + rc = -errno; + free(src); + } + + if (!rc) { + DBG(HOOK, ul_debug("umount old root %s", root)); + rc = umount(root); + set_syscall_status(cxt, "umount", rc == 0); + if (rc) + rc = -errno; + hsd->tmp_umounted = 1; + + } + + return rc; +} + + +static int hook_mount_post( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct hookset_data *hsd; + int rc = 0; + + hsd = mnt_context_get_hookset_data(cxt, hs); + if (!hsd || !hsd->subdir) + return 0; + + /* reset to the original mountpoint */ + mnt_fs_set_target(cxt->fs, hsd->org_target); + + /* bind subdir to the real target, umount temporary target */ + rc = do_mount_subdir(cxt, hsd, + MNT_PATH_TMPTGT, + mnt_fs_get_target(cxt->fs)); + if (rc) + return rc; + + tmptgt_cleanup(hsd); + + return rc; +} + +static int hook_mount_pre( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct hookset_data *hsd; + int rc = 0; + + hsd = mnt_context_get_hookset_data(cxt, hs); + if (!hsd) + return 0; + + /* create unhared temporary target */ + hsd->org_target = strdup(mnt_fs_get_target(cxt->fs)); + if (!hsd->org_target) + rc = -ENOMEM; + if (!rc) + rc = tmptgt_unshare(hsd); + if (!rc) + mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT); + if (!rc) + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + NULL, hook_mount_post); + + DBG(HOOK, ul_debugobj(hs, "unshared tmp target %s [rc=%d]", + MNT_PATH_TMPTGT, rc)); + return rc; +} + + + +static int is_subdir_required(struct libmnt_context *cxt, int *rc, char **subdir) +{ + struct libmnt_optlist *ol; + struct libmnt_opt *opt; + const char *dir = NULL; + + assert(cxt); + assert(rc); + + *rc = 0; + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + opt = mnt_optlist_get_named(ol, "X-mount.subdir", cxt->map_userspace); + if (!opt) + return 0; + + dir = mnt_opt_get_value(opt); + + if (dir && *dir == '"') + dir++; + + if (!dir || !*dir) { + DBG(HOOK, ul_debug("failed to parse X-mount.subdir '%s'", dir)); + *rc = -MNT_ERR_MOUNTOPT; + } else { + *subdir = strdup(dir); + if (!*subdir) + *rc = -ENOMEM; + } + + return *rc == 0; +} + +/* this is the initial callback used to check mount options and define next + * actions if necessary */ +static int hook_prepare_target( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + const char *tgt; + char *subdir = NULL; + int rc = 0; + + assert(cxt); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + + if (cxt->action == MNT_ACT_MOUNT + && is_subdir_required(cxt, &rc, &subdir)) { + + /* create a global data */ + struct hookset_data *hsd = new_hookset_data(cxt, hs); + + if (!hsd) { + free(subdir); + return -ENOMEM; + } + hsd->subdir = subdir; + + DBG(HOOK, ul_debugobj(hs, "subdir %s wanted", subdir)); + + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_PRE, + NULL, hook_mount_pre); + } + + return rc; +} + +const struct libmnt_hookset hookset_subdir = +{ + .name = "__subdir", + + .firststage = MNT_STAGE_PREP_TARGET, + .firstcall = hook_prepare_target, + + .deinit = hookset_deinit +}; diff --git a/libmount/src/hook_veritydev.c b/libmount/src/hook_veritydev.c new file mode 100644 index 0000000..f91778a --- /dev/null +++ b/libmount/src/hook_veritydev.c @@ -0,0 +1,646 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2019 Microsoft Corporation + * 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. + * + * + * Please, see comment in libmount/src/hooks.c to understand how hooks work. + */ +#include "mountP.h" + +#ifdef HAVE_CRYPTSETUP + +#include <libcryptsetup.h> +#include "path.h" +#include "strutils.h" +#include "fileutils.h" +#include "pathnames.h" + +#ifdef CRYPTSETUP_VIA_DLOPEN +# include <dlfcn.h> + +/* Pointers to libcryptsetup functions (initiliazed by dlsym()) */ +struct verity_opers { + void (*crypt_set_debug_level)(int); + void (*crypt_set_log_callback)(struct crypt_device *, void (*log)(int, const char *, void *), void *); + int (*crypt_init_data_device)(struct crypt_device **, const char *, const char *); + int (*crypt_load)(struct crypt_device *, const char *, void *); + int (*crypt_get_volume_key_size)(struct crypt_device *); +# ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + int (*crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t); +# endif + int (*crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t); + void (*crypt_free)(struct crypt_device *); + int (*crypt_init_by_name)(struct crypt_device **, const char *); + int (*crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *); + int (*crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t); + + int (*crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t); +}; + +/* libcryptsetup functions names and offsets in 'struct verity_opers' */ +struct verity_sym { + const char *name; + size_t offset; /* offset of the symbol in verity_opers */ +}; + +# define DEF_VERITY_SYM(_name) \ + { \ + .name = # _name, \ + .offset = offsetof(struct verity_opers, _name), \ + } + +/* All required symbols */ +static const struct verity_sym verity_symbols[] = +{ + DEF_VERITY_SYM( crypt_set_debug_level ), + DEF_VERITY_SYM( crypt_set_log_callback ), + DEF_VERITY_SYM( crypt_init_data_device ), + DEF_VERITY_SYM( crypt_load ), + DEF_VERITY_SYM( crypt_get_volume_key_size ), +# ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + DEF_VERITY_SYM( crypt_activate_by_signed_key ), +# endif + DEF_VERITY_SYM( crypt_activate_by_volume_key ), + DEF_VERITY_SYM( crypt_free ), + DEF_VERITY_SYM( crypt_init_by_name ), + DEF_VERITY_SYM( crypt_get_verity_info ), + DEF_VERITY_SYM( crypt_volume_key_get ), + + DEF_VERITY_SYM( crypt_deactivate_by_name ), +}; +#endif /* CRYPTSETUP_VIA_DLOPEN */ + + +/* Data used by all verity hooks */ +struct hookset_data { + char *devname; /* the device */ +#ifdef CRYPTSETUP_VIA_DLOPEN + void *dl; /* dlopen() */ + struct verity_opers dl_funcs; /* dlsym() */ +#endif +}; + +/* libcryptsetup call -- dlopen version requires 'struct hookset_data *hsd' */ +#ifdef CRYPTSETUP_VIA_DLOPEN +# define verity_call(_func) (hsd->dl_funcs._func) +#else +# define verity_call(_func) (_func) +#endif + + +static void delete_veritydev(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + struct hookset_data *hsd); + + +#ifdef CRYPTSETUP_VIA_DLOPEN +static int load_libcryptsetup_symbols(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + struct hookset_data *hsd) +{ + size_t i; + int flags = RTLD_LAZY | RTLD_LOCAL; + + assert(cxt); + assert(hsd); + assert(hsd->dl == NULL); + + /* glibc extension: mnt_context_deferred_delete_veritydev is called immediately after, don't unload on dl_close */ +#ifdef RTLD_NODELETE + flags |= RTLD_NODELETE; +#endif + /* glibc extension: might help to avoid further symbols clashes */ +#ifdef RTLD_DEEPBIND + flags |= RTLD_DEEPBIND; +#endif + hsd->dl = dlopen("libcryptsetup.so.12", flags); + if (!hsd->dl) { + DBG(HOOK, ul_debugobj(hs, "cannot dlopen libcryptsetup")); + return -ENOTSUP; + } + + /* clear errors first, then load all the libcryptsetup symbols */ + dlerror(); + + /* dlsym() */ + for (i = 0; i < ARRAY_SIZE(verity_symbols); i++) { + char *errmsg; + const struct verity_sym *def = &verity_symbols[i]; + void **sym; + + sym = (void **) ((char *) (&hsd->dl_funcs) + def->offset); + *sym = dlsym(hsd->dl, def->name); + + errmsg = dlerror(); + if (errmsg) { + DBG(HOOK, ul_debugobj(hs, "dlsym failed %s: %s", def->name, errmsg)); + return -ENOTSUP; + } + } + return 0; +} +#endif + +/* libcryptsetup callback */ +static void libcryptsetup_log(int level __attribute__((__unused__)), + const char *msg, void *data) +{ + const struct libmnt_hookset *hs = (struct libmnt_hookset *) data; + DBG(HOOK, ul_debugobj(hs, "cryptsetup: %s", msg)); +} + +/* free global data */ +static void free_hookset_data( struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hsd = mnt_context_get_hookset_data(cxt, hs); + + if (!hsd) + return; + if (hsd->devname) + delete_veritydev(cxt, hs, hsd); +#ifdef CRYPTSETUP_VIA_DLOPEN + if (hsd->dl) + dlclose(hsd->dl); +#endif + free(hsd); + mnt_context_set_hookset_data(cxt, hs, NULL); +} + +/* global data, used by all callbacks */ +static struct hookset_data *new_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hsd = calloc(1, sizeof(struct hookset_data)); + + if (hsd && mnt_context_set_hookset_data(cxt, hs, hsd) != 0) + goto failed; + +#ifdef CRYPTSETUP_VIA_DLOPEN + if (load_libcryptsetup_symbols(cxt, hs, hsd) != 0) + goto failed; +#endif + if (mnt_context_is_verbose(cxt)) + verity_call( crypt_set_debug_level(CRYPT_DEBUG_ALL) ); + + verity_call( crypt_set_log_callback(NULL, libcryptsetup_log, (void *) hs) ); + + return hsd; +failed: + free(hsd); + return NULL; +} + +/* libmount callback -- cleanup all */ +static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs) +{ + DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name)); + + /* remove all our hooks */ + while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0); + + /* free and remove global hookset data */ + free_hookset_data(cxt, hs); + + return 0; +} + +/* check mount options for verity stuff */ +static int is_veritydev_required(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + struct libmnt_optlist *ol) +{ + const char *src; + unsigned long flags = 0; + + assert(cxt); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (cxt->action != MNT_ACT_MOUNT) + return 0; + if (!cxt->fs) + return 0; + src = mnt_fs_get_srcpath(cxt->fs); + if (!src) + return 0; /* backing file not set */ + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return 0; + if (mnt_optlist_is_bind(ol) + || mnt_optlist_is_move(ol) + || mnt_context_propagation_only(cxt)) + return 0; + + if (mnt_context_get_user_mflags(cxt, &flags)) + return 0; + + if (flags & (MNT_MS_HASH_DEVICE | MNT_MS_ROOT_HASH | MNT_MS_HASH_OFFSET)) { + DBG(HOOK, ul_debugobj(hs, "verity options detected")); + return 1; + } + + return 0; +} + +static void delete_veritydev(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + struct hookset_data *hsd) +{ + uint32_t flags = 0; + int rc; + + if (!hsd || !hsd->devname) + return; + + if (mnt_context_get_status(cxt) != 0) + /* + * mount(2) success, use deferred deactivation + */ + flags |= CRYPT_DEACTIVATE_DEFERRED; + + rc = verity_call( crypt_deactivate_by_name(NULL, hsd->devname, flags) ); + + DBG(HOOK, ul_debugobj(hs, "deleted %s [rc=%d%s]", + hsd->devname, rc, + flags & CRYPT_DEACTIVATE_DEFERRED ? " deferred" : "" )); + if (rc == 0) { + free(hsd->devname); + hsd->devname = NULL; + } + + return; +} + +/* Taken from https://gitlab.com/cryptsetup/cryptsetup/blob/master/lib/utils_crypt.c#L225 */ +static size_t crypt_hex_to_bytes(const char *hex, char **result) +{ + char buf[3] = "xx\0", *endp, *bytes; + size_t i, len; + + len = strlen(hex); + if (len % 2) + return -EINVAL; + len /= 2; + + bytes = malloc(len); + if (!bytes) + return -ENOMEM; + + for (i = 0; i < len; i++) { + memcpy(buf, &hex[i * 2], 2); + errno = 0; + bytes[i] = strtoul(buf, &endp, 16); + if (errno || endp != &buf[2]) { + free(bytes); + return -EINVAL; + } + } + *result = bytes; + return i; +} + + +static int setup_veritydev( struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + struct hookset_data *hsd, + struct libmnt_optlist *ol) +{ + struct libmnt_opt *opt; + const char *backing_file, + *hash_device = NULL, + *root_hash_file = NULL, + *fec_device = NULL, + *root_hash_sig_file = NULL; + char *key = NULL, + *root_hash_binary = NULL, + *mapper_device = NULL, + *root_hash = NULL, + *hash_sig = NULL; + + size_t hash_size, hash_sig_size = 0, keysize = 0; + struct crypt_params_verity crypt_params = {}; + struct crypt_device *crypt_dev = NULL; + + int rc = 0; + /* Use the same default for FEC parity bytes as cryptsetup uses */ + uint64_t offset = 0, fec_offset = 0, fec_roots = 2; + uint32_t crypt_activate_flags = CRYPT_ACTIVATE_READONLY; + + struct stat hash_sig_st; + + assert(cxt); + assert(cxt->fs); + assert(hsd); + assert(hsd->devname == NULL); + + /* dm-verity volumes are read-only, and mount will fail if not set */ + mnt_optlist_append_flags(ol, MS_RDONLY, cxt->map_linux); + + backing_file = mnt_fs_get_srcpath(cxt->fs); + if (!backing_file) + return -EINVAL; + else { + /* To avoid clashes, prefix libmnt_ to all mapper devices */ + char *p, *path = strdup(backing_file); + if (!path) + return -ENOMEM; + + p = stripoff_last_component(path); + if (p) + mapper_device = calloc(strlen(p) + sizeof("libmnt_"), sizeof(char)); + if (mapper_device) { + strcat(mapper_device, "libmnt_"); + strcat(mapper_device, p); + } + free(path); + if (!mapper_device) + return -ENOMEM; + } + + DBG(HOOK, ul_debugobj(hs, "verity: setup for %s [%s]", backing_file, mapper_device)); + + /* verity.hashdevice= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_HASH_DEVICE, cxt->map_userspace))) + hash_device = mnt_opt_get_value(opt); + + /* verity.roothash= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_ROOT_HASH, cxt->map_userspace))) { + root_hash = strdup(mnt_opt_get_value(opt)); + rc = root_hash ? 0 : -ENOMEM; + } + + /* verity.hashoffset= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_HASH_OFFSET, cxt->map_userspace)) + && mnt_opt_has_value(opt)) { + if (strtosize(mnt_opt_get_value(opt), &offset)) { + DBG(HOOK, ul_debugobj(hs, "failed to parse verity.hashoffset=")); + rc = -MNT_ERR_MOUNTOPT; + } + } + + /* verity.roothashfile= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_ROOT_HASH_FILE, cxt->map_userspace))) + root_hash_file = mnt_opt_get_value(opt); + + /* verity.fecdevice= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_FEC_DEVICE, cxt->map_userspace))) + fec_device = mnt_opt_get_value(opt); + + /* verity.fecoffset= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_FEC_OFFSET, cxt->map_userspace)) + && mnt_opt_has_value(opt) + && strtosize(mnt_opt_get_value(opt), &fec_offset)) { + DBG(HOOK, ul_debugobj(hs, "failed to parse verity.fecoffset=")); + rc = -MNT_ERR_MOUNTOPT; + } + + /* verity.fecroots= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_FEC_ROOTS, cxt->map_userspace)) + && mnt_opt_has_value(opt) + && strtosize(mnt_opt_get_value(opt), &fec_roots)) { + DBG(HOOK, ul_debugobj(hs, "failed to parse verity.fecroots=")); + rc = -MNT_ERR_MOUNTOPT; + } + + /* verity.roothashsig= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_ROOT_HASH_SIG, cxt->map_userspace)) + && mnt_opt_has_value(opt)) { + root_hash_sig_file = mnt_opt_get_value(opt); + + DBG(HOOK, ul_debugobj(hs, "verity: checking %s", root_hash_sig_file)); + + rc = ul_path_stat(NULL, &hash_sig_st, 0, root_hash_sig_file); + if (rc == 0) + rc = S_ISREG(hash_sig_st.st_mode) && hash_sig_st.st_size ? 0 : -EINVAL; + if (rc == 0) { + hash_sig_size = hash_sig_st.st_size; + hash_sig = malloc(hash_sig_size); + rc = hash_sig ? 0 : -ENOMEM; + } + if (rc == 0) { + rc = ul_path_read(NULL, hash_sig, hash_sig_size, root_hash_sig_file); + rc = rc < (int)hash_sig_size ? -1 : 0; + } + } + + /* verity.oncorruption= */ + if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_VERITY_ON_CORRUPTION, cxt->map_userspace)) + && mnt_opt_has_value(opt)) { + const char *val = mnt_opt_get_value(opt); + if (!strcmp(val, "ignore")) + crypt_activate_flags |= CRYPT_ACTIVATE_IGNORE_CORRUPTION; + else if (!strcmp(val, "restart")) + crypt_activate_flags |= CRYPT_ACTIVATE_RESTART_ON_CORRUPTION; + else if (!strcmp(val, "panic")) + /* Added by libcryptsetup v2.3.4 - ignore on lower versions, as with other optional features */ +#ifdef CRYPT_ACTIVATE_PANIC_ON_CORRUPTION + crypt_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION; +#else + DBG(HOOK, ul_debugobj(hs, "verity.oncorruption=panic not supported by libcryptsetup, ignoring")); +#endif + else { + DBG(HOOK, ul_debugobj(hs, "failed to parse verity.oncorruption=")); + rc = -MNT_ERR_MOUNTOPT; + } + } + + if (!rc && root_hash && root_hash_file) { + DBG(HOOK, ul_debugobj(hs, "verity.roothash and verity.roothashfile are mutually exclusive")); + rc = -EINVAL; + } else if (!rc && root_hash_file) { + rc = ul_path_read_string(NULL, &root_hash, root_hash_file); + rc = rc < 1 ? rc : 0; + } + + if (!rc && (!hash_device || !root_hash)) { + DBG(HOOK, ul_debugobj(hs, "verity.hashdevice and one of verity.roothash or verity.roothashfile are mandatory")); + rc = -EINVAL; + } + + if (!rc) + rc = verity_call( crypt_init_data_device(&crypt_dev, hash_device, backing_file) ); + if (rc) + goto done; + + memset(&crypt_params, 0, sizeof(struct crypt_params_verity)); + crypt_params.hash_area_offset = offset; + crypt_params.fec_area_offset = fec_offset; + crypt_params.fec_roots = fec_roots; + crypt_params.fec_device = fec_device; + crypt_params.flags = 0; + + rc = verity_call( crypt_load(crypt_dev, CRYPT_VERITY, &crypt_params) ); + if (rc < 0) + goto done; + + hash_size = verity_call( crypt_get_volume_key_size(crypt_dev) ); + if (crypt_hex_to_bytes(root_hash, &root_hash_binary) != hash_size) { + DBG(HOOK, ul_debugobj(hs, "root hash %s is not of length %zu", root_hash, hash_size)); + rc = -EINVAL; + goto done; + } + + if (hash_sig) { +#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + rc = verity_call( crypt_activate_by_signed_key(crypt_dev, mapper_device, root_hash_binary, hash_size, + hash_sig, hash_sig_size, crypt_activate_flags) ); +#else + rc = -EINVAL; + DBG(HOOK, ul_debugobj(hs, "verity.roothashsig=%s passed but libcryptsetup does not provide crypt_activate_by_signed_key()", hash_sig)); +#endif + } else + rc = verity_call( crypt_activate_by_volume_key(crypt_dev, mapper_device, root_hash_binary, hash_size, + crypt_activate_flags) ); + + /* + * If the mapper device already exists, and if libcryptsetup supports it, get the root + * hash associated with the existing one and compare it with the parameter passed by + * the user. If they match, then we can be sure the user intended to mount the exact + * same device, and simply reuse it and return success. + * The kernel does the refcounting for us. + * If libcryptsetup does not support getting the root hash out of an existing device, + * then return an error and tell the user that the device is already in use. + * Pass through only OOM errors or mismatching root hash errors. + */ + if (rc == -EEXIST) { + DBG(HOOK, ul_debugobj(hs, "%s already in use as /dev/mapper/%s", backing_file, mapper_device)); + + verity_call( crypt_free(crypt_dev) ); + + rc = verity_call( crypt_init_by_name(&crypt_dev, mapper_device) ); + if (!rc) { + rc = verity_call( crypt_get_verity_info(crypt_dev, &crypt_params) ); + if (!rc) { + key = calloc(hash_size, 1); + if (!key) { + rc = -ENOMEM; + goto done; + } + } + if (!rc) { + keysize = hash_size; + rc = verity_call( crypt_volume_key_get(crypt_dev, CRYPT_ANY_SLOT, key, &keysize, NULL, 0) ); + } + if (!rc) { + DBG(HOOK, ul_debugobj(hs, "comparing root hash of existing device with %s", root_hash)); + if (memcmp(key, root_hash_binary, hash_size)) { + DBG(HOOK, ul_debugobj(hs, "existing device's hash does not match with %s", root_hash)); + rc = -EINVAL; + goto done; + } + } else { + DBG(HOOK, ul_debugobj(hs, "libcryptsetup does not support extracting root hash of existing device")); + } + } + if (rc) { + rc = -EEXIST; + } else { + /* + * Ensure that, if signatures are supported, we only reuse the device if the previous mount + * used the same settings, so that a previous unsigned mount will not be reused if the user + * asks to use signing for the new one, and viceversa. + */ +#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + if (!!hash_sig != !!(crypt_params.flags & CRYPT_VERITY_ROOT_HASH_SIGNATURE)) { + rc = -EINVAL; + DBG(HOOK, ul_debugobj(hs, "existing device and new mount have to either be both opened with signature or both without")); + goto done; + } +#endif + DBG(HOOK, ul_debugobj(hs, "root hash of %s matches %s, reusing device", mapper_device, root_hash)); + } + } + + if (!rc) { + hsd->devname = calloc(strlen(mapper_device) + + sizeof(_PATH_DEV_MAPPER) + 2, sizeof(char)); + if (!hsd->devname) + rc = -ENOMEM; + else { + strcat(hsd->devname, _PATH_DEV_MAPPER "/"); + strcat(hsd->devname, mapper_device); + rc = mnt_fs_set_source(cxt->fs, hsd->devname); + } + } + +done: + verity_call( crypt_free(crypt_dev) ); + + free(root_hash_binary); + free(mapper_device); + free(root_hash); + free(hash_sig); + free(key); + return rc; +} +/* call after mount(2) */ +static int hook_mount_post( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + + delete_veritydev(cxt, hs, mnt_context_get_hookset_data(cxt, hs)); + return 0; +} + +/* + * first call (first callback in this hookset) + */ +static int hook_prepare_source( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data __attribute__((__unused__))) +{ + struct libmnt_optlist *ol; + struct hookset_data *hsd; + int rc; + + assert(cxt); + + ol = mnt_context_get_optlist(cxt); + if (!ol) + return -ENOMEM; + + if (!is_veritydev_required(cxt, hs, ol)) + return 0; + + hsd = new_hookset_data(cxt, hs); + if (!hsd) + return -ENOMEM; + + rc = setup_veritydev(cxt, hs, hsd, ol); + if (!rc) { + rc = mnt_context_append_hook(cxt, hs, + MNT_STAGE_MOUNT_POST, + NULL, hook_mount_post); + if (rc) + delete_veritydev(cxt, hs, hsd); + } + return rc; +} + + +const struct libmnt_hookset hookset_veritydev = +{ + .name = "__veritydev", + + .firststage = MNT_STAGE_PREP_SOURCE, + .firstcall = hook_prepare_source, + + .deinit = hookset_deinit +}; +#endif /*HAVE_CRYPTSETUP*/ + + + diff --git a/libmount/src/hooks.c b/libmount/src/hooks.c new file mode 100644 index 0000000..dcfe69f --- /dev/null +++ b/libmount/src/hooks.c @@ -0,0 +1,404 @@ +/* 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 "hookset" is a set of callbacks (hooks) that implement some functionality. + * The library defines stages where hooks are called (e.g. when preparing source, post + * mount(2), etc.). An arbitrary hook can, on the fly, define another hook for the + * arbitrary stage. The first hook from the hookset which goes to the game is a + * "firstcall" (defined in struct libmnt_hookset). This first hook controls + * what will happen in the next stages (usually nothing). + * + * The library supports two kinds of data for hooksets: + * + * - global data; accessible for all callbacks. Makes sense for complex + * hooksets with more callbacks in more stages. Usually implemented by + * locally defined 'struct hookset_data' in hook_*.c. + * + * - per-hook data; acessible for specific callback + * Usually implemented by locally defined 'struct hook_data' in hook_*.c. + */ +#include "mountP.h" +#include "mount-api-utils.h" + +/* built-in hooksets */ +static const struct libmnt_hookset *hooksets[] = +{ +#ifdef __linux__ + &hookset_loopdev, +#ifdef HAVE_CRYPTSETUP + &hookset_veritydev, +#endif + &hookset_mkdir, +#ifdef HAVE_LIBSELINUX + &hookset_selinux, +#endif + &hookset_subdir, +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + &hookset_mount, +#endif + &hookset_mount_legacy, +#if defined(HAVE_MOUNTFD_API) && defined(HAVE_LINUX_MOUNT_H) + &hookset_idmap, +#endif + &hookset_owner +#endif +}; + +/* hooksets data (this is global list of hookset data) */ +struct hookset_data { + const struct libmnt_hookset *hookset; + void *data; + + struct list_head datas; +}; + +/* individial callback */ +struct hookset_hook { + const struct libmnt_hookset *hookset; + int stage; + void *data; + const char *after; + + int (*func)(struct libmnt_context *, const struct libmnt_hookset *, void *); + + struct list_head hooks; + unsigned int executed : 1; +}; + +static const char *stagenames[] = { + /* prepare */ + [MNT_STAGE_PREP_SOURCE] = "prep-source", + [MNT_STAGE_PREP_TARGET] = "prep-target", + [MNT_STAGE_PREP_OPTIONS] = "prep-options", + [MNT_STAGE_PREP] = "prep", + + /* mount */ + [MNT_STAGE_MOUNT_PRE] = "pre-mount", + [MNT_STAGE_MOUNT] = "mount", + [MNT_STAGE_MOUNT_POST] = "post-mount", + + /* post */ + [MNT_STAGE_POST] = "post", +}; + +static int call_depend_hooks(struct libmnt_context *cxt, const char *name, int stage); + + +int mnt_context_deinit_hooksets(struct libmnt_context *cxt) +{ + size_t i; + int rc = 0; + + assert(cxt); + + if (list_empty(&cxt->hooksets_datas) && + list_empty(&cxt->hooksets_hooks)) + return 0; + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + rc += hs->deinit(cxt, hs); + } + + assert(list_empty(&cxt->hooksets_datas)); + assert(list_empty(&cxt->hooksets_hooks)); + + INIT_LIST_HEAD(&cxt->hooksets_datas); + INIT_LIST_HEAD(&cxt->hooksets_hooks); + + return rc; +} + +const struct libmnt_hookset *mnt_context_get_hookset( + struct libmnt_context *cxt, const char *name) +{ + size_t i; + + assert(cxt); + assert(name); + + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + if (strcmp(name, hs->name) == 0) + return hs; + } + + return NULL; +} + +static struct hookset_data *get_hookset_data( + struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct list_head *p; + + assert(cxt); + assert(hs); + + list_for_each(p, &cxt->hooksets_datas) { + struct hookset_data *x = list_entry(p, struct hookset_data, datas); + + if (x->hookset == hs) + return x; + } + return 0; +} + +int mnt_context_set_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data) +{ + struct hookset_data *hd = NULL; + + hd = get_hookset_data(cxt, hs); + + /* deallocate old data */ + if (data == NULL) { + if (hd) { + DBG(CXT, ul_debugobj(cxt, " free '%s' data", hs->name)); + list_del(&hd->datas); + free(hd); + } + return 0; + } + + /* create and append new data */ + if (!hd) { + hd = calloc(1, sizeof(*hd)); + if (!hd) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, " alloc '%s' data", hs->name)); + INIT_LIST_HEAD(&hd->datas); + hd->hookset = hs; + list_add_tail(&hd->datas, &cxt->hooksets_datas); + + } + hd->data = data; + return 0; +} + +void *mnt_context_get_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs) +{ + struct hookset_data *hd = get_hookset_data(cxt, hs); + + return hd ? hd->data : NULL; +} + +static int append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *), + const char *after) +{ + struct hookset_hook *hook; + + assert(cxt); + assert(hs); + assert(stage); + + hook = calloc(1, sizeof(*hook)); + if (!hook) + return -ENOMEM; + + DBG(CXT, ul_debugobj(cxt, " appending %s hook from %s", + stagenames[stage], hs->name)); + + INIT_LIST_HEAD(&hook->hooks); + + hook->hookset = hs; + hook->data = data; + hook->func = func; + hook->stage = stage; + hook->after = after; + + list_add_tail(&hook->hooks, &cxt->hooksets_hooks); + return 0; +} + +int mnt_context_append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)) +{ + return append_hook(cxt, hs, stage, data, func, NULL); +} + +int mnt_context_insert_hook(struct libmnt_context *cxt, + const char *after, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)) +{ + return append_hook(cxt, hs, stage, data, func, after); +} + +static struct hookset_hook *get_hookset_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data) +{ + struct list_head *p, *next; + + assert(cxt); + + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (hs && x->hookset != hs) + continue; + if (stage && x->stage != stage) + continue; + if (data && x->data != data) + continue; + return x; + } + + return NULL; +} + +int mnt_context_remove_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void **data) +{ + struct hookset_hook *hook; + + assert(cxt); + + hook = get_hookset_hook(cxt, hs, stage, NULL); + if (hook) { + DBG(CXT, ul_debugobj(cxt, " removing %s hook from %s", + stagenames[hook->stage], hook->hookset->name)); + + if (data) + *data = hook->data; + + list_del(&hook->hooks); + free(hook); + return 0; + } + + return 1; +} + +int mnt_context_has_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data) +{ + return get_hookset_hook(cxt, hs, stage, data) ? 1 : 0; +} + +static int call_hook(struct libmnt_context *cxt, struct hookset_hook *hook) +{ + int rc = 0; + + if (mnt_context_is_fake(cxt)) + DBG(CXT, ul_debugobj(cxt, " FAKE call")); + else + rc = hook->func(cxt, hook->hookset, hook->data); + + hook->executed = 1; + if (!rc) + rc = call_depend_hooks(cxt, hook->hookset->name, hook->stage); + return rc; +} + +static int call_depend_hooks(struct libmnt_context *cxt, const char *name, int stage) +{ + struct list_head *p = NULL, *next = NULL; + int rc = 0; + + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage || x->executed || + x->after == NULL || strcmp(x->after, name) != 0) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s [after]", x->hookset->name)); + rc = call_hook(cxt, x); + if (rc) + break; + } + + return rc; +} + +int mnt_context_call_hooks(struct libmnt_context *cxt, int stage) +{ + struct list_head *p = NULL, *next = NULL; + size_t i; + int rc = 0; + + DBG(CXT, ul_debugobj(cxt, "---> stage:%s", stagenames[stage])); + + /* call initial hooks */ + for (i = 0; i < ARRAY_SIZE(hooksets); i++) { + const struct libmnt_hookset *hs = hooksets[i]; + + if (hs->firststage != stage) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s [first]", hs->name)); + + if (mnt_context_is_fake(cxt)) + DBG(CXT, ul_debugobj(cxt, " FAKE call")); + else + rc = hs->firstcall(cxt, hs, NULL); + if (!rc) + rc = call_depend_hooks(cxt, hs->name, stage); + if (rc < 0) + goto done; + } + + /* call already active hooks */ + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage || x->executed) + continue; + + DBG(CXT, ul_debugobj(cxt, "calling %s [active]", x->hookset->name)); + rc = call_hook(cxt, x); + if (rc < 0) + goto done; + } + +done: + /* zeroize status */ + p = next = NULL; + list_for_each_safe(p, next, &cxt->hooksets_hooks) { + struct hookset_hook *x = list_entry(p, struct hookset_hook, hooks); + + if (x->stage != stage) + continue; + x->executed = 0; + } + + DBG(CXT, ul_debugobj(cxt, "<--- stage:%s [rc=%d status=%d]", + stagenames[stage], rc, cxt->syscall_status)); + return rc; +} diff --git a/libmount/src/init.c b/libmount/src/init.c new file mode 100644 index 0000000..869be50 --- /dev/null +++ b/libmount/src/init.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 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. + */ + +/** + * SECTION: init + * @title: Library initialization + * @short_description: initialize debugging + */ + +#include <stdarg.h> + +#include "mountP.h" + +UL_DEBUG_DEFINE_MASK(libmount); +UL_DEBUG_DEFINE_MASKNAMES(libmount) = +{ + { "all", MNT_DEBUG_ALL, "info about all subsystems" }, + { "cache", MNT_DEBUG_CACHE, "paths and tags cache" }, + { "cxt", MNT_DEBUG_CXT, "library context (handler)" }, + { "diff", MNT_DEBUG_DIFF, "mountinfo changes tracking" }, + { "fs", MNT_DEBUG_FS, "FS abstraction" }, + { "help", MNT_DEBUG_HELP, "this help" }, + { "hook", MNT_DEBUG_HOOK, "hooks functionality" }, + { "locks", MNT_DEBUG_LOCKS, "mtab and utab locking" }, + { "loop", MNT_DEBUG_LOOP, "loop devices routines" }, + { "options", MNT_DEBUG_OPTIONS, "mount options parsing" }, + { "optlist", MNT_DEBUG_OPTLIST, "mount options container" }, + { "tab", MNT_DEBUG_TAB, "fstab, mtab, mountinfo routines" }, + { "update", MNT_DEBUG_UPDATE, "mtab, utab updates" }, + { "utils", MNT_DEBUG_UTILS, "misc library utils" }, + { "monitor", MNT_DEBUG_MONITOR, "mount tables monitor" }, + { "btrfs", MNT_DEBUG_BTRFS, "btrfs specific routines" }, + { "verity", MNT_DEBUG_VERITY, "verity specific routines" }, + + { NULL, 0 } +}; + +/** + * mnt_init_debug: + * @mask: debug mask (0xffff to enable full debugging) + * + * If the @mask is not specified, then this function reads + * the LIBMOUNT_DEBUG environment variable to get the mask. + * + * Already initialized debugging stuff cannot be changed. Calling + * this function twice has no effect. + */ +void mnt_init_debug(int mask) +{ + if (libmount_debug_mask) + return; + + __UL_INIT_DEBUG_FROM_ENV(libmount, MNT_DEBUG_, mask, LIBMOUNT_DEBUG); + + if (libmount_debug_mask != MNT_DEBUG_INIT + && libmount_debug_mask != (MNT_DEBUG_HELP|MNT_DEBUG_INIT)) { + const char *ver = NULL; + const char **features = NULL, **p; + + mnt_get_library_version(&ver); + mnt_get_library_features(&features); + + DBG(INIT, ul_debug("library debug mask: 0x%06x", libmount_debug_mask)); + DBG(INIT, ul_debug("library version: %s", ver)); + p = features; + while (p && *p) + DBG(INIT, ul_debug(" feature: %s", *p++)); + } + + ON_DBG(HELP, ul_debug_print_masks("LIBMOUNT_DEBUG", + UL_DEBUG_MASKNAMES(libmount))); +} + +#ifdef TEST_PROGRAM + +#include <errno.h> +#include <stdlib.h> +int main(int argc, char *argv[]) +{ + if (argc == 2) { + int mask; + + errno = 0; + mask = strtoul(argv[1], 0, 0); + + if (errno) + return 1; + + mnt_init_debug(mask); + } + else if (argc == 1) { + mnt_init_debug(0); + } else + return 1; + + return 0; +} +#endif /* TEST_PROGRAM */ + diff --git a/libmount/src/iter.c b/libmount/src/iter.c new file mode 100644 index 0000000..891e2c8 --- /dev/null +++ b/libmount/src/iter.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2009-2018 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. + */ + +/** + * SECTION: iter + * @title: Iterator + * @short_description: unified iterator + * + * The iterator keeps the direction and the last position + * for access to the internal library tables/lists. + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "mountP.h" + +/** + * mnt_new_iter: + * @direction: MNT_INTER_{FOR,BACK}WARD direction + * + * Returns: newly allocated generic libmount iterator. + */ +struct libmnt_iter *mnt_new_iter(int direction) +{ + struct libmnt_iter *itr = calloc(1, sizeof(*itr)); + if (!itr) + return NULL; + itr->direction = direction; + return itr; +} + +/** + * mnt_free_iter: + * @itr: iterator pointer + * + * Deallocates the iterator. + */ +void mnt_free_iter(struct libmnt_iter *itr) +{ + free(itr); +} + +/** + * mnt_reset_iter: + * @itr: iterator pointer + * @direction: MNT_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged + * + * Resets the iterator. + */ +void mnt_reset_iter(struct libmnt_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +/** + * mnt_iter_get_direction: + * @itr: iterator pointer + * + * Returns: MNT_INTER_{FOR,BACK}WARD + */ +int mnt_iter_get_direction(struct libmnt_iter *itr) +{ + return itr->direction; +} diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in new file mode 100644 index 0000000..06c2704 --- /dev/null +++ b/libmount/src/libmount.h.in @@ -0,0 +1,1053 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * libmount.h - libmount API + * + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com> + * + * This library 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _LIBMOUNT_MOUNT_H +#define _LIBMOUNT_MOUNT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <mntent.h> +#include <sys/types.h> + +/* Make sure libc MS_* definitions are used by default. Note that MS_* flags + * may be already defined by linux/fs.h or another file -- in this case we + * don't want to include sys/mount.h at all to avoid collisions. + */ +#if defined(__linux__) && !defined(MS_RDONLY) +# include <sys/mount.h> +#endif + +#define LIBMOUNT_VERSION "@LIBMOUNT_VERSION@" +#define LIBMOUNT_MAJOR_VERSION @LIBMOUNT_MAJOR_VERSION@ +#define LIBMOUNT_MINOR_VERSION @LIBMOUNT_MINOR_VERSION@ +#define LIBMOUNT_PATCH_VERSION @LIBMOUNT_PATCH_VERSION@ + +/** + * libmnt_cache: + * + * Stores canonicalized paths and evaluated tags + */ +struct libmnt_cache; + +/** + * libmnt_lock: + * + * Stores information about the locked file + */ +struct libmnt_lock; + +/** + * libmnt_iter: + * + * Generic iterator (stores state about lists) + */ +struct libmnt_iter; + +/** + * libmnt_optmap: + * @name: option name[=type] where type is printf-like type specifier") + * @id: option ID or MS_* flags (e.g MS_RDONLY) + * @mask: MNT_{NOMTAB,INVERT,...} mask + * + * Mount options description (map) + */ +struct libmnt_optmap +{ + const char *name; + int id; + int mask; +}; + +/* + * mount options map masks + */ +#define MNT_INVERT (1 << 1) /* invert the mountflag */ +#define MNT_NOMTAB (1 << 2) /* skip in the mtab option string */ +#define MNT_PREFIX (1 << 3) /* prefix used for some options (e.g. "x-foo") */ +#define MNT_NOHLPS (1 << 4) /* don't add the option to mount.<type> helpers command line */ +#define MNT_NOFSTAB (1 << 5) /* not expected in fstab */ +#define MNT_SUPERBLOCK (1 << 6) /* MS_* for mount(2), otherwise requires fsconfig() */ + +/** + * libmnt_fs: + * + * Parsed fstab or mountinfo entry + */ +struct libmnt_fs; + +/** + * libmnt_table: + * + * List of struct libmnt_fs entries (parsed fstab or mountinfo) + */ +struct libmnt_table; + +/** + * libmnt_update + * + * utab update description + */ +struct libmnt_update; + +/** + * libmnt_context + * + * Mount/umount status + */ +struct libmnt_context; + +/** + * libmnt_monitor + * + * Mount tables monitor + */ +struct libmnt_monitor; + +/** + * libmnt_tabdiff: + * + * Stores mountinfo state + */ +struct libmnt_tabdiff; + +/** + * libmnt_ns: + * + * Describes mount namespace + */ +struct libmnt_ns; + +/* + * Actions + */ +enum { + MNT_ACT_MOUNT = 1, + MNT_ACT_UMOUNT +}; + +/* + * Errors -- by default libmount returns -errno for generic errors (ENOMEM, + * EINVAL, ...) and for mount(2) errors, but for some specific operations it + * returns private error codes. Note that maximum system errno value should be + * 4095 on UNIXes. + * + * See also mnt_context_get_syscall_errno() and mnt_context_get_helper_status(). + */ +/** + * MNT_ERR_NOFSTAB: + * + * not found required entry in fstab + */ +#define MNT_ERR_NOFSTAB 5000 +/** + * MNT_ERR_NOFSTYPE: + * + * failed to detect filesystem type + */ +#define MNT_ERR_NOFSTYPE 5001 +/** + * MNT_ERR_NOSOURCE: + * + * required mount source undefined + */ +#define MNT_ERR_NOSOURCE 5002 +/** + * MNT_ERR_LOOPDEV: + * + * loopdev setup failed, errno set by libc + */ +#define MNT_ERR_LOOPDEV 5003 +/** + * MNT_ERR_MOUNTOPT: + * + * failed to parse/use userspace mount options + */ +#define MNT_ERR_MOUNTOPT 5004 +/** + * MNT_ERR_APPLYFLAGS: + * + * failed to apply MS_PROPAGATION flags, and MOUNT_ATTR_* attributes for + * mount_setattr(2) + */ +#define MNT_ERR_APPLYFLAGS 5005 +/** + * MNT_ERR_AMBIFS: + * + * libblkid detected more filesystems on the device + */ +#define MNT_ERR_AMBIFS 5006 +/** + * MNT_ERR_LOOPOVERLAP: + * + * detected overlapping loop device that cannot be re-used + */ +#define MNT_ERR_LOOPOVERLAP 5007 +/** + * MNT_ERR_LOCK: + * + * failed to lock utab or so. + */ +#define MNT_ERR_LOCK 5008 +/** + * MNT_ERR_NAMESPACE: + * + * failed to switch namespace + */ +#define MNT_ERR_NAMESPACE 5009 +/** + * MNT_ERR_ONLYONCE: + * + * filesystem mounted, but --onlyonce specified + */ +#define MNT_ERR_ONLYONCE 5010 +/** + * MNT_ERR_CHOWN: + * + * filesystem mounted, but subsequent X-mount.owner=/X-mount.group= lchown(2) failed + */ +#define MNT_ERR_CHOWN 5011 +/** + * MNT_ERR_CHMOD: + * + * filesystem mounted, but subsequent X-mount.mode= chmod(2) failed + */ +#define MNT_ERR_CHMOD 5012 +/** + * MNT_ERR_IDMAP: + * + * filesystem mounted, but subsequent X-mount.idmap= failed + */ +#define MNT_ERR_IDMAP 5013 + + +/* + * Overall return codes -- based on mount(8) and umount(8) return codes. + * See mnt_context_get_excode() for more details. + */ + +/** + * MNT_EX_SUCCESS: + * + * [u]mount(8) exit code: no errors + */ +#define MNT_EX_SUCCESS 0 + +/** + * MNT_EX_USAGE: + * + * [u]mount(8) exit code: incorrect invocation or permission + */ +#define MNT_EX_USAGE 1 + +/** + * MNT_EX_SYSERR: + * + * [u]mount(8) exit code: out of memory, cannot fork, ... + */ +#define MNT_EX_SYSERR 2 + +/** + * MNT_EX_SOFTWARE: + * + * [u]mount(8) exit code: internal mount bug or wrong version + */ +#define MNT_EX_SOFTWARE 4 + +/** + * MNT_EX_USER: + * + * [u]mount(8) exit code: user interrupt + */ +#define MNT_EX_USER 8 + +/** + * MNT_EX_FILEIO: + * + * [u]mount(8) exit code: problems writing, locking, ... utab + */ +#define MNT_EX_FILEIO 16 + +/** + * MNT_EX_FAIL: + * + * [u]mount(8) exit code: mount failure + */ +#define MNT_EX_FAIL 32 + +/** + * MNT_EX_SOMEOK: + * + * [u]mount(8) exit code: some mount succeeded; usually when executed with + * --all options. Never returned by libmount. + */ +#define MNT_EX_SOMEOK 64 + + + +#ifndef __GNUC_PREREQ +# if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GNUC_PREREQ(maj, min) 0 +# endif +#endif + +#ifndef __ul_attribute__ +# if __GNUC_PREREQ (3, 4) +# define __ul_attribute__(_a_) __attribute__(_a_) +# else +# define __ul_attribute__(_a_) +# endif +#endif + + +/* init.c */ +extern void mnt_init_debug(int mask); + +/* version.c */ +extern int mnt_parse_version_string(const char *ver_string); +extern int mnt_get_library_version(const char **ver_string); +extern int mnt_get_library_features(const char ***features); + +/* utils.c */ +extern char *mnt_mangle(const char *str) + __ul_attribute__((warn_unused_result)); +extern char *mnt_unmangle(const char *str) + __ul_attribute__((warn_unused_result)); + +extern int mnt_tag_is_valid(const char *tag); +extern int mnt_fstype_is_netfs(const char *type); +extern int mnt_fstype_is_pseudofs(const char *type); + +extern int mnt_match_fstype(const char *type, const char *pattern) + __ul_attribute__((warn_unused_result)); +extern int mnt_match_options(const char *optstr, const char *pattern) + __ul_attribute__((warn_unused_result)); +extern const char *mnt_get_fstab_path(void); +extern const char *mnt_get_swaps_path(void); +extern const char *mnt_get_mtab_path(void); +extern int mnt_has_regular_mtab(const char **mtab, int *writable); +extern char *mnt_get_mountpoint(const char *path) + __ul_attribute__((warn_unused_result)); +extern int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path) + __ul_attribute__((nonnull(3))); + +/* cache.c */ +extern struct libmnt_cache *mnt_new_cache(void) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_cache(struct libmnt_cache *cache); + +extern void mnt_ref_cache(struct libmnt_cache *cache); +extern void mnt_unref_cache(struct libmnt_cache *cache); + +extern int mnt_cache_set_targets(struct libmnt_cache *cache, + struct libmnt_table *mountinfo); +extern int mnt_cache_set_sbprobe(struct libmnt_cache *cache, int flags); +extern int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname); + +extern int mnt_cache_device_has_tag(struct libmnt_cache *cache, + const char *devname, + const char *token, + const char *value); + +extern char *mnt_cache_find_tag_value(struct libmnt_cache *cache, + const char *devname, const char *token); + +extern char *mnt_get_fstype(const char *devname, int *ambi, + struct libmnt_cache *cache) + __ul_attribute__((warn_unused_result)); +extern char *mnt_resolve_path(const char *path, struct libmnt_cache *cache) + __ul_attribute__((warn_unused_result)); +extern char *mnt_resolve_target(const char *path, struct libmnt_cache *cache) + __ul_attribute__((warn_unused_result)); +extern char *mnt_resolve_tag(const char *token, const char *value, + struct libmnt_cache *cache) + __ul_attribute__((warn_unused_result)); +extern char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache) + __ul_attribute__((warn_unused_result)); +extern char *mnt_pretty_path(const char *path, struct libmnt_cache *cache) + __ul_attribute__((warn_unused_result)); + +/* optstr.c */ +extern int mnt_optstr_next_option(char **optstr, char **name, size_t *namesz, + char **value, size_t *valuesz); +extern int mnt_optstr_append_option(char **optstr, const char *name, + const char *value); +extern int mnt_optstr_prepend_option(char **optstr, const char *name, + const char *value); + +extern int mnt_optstr_get_option(const char *optstr, const char *name, + char **value, size_t *valsz); +extern int mnt_optstr_set_option(char **optstr, const char *name, + const char *value); +extern int mnt_optstr_remove_option(char **optstr, const char *name); +extern int mnt_optstr_deduplicate_option(char **optstr, const char *name); + +extern int mnt_split_optstr(const char *optstr, + char **user, char **vfs, char **fs, + int ignore_user, int ignore_vfs); + +extern int mnt_optstr_get_options(const char *optstr, char **subset, + const struct libmnt_optmap *map, int ignore); + +extern int mnt_optstr_get_flags(const char *optstr, unsigned long *flags, + const struct libmnt_optmap *map); + +extern int mnt_optstr_apply_flags(char **optstr, unsigned long flags, + const struct libmnt_optmap *map); + +/* iter.c */ +enum { + + MNT_ITER_FORWARD = 0, + MNT_ITER_BACKWARD +}; +extern struct libmnt_iter *mnt_new_iter(int direction) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_iter(struct libmnt_iter *itr); + +extern void mnt_reset_iter(struct libmnt_iter *itr, int direction) + __ul_attribute__((nonnull)); +extern int mnt_iter_get_direction(struct libmnt_iter *itr) + __ul_attribute__((nonnull)); + +/* optmap.c */ +enum { + MNT_LINUX_MAP = 1, + MNT_USERSPACE_MAP +}; +extern const struct libmnt_optmap *mnt_get_builtin_optmap(int id); + +/* lock.c */ +extern struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_lock(struct libmnt_lock *ml); + +extern void mnt_unlock_file(struct libmnt_lock *ml); +extern int mnt_lock_file(struct libmnt_lock *ml); +extern int mnt_lock_block_signals(struct libmnt_lock *ml, int enable); + +/* fs.c */ +extern struct libmnt_fs *mnt_new_fs(void) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_fs(struct libmnt_fs *fs); +extern void mnt_ref_fs(struct libmnt_fs *fs); +extern void mnt_unref_fs(struct libmnt_fs *fs); + +extern void mnt_reset_fs(struct libmnt_fs *fs); +extern struct libmnt_fs *mnt_copy_fs(struct libmnt_fs *dest, + const struct libmnt_fs *src) + __ul_attribute__((warn_unused_result)); +extern void *mnt_fs_get_userdata(struct libmnt_fs *fs); +extern int mnt_fs_set_userdata(struct libmnt_fs *fs, void *data); +extern const char *mnt_fs_get_source(struct libmnt_fs *fs); +extern int mnt_fs_set_source(struct libmnt_fs *fs, const char *source); +extern const char *mnt_fs_get_srcpath(struct libmnt_fs *fs); +extern int mnt_fs_get_table(struct libmnt_fs *fs, struct libmnt_table **tb); + +extern int mnt_fs_get_tag(struct libmnt_fs *fs, const char **name, + const char **value); +extern const char *mnt_fs_get_target(struct libmnt_fs *fs); +extern int mnt_fs_set_target(struct libmnt_fs *fs, const char *tgt); +extern const char *mnt_fs_get_fstype(struct libmnt_fs *fs); +extern int mnt_fs_set_fstype(struct libmnt_fs *fs, const char *fstype); + +extern int mnt_fs_streq_srcpath(struct libmnt_fs *fs, const char *path) + __ul_attribute__((warn_unused_result)); +extern int mnt_fs_streq_target(struct libmnt_fs *fs, const char *path) + __ul_attribute__((warn_unused_result)); + +extern char *mnt_fs_strdup_options(struct libmnt_fs *fs) + __ul_attribute__((warn_unused_result)); +extern const char *mnt_fs_get_options(struct libmnt_fs *fs) + __ul_attribute__((warn_unused_result)); +extern const char *mnt_fs_get_optional_fields(struct libmnt_fs *fs) + __ul_attribute__((warn_unused_result)); +extern int mnt_fs_get_propagation(struct libmnt_fs *fs, unsigned long *flags); + +extern int mnt_fs_set_options(struct libmnt_fs *fs, const char *optstr); +extern int mnt_fs_append_options(struct libmnt_fs *fs, const char *optstr); +extern int mnt_fs_prepend_options(struct libmnt_fs *fs, const char *optstr); + +extern int mnt_fs_get_option(struct libmnt_fs *fs, const char *name, + char **value, size_t *valsz); + +extern const char *mnt_fs_get_fs_options(struct libmnt_fs *fs); +extern const char *mnt_fs_get_vfs_options(struct libmnt_fs *fs); +extern const char *mnt_fs_get_user_options(struct libmnt_fs *fs); +extern char *mnt_fs_get_vfs_options_all(struct libmnt_fs *fs); + +extern const char *mnt_fs_get_attributes(struct libmnt_fs *fs); +extern int mnt_fs_set_attributes(struct libmnt_fs *fs, const char *optstr); +extern int mnt_fs_get_attribute(struct libmnt_fs *fs, const char *name, + char **value, size_t *valsz); +extern int mnt_fs_append_attributes(struct libmnt_fs *fs, const char *optstr); +extern int mnt_fs_prepend_attributes(struct libmnt_fs *fs, const char *optstr); + +extern int mnt_fs_get_freq(struct libmnt_fs *fs); +extern int mnt_fs_set_freq(struct libmnt_fs *fs, int freq); +extern int mnt_fs_get_passno(struct libmnt_fs *fs); +extern int mnt_fs_set_passno(struct libmnt_fs *fs, int passno); +extern const char *mnt_fs_get_root(struct libmnt_fs *fs); +extern int mnt_fs_set_root(struct libmnt_fs *fs, const char *path); +extern const char *mnt_fs_get_bindsrc(struct libmnt_fs *fs); +extern int mnt_fs_set_bindsrc(struct libmnt_fs *fs, const char *src); +extern int mnt_fs_get_id(struct libmnt_fs *fs); +extern int mnt_fs_get_parent_id(struct libmnt_fs *fs); +extern dev_t mnt_fs_get_devno(struct libmnt_fs *fs); +extern pid_t mnt_fs_get_tid(struct libmnt_fs *fs); + +extern const char *mnt_fs_get_swaptype(struct libmnt_fs *fs); +extern off_t mnt_fs_get_size(struct libmnt_fs *fs); +extern off_t mnt_fs_get_usedsize(struct libmnt_fs *fs); +extern int mnt_fs_get_priority(struct libmnt_fs *fs); +extern int mnt_fs_set_priority(struct libmnt_fs *fs, int prio); + +extern const char *mnt_fs_get_comment(struct libmnt_fs *fs); +extern int mnt_fs_set_comment(struct libmnt_fs *fs, const char *comm); +extern int mnt_fs_append_comment(struct libmnt_fs *fs, const char *comm); + +extern int mnt_fs_match_target(struct libmnt_fs *fs, const char *target, + struct libmnt_cache *cache); +extern int mnt_fs_match_source(struct libmnt_fs *fs, const char *source, + struct libmnt_cache *cache); +extern int mnt_fs_match_fstype(struct libmnt_fs *fs, const char *types); +extern int mnt_fs_match_options(struct libmnt_fs *fs, const char *options); +extern int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file); + +extern int mnt_fs_is_kernel(struct libmnt_fs *fs); +extern int mnt_fs_is_swaparea(struct libmnt_fs *fs); +extern int mnt_fs_is_netfs(struct libmnt_fs *fs); +extern int mnt_fs_is_pseudofs(struct libmnt_fs *fs); +extern int mnt_fs_is_regularfs(struct libmnt_fs *fs); + +extern void mnt_free_mntent(struct mntent *mnt); +extern int mnt_fs_to_mntent(struct libmnt_fs *fs, struct mntent **mnt); + +/* tab-parse.c */ +extern struct libmnt_table *mnt_new_table_from_file(const char *filename) + __ul_attribute__((warn_unused_result)); +extern struct libmnt_table *mnt_new_table_from_dir(const char *dirname) + __ul_attribute__((warn_unused_result)); +extern int mnt_table_parse_stream(struct libmnt_table *tb, FILE *f, + const char *filename); +extern int mnt_table_parse_file(struct libmnt_table *tb, const char *filename); +extern int mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname); + +extern int mnt_table_parse_fstab(struct libmnt_table *tb, const char *filename); +extern int mnt_table_parse_swaps(struct libmnt_table *tb, const char *filename); +extern int mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename); +extern int mnt_table_set_parser_errcb(struct libmnt_table *tb, + int (*cb)(struct libmnt_table *tb, const char *filename, int line)); + +/* tab.c */ +extern struct libmnt_table *mnt_new_table(void) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_table(struct libmnt_table *tb); + +extern void mnt_ref_table(struct libmnt_table *tb); +extern void mnt_unref_table(struct libmnt_table *tb); + +extern int mnt_reset_table(struct libmnt_table *tb); +extern int mnt_table_get_nents(struct libmnt_table *tb); +extern int mnt_table_is_empty(struct libmnt_table *tb); + +extern int mnt_table_set_userdata(struct libmnt_table *tb, void *data); +extern void *mnt_table_get_userdata(struct libmnt_table *tb); + +extern void mnt_table_enable_comments(struct libmnt_table *tb, int enable); +extern int mnt_table_with_comments(struct libmnt_table *tb); +extern const char *mnt_table_get_intro_comment(struct libmnt_table *tb); +extern int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm); +extern int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm); +extern int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm); +extern const char *mnt_table_get_trailing_comment(struct libmnt_table *tb); +extern int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm); + +extern int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc); +extern struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb); +extern int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs); +extern int mnt_table_find_fs(struct libmnt_table *tb, struct libmnt_fs *fs); +extern int mnt_table_insert_fs(struct libmnt_table *tb, int before, + struct libmnt_fs *pos, struct libmnt_fs *fs); +extern int mnt_table_move_fs(struct libmnt_table *src, struct libmnt_table *dst, + int before, struct libmnt_fs *pos, struct libmnt_fs *fs); +extern int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs); +extern int mnt_table_first_fs(struct libmnt_table *tb, struct libmnt_fs **fs); +extern int mnt_table_last_fs(struct libmnt_table *tb, struct libmnt_fs **fs); +extern int mnt_table_over_fs(struct libmnt_table *tb, struct libmnt_fs *parent, + struct libmnt_fs **child); +extern int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, + struct libmnt_fs **fs); +extern int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr, + struct libmnt_fs *parent, struct libmnt_fs **chld); +extern int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root); +extern int mnt_table_set_iter(struct libmnt_table *tb, struct libmnt_iter *itr, + struct libmnt_fs *fs); + +enum { + MNT_UNIQ_FORWARD = (1 << 1), /* default is backward */ + MNT_UNIQ_KEEPTREE = (1 << 2) +}; +extern int mnt_table_uniq_fs(struct libmnt_table *tb, int flags, + int (*cmp)(struct libmnt_table *, + struct libmnt_fs *, + struct libmnt_fs *)); + +extern struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb, + const char *path, int direction); +extern struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, + const char *path, int direction); +extern struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, + const char *path, int direction); +extern struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag, + const char *val, int direction); +extern struct libmnt_fs *mnt_table_find_target_with_option(struct libmnt_table *tb, const char *path, + const char *option, const char *val, int direction); +extern struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb, + const char *source, int direction); +extern struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb, + const char *source, + const char *target, int direction); +extern struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb, + dev_t devno, int direction); + +extern 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); + +extern int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs); + +/* tab_update.c */ +extern struct libmnt_update *mnt_new_update(void) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_update(struct libmnt_update *upd); + +extern int mnt_table_replace_file(struct libmnt_table *tb, const char *filename); +extern int mnt_table_write_file(struct libmnt_table *tb, FILE *file); + +extern int mnt_update_is_ready(struct libmnt_update *upd); +extern int mnt_update_set_fs(struct libmnt_update *upd, unsigned long mountflags, + const char *target, struct libmnt_fs *fs); +extern int mnt_update_table(struct libmnt_update *upd, struct libmnt_lock *lc); +extern unsigned long mnt_update_get_mflags(struct libmnt_update *upd); +extern int mnt_update_force_rdonly(struct libmnt_update *upd, int rdonly); +extern const char *mnt_update_get_filename(struct libmnt_update *upd); +extern struct libmnt_fs *mnt_update_get_fs(struct libmnt_update *upd); + +/* tab_diff.c */ +enum { + MNT_TABDIFF_MOUNT = 1, + MNT_TABDIFF_UMOUNT, + MNT_TABDIFF_MOVE, + MNT_TABDIFF_REMOUNT, + MNT_TABDIFF_PROPAGATION, /* not implemented yet (TODO) */ +}; + +extern struct libmnt_tabdiff *mnt_new_tabdiff(void) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_tabdiff(struct libmnt_tabdiff *df); + +extern int mnt_diff_tables(struct libmnt_tabdiff *df, + struct libmnt_table *old_tab, + struct libmnt_table *new_tab); + +extern int mnt_tabdiff_next_change(struct libmnt_tabdiff *df, + struct libmnt_iter *itr, + struct libmnt_fs **old_fs, + struct libmnt_fs **new_fs, + int *oper); + +/* monitor.c */ +enum { + MNT_MONITOR_TYPE_USERSPACE = 1, /* userspace mount options */ + MNT_MONITOR_TYPE_KERNEL /* kernel mount table */ +}; + +extern struct libmnt_monitor *mnt_new_monitor(void); +extern void mnt_ref_monitor(struct libmnt_monitor *mn); +extern void mnt_unref_monitor(struct libmnt_monitor *mn); + +extern int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable); +extern int mnt_monitor_enable_userspace(struct libmnt_monitor *mn, + int enable, const char *filename); + +extern int mnt_monitor_get_fd(struct libmnt_monitor *mn); +extern int mnt_monitor_close_fd(struct libmnt_monitor *mn); +extern int mnt_monitor_wait(struct libmnt_monitor *mn, int timeout); + +extern int mnt_monitor_next_change(struct libmnt_monitor *mn, + const char **filename, int *type); +extern int mnt_monitor_event_cleanup(struct libmnt_monitor *mn); + + +/* context.c */ + +/* + * Mode for mount options from fstab), see mnt_context_set_optsmode(). + */ +enum { + MNT_OMODE_IGNORE = (1 << 1), /* ignore fstab options */ + MNT_OMODE_APPEND = (1 << 2), /* append fstab options to existing options */ + MNT_OMODE_PREPEND = (1 << 3), /* prepend fstab options to existing options */ + MNT_OMODE_REPLACE = (1 << 4), /* replace existing options with options from mtab/fstab */ + + MNT_OMODE_FORCE = (1 << 5), /* always read fstab options */ + + MNT_OMODE_FSTAB = (1 << 10), /* read from fstab */ + MNT_OMODE_MTAB = (1 << 11), /* read from mountinfo if fstab not enabled or failed */ + MNT_OMODE_NOTAB = (1 << 12), /* do not read fstab at all */ + + /* default */ + MNT_OMODE_AUTO = (MNT_OMODE_PREPEND | MNT_OMODE_FSTAB | MNT_OMODE_MTAB), + /* non-root users */ + MNT_OMODE_USER = (MNT_OMODE_REPLACE | MNT_OMODE_FORCE | MNT_OMODE_FSTAB) +}; + +extern struct libmnt_context *mnt_new_context(void) + __ul_attribute__((warn_unused_result)); +extern void mnt_free_context(struct libmnt_context *cxt); + +extern int mnt_reset_context(struct libmnt_context *cxt); +extern int mnt_context_is_restricted(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_force_unrestricted(struct libmnt_context *cxt); + +extern int mnt_context_init_helper(struct libmnt_context *cxt, + int action, int flags); +extern int mnt_context_helper_setopt(struct libmnt_context *cxt, int c, char *arg); + +extern int mnt_context_set_optsmode(struct libmnt_context *cxt, int mode); +extern int mnt_context_disable_canonicalize(struct libmnt_context *cxt, int disable); +extern int mnt_context_enable_onlyonce(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_lazy(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_rdonly_umount(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_rwonly_mount(struct libmnt_context *cxt, int enable); +extern int mnt_context_disable_helpers(struct libmnt_context *cxt, int disable); +extern int mnt_context_enable_sloppy(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_fake(struct libmnt_context *cxt, int enable); +extern int mnt_context_disable_mtab(struct libmnt_context *cxt, int disable); +extern int mnt_context_enable_force(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_verbose(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_loopdel(struct libmnt_context *cxt, int enable); +extern int mnt_context_enable_fork(struct libmnt_context *cxt, int enable); +extern int mnt_context_disable_swapmatch(struct libmnt_context *cxt, int disable); + +extern int mnt_context_get_optsmode(struct libmnt_context *cxt); + +extern int mnt_context_is_onlyonce(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_lazy(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_rdonly_umount(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_rwonly_mount(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_sloppy(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_fake(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_nomtab(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_force(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_verbose(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_loopdel(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_nohelpers(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_nocanonicalize(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_swapmatch(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_forced_rdonly(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); + +extern int mnt_context_is_fork(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_parent(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); +extern int mnt_context_is_child(struct libmnt_context *cxt) + __ul_attribute__((nonnull)); + +extern int mnt_context_wait_for_children(struct libmnt_context *cxt, + int *nchildren, int *nerrs); + +extern int mnt_context_is_fs_mounted(struct libmnt_context *cxt, + struct libmnt_fs *fs, int *mounted); +extern int mnt_context_set_fs(struct libmnt_context *cxt, struct libmnt_fs *fs); +extern struct libmnt_fs *mnt_context_get_fs(struct libmnt_context *cxt); + +extern int mnt_context_set_source(struct libmnt_context *cxt, const char *source); +extern int mnt_context_set_target(struct libmnt_context *cxt, const char *target); +extern int mnt_context_set_fstype(struct libmnt_context *cxt, const char *fstype); +extern int mnt_context_set_target_prefix(struct libmnt_context *cxt, const char *path); + +extern const char *mnt_context_get_source(struct libmnt_context *cxt); +extern const char *mnt_context_get_target(struct libmnt_context *cxt); +extern const char *mnt_context_get_fstype(struct libmnt_context *cxt); +extern const char *mnt_context_get_target_prefix(struct libmnt_context *cxt); + +extern void *mnt_context_get_mtab_userdata(struct libmnt_context *cxt); +extern void *mnt_context_get_fstab_userdata(struct libmnt_context *cxt); +extern void *mnt_context_get_fs_userdata(struct libmnt_context *cxt); + +extern int mnt_context_set_options(struct libmnt_context *cxt, const char *optstr); +extern int mnt_context_append_options(struct libmnt_context *cxt, const char *optstr); + +extern const char *mnt_context_get_options(struct libmnt_context *cxt); + +extern int mnt_context_set_fstype_pattern(struct libmnt_context *cxt, const char *pattern); +extern int mnt_context_set_options_pattern(struct libmnt_context *cxt, const char *pattern); + +extern int mnt_context_set_passwd_cb(struct libmnt_context *cxt, + char *(*get)(struct libmnt_context *), + void (*release)(struct libmnt_context *, char *)) + __ul_attribute__((deprecated)); + +extern int mnt_context_set_tables_errcb(struct libmnt_context *cxt, + int (*cb)(struct libmnt_table *tb, const char *filename, int line)); +extern int mnt_context_set_fstab(struct libmnt_context *cxt, + struct libmnt_table *tb); +extern int mnt_context_get_fstab(struct libmnt_context *cxt, + struct libmnt_table **tb); + +extern int mnt_context_get_mtab(struct libmnt_context *cxt, + struct libmnt_table **tb); +extern int mnt_context_get_table(struct libmnt_context *cxt, + const char *filename, + struct libmnt_table **tb); +extern int mnt_context_set_cache(struct libmnt_context *cxt, + struct libmnt_cache *cache); +extern struct libmnt_cache *mnt_context_get_cache(struct libmnt_context *cxt); +extern struct libmnt_lock *mnt_context_get_lock(struct libmnt_context *cxt); +extern int mnt_context_set_mflags(struct libmnt_context *cxt, + unsigned long flags); +extern int mnt_context_get_mflags(struct libmnt_context *cxt, + unsigned long *flags); +extern int mnt_context_set_user_mflags(struct libmnt_context *cxt, + unsigned long flags); +extern int mnt_context_get_user_mflags(struct libmnt_context *cxt, + unsigned long *flags); + +extern int mnt_context_set_mountdata(struct libmnt_context *cxt, void *data); +extern int mnt_context_apply_fstab(struct libmnt_context *cxt); + +extern int mnt_context_reset_status(struct libmnt_context *cxt); +extern int mnt_context_get_status(struct libmnt_context *cxt); + +extern int mnt_context_helper_executed(struct libmnt_context *cxt); +extern int mnt_context_get_helper_status(struct libmnt_context *cxt); + +extern int mnt_context_syscall_called(struct libmnt_context *cxt); + +extern int mnt_context_get_syscall_errno(struct libmnt_context *cxt); + +extern int mnt_context_strerror(struct libmnt_context *cxt, char *buf, + size_t bufsiz) + __ul_attribute__((deprecated)); + +extern int mnt_context_enable_noautofs(struct libmnt_context *cxt, int ignore); + +extern int mnt_context_get_excode(struct libmnt_context *cxt, + int rc, char *buf, size_t bufsz); + +extern int mnt_context_set_target_ns(struct libmnt_context *cxt, const char *path); +extern struct libmnt_ns *mnt_context_get_target_ns(struct libmnt_context *cxt); +extern struct libmnt_ns *mnt_context_get_origin_ns(struct libmnt_context *cxt); +extern struct libmnt_ns *mnt_context_switch_ns(struct libmnt_context *cxt, struct libmnt_ns *ns); +extern struct libmnt_ns *mnt_context_switch_origin_ns(struct libmnt_context *cxt); +extern struct libmnt_ns *mnt_context_switch_target_ns(struct libmnt_context *cxt); + + +/* context_mount.c */ +extern int mnt_context_mount(struct libmnt_context *cxt); +extern int mnt_context_umount(struct libmnt_context *cxt); +extern int mnt_context_next_mount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, int *ignored); + +extern int mnt_context_next_remount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, + int *ignored); + +extern int mnt_context_prepare_mount(struct libmnt_context *cxt) + __ul_attribute__((warn_unused_result)); +extern int mnt_context_do_mount(struct libmnt_context *cxt); +extern int mnt_context_finalize_mount(struct libmnt_context *cxt); + +/* context_umount.c */ +extern int mnt_context_find_umount_fs(struct libmnt_context *cxt, + const char *tgt, + struct libmnt_fs **pfs); +extern int mnt_context_next_umount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, int *ignored); + +extern int mnt_context_prepare_umount(struct libmnt_context *cxt) + __ul_attribute__((warn_unused_result)); +extern int mnt_context_do_umount(struct libmnt_context *cxt); +extern int mnt_context_finalize_umount(struct libmnt_context *cxt); + +extern int mnt_context_tab_applied(struct libmnt_context *cxt); +extern int mnt_context_set_syscall_status(struct libmnt_context *cxt, int status); + +/* + * mount(8) userspace options masks (MNT_MAP_USERSPACE map) + */ +#define MNT_MS_NOAUTO (1 << 2) +#define MNT_MS_USER (1 << 3) +#define MNT_MS_USERS (1 << 4) +#define MNT_MS_OWNER (1 << 5) +#define MNT_MS_GROUP (1 << 6) +#define MNT_MS_NETDEV (1 << 7) +#define MNT_MS_COMMENT (1 << 8) +#define MNT_MS_LOOP (1 << 9) +#define MNT_MS_NOFAIL (1 << 10) +#define MNT_MS_UHELPER (1 << 11) +#define MNT_MS_HELPER (1 << 12) +#define MNT_MS_XCOMMENT (1 << 13) +#define MNT_MS_OFFSET (1 << 14) +#define MNT_MS_SIZELIMIT (1 << 15) +#define MNT_MS_ENCRYPTION (1 << 16) +#define MNT_MS_XFSTABCOMM (1 << 17) +#define MNT_MS_HASH_DEVICE (1 << 18) +#define MNT_MS_ROOT_HASH (1 << 19) +#define MNT_MS_HASH_OFFSET (1 << 20) +#define MNT_MS_ROOT_HASH_FILE (1 << 21) +#define MNT_MS_FEC_DEVICE (1 << 22) +#define MNT_MS_FEC_OFFSET (1 << 23) +#define MNT_MS_FEC_ROOTS (1 << 24) +#define MNT_MS_ROOT_HASH_SIG (1 << 25) +#define MNT_MS_VERITY_ON_CORRUPTION (1 << 26) + +/* + * mount(2) MS_* masks (MNT_MAP_LINUX map) + */ +#ifndef MS_RDONLY +#define MS_RDONLY 1 /* Mount read-only */ +#endif +#ifndef MS_NOSUID +#define MS_NOSUID 2 /* Ignore suid and sgid bits */ +#endif +#ifndef MS_NODEV +#define MS_NODEV 4 /* Disallow access to device special files */ +#endif +#ifndef MS_NOEXEC +#define MS_NOEXEC 8 /* Disallow program execution */ +#endif +#ifndef MS_SYNCHRONOUS +#define MS_SYNCHRONOUS 16 /* Writes are synced at once */ +#endif +#ifndef MS_REMOUNT +#define MS_REMOUNT 32 /* Alter flags of a mounted FS */ +#endif +#ifndef MS_MANDLOCK +#define MS_MANDLOCK 64 /* Allow mandatory locks on an FS */ +#endif +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 /* Directory modifications are synchronous */ +#endif +#ifndef MS_NOSYMFOLLOW +#define MS_NOSYMFOLLOW 256 /* Don't follow symlinks */ +#endif +#ifndef MS_NOATIME +#define MS_NOATIME 0x400 /* 1024: Do not update access times. */ +#endif +#ifndef MS_NODIRATIME +#define MS_NODIRATIME 0x800 /* 2048: Don't update directory access times */ +#endif +#ifndef MS_BIND +#define MS_BIND 0x1000 /* 4096: Mount existing tree elsewhere as well */ +#endif +#ifndef MS_MOVE +#define MS_MOVE 0x2000 /* 8192: Atomically move the tree */ +#endif +#ifndef MS_REC +#define MS_REC 0x4000 /* 16384: Recursive loopback */ +#endif +#ifndef MS_SILENT +#define MS_SILENT 0x8000 /* 32768: Don't emit certain kernel messages */ +#endif +#ifndef MS_UNBINDABLE +#define MS_UNBINDABLE (1<<17) /* 131072: Make unbindable */ +#endif +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) /* 262144: Make private */ +#endif +#ifndef MS_SLAVE +#define MS_SLAVE (1<<19) /* 524288: Make slave */ +#endif +#ifndef MS_SHARED +#define MS_SHARED (1<<20) /* 1048576: Make shared */ +#endif +#ifndef MS_RELATIME +#define MS_RELATIME (1<<21) /* 2097152: Update atime relative to mtime/ctime */ +#endif +#ifndef MS_I_VERSION +#define MS_I_VERSION (1<<23) /* Update the inode I_version field */ +#endif +#ifndef MS_STRICTATIME +#define MS_STRICTATIME (1<<24) /* Always perform atime updates */ +#endif +#ifndef MS_LAZYTIME +#define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */ +#endif + + +/* + * Magic mount flag number. Had to be or-ed to the flag values. Deprecated and + * no more used since libmount v2.33; required for Linux <= 2.4. + */ +#ifndef MS_MGC_VAL +#define MS_MGC_VAL 0xC0ED0000 /* magic flag number to indicate "new" flags */ +#endif +#ifndef MS_MGC_MSK +#define MS_MGC_MSK 0xffff0000 /* magic flag number mask */ +#endif + + +/* Shared-subtree options */ +#define MS_PROPAGATION (MS_SHARED|MS_SLAVE|MS_UNBINDABLE|MS_PRIVATE) + +/* Options that we make ordinary users have by default. */ +#define MS_SECURE (MS_NOEXEC|MS_NOSUID|MS_NODEV) + +/* Options that we make owner-mounted devices have by default */ +#define MS_OWNERSECURE (MS_NOSUID|MS_NODEV) + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBMOUNT_MOUNT_H */ diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym new file mode 100644 index 0000000..715bb5c --- /dev/null +++ b/libmount/src/libmount.sym @@ -0,0 +1,377 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2011-2018 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 symbol versioning ensures that a new application requiring symbol foo, + * can't run with old library.so not providing foo. + * + * Version info can't enforce this since we never change the SONAME. + */ +MOUNT_2.19 { +global: + mnt_cache_device_has_tag; + mnt_cache_find_tag_value; + mnt_cache_read_tags; + mnt_context_append_options; + mnt_context_apply_fstab; + mnt_context_disable_canonicalize; + mnt_context_disable_helpers; + mnt_context_disable_mtab; + mnt_context_do_mount; + mnt_context_do_umount; + mnt_context_enable_fake; + mnt_context_enable_force; + mnt_context_enable_lazy; + mnt_context_enable_loopdel; + mnt_context_enable_rdonly_umount; + mnt_context_enable_sloppy; + mnt_context_enable_verbose; + mnt_context_finalize_mount; + mnt_context_finalize_umount; + mnt_context_get_cache; + mnt_context_get_fs; + mnt_context_get_fstab; + mnt_context_get_fstype; + mnt_context_get_lock; + mnt_context_get_mflags; + mnt_context_get_mtab; + mnt_context_get_optsmode; + mnt_context_get_source; + mnt_context_get_status; + mnt_context_get_target; + mnt_context_get_user_mflags; + mnt_context_helper_setopt; + mnt_context_init_helper; + mnt_context_is_fake; + mnt_context_is_force; + mnt_context_is_lazy; + mnt_context_is_nomtab; + mnt_context_is_rdonly_umount; + mnt_context_is_restricted; + mnt_context_is_sloppy; + mnt_context_is_verbose; + mnt_context_mount; + mnt_context_prepare_mount; + mnt_context_prepare_umount; + mnt_context_set_cache; + mnt_context_set_fs; + mnt_context_set_fstab; + mnt_context_set_fstype; + mnt_context_set_fstype_pattern; + mnt_context_set_mflags; + mnt_context_set_mountdata; + mnt_context_set_options; + mnt_context_set_options_pattern; + mnt_context_set_optsmode; + mnt_context_set_source; + mnt_context_set_syscall_status; + mnt_context_set_target; + mnt_context_set_user_mflags; + mnt_context_strerror; + mnt_context_umount; + mnt_copy_fs; + mnt_free_cache; + mnt_free_context; + mnt_free_fs; + mnt_free_iter; + mnt_free_lock; + mnt_free_mntent; + mnt_free_table; + mnt_free_update; + mnt_fs_append_attributes; + mnt_fs_append_options; + mnt_fs_get_attribute; + mnt_fs_get_attributes; + mnt_fs_get_bindsrc; + mnt_fs_get_devno; + mnt_fs_get_freq; + mnt_fs_get_fs_options; + mnt_fs_get_fstype; + mnt_fs_get_id; + mnt_fs_get_option; + mnt_fs_get_parent_id; + mnt_fs_get_passno; + mnt_fs_get_root; + mnt_fs_get_source; + mnt_fs_get_srcpath; + mnt_fs_get_tag; + mnt_fs_get_target; + mnt_fs_get_userdata; + mnt_fs_get_user_options; + mnt_fs_get_vfs_options; + mnt_fs_is_kernel; + mnt_fs_match_fstype; + mnt_fs_match_options; + mnt_fs_match_source; + mnt_fs_match_target; + mnt_fs_prepend_attributes; + mnt_fs_prepend_options; + mnt_fs_print_debug; + mnt_fs_set_attributes; + mnt_fs_set_bindsrc; + mnt_fs_set_freq; + mnt_fs_set_fstype; + mnt_fs_set_options; + mnt_fs_set_passno; + mnt_fs_set_root; + mnt_fs_set_source; + mnt_fs_set_target; + mnt_fs_set_userdata; + mnt_fs_strdup_options; + mnt_fs_to_mntent; + mnt_fstype_is_netfs; + mnt_fstype_is_pseudofs; + mnt_get_builtin_optmap; + mnt_get_fstab_path; + mnt_get_fstype; + mnt_get_library_version; + mnt_get_mtab_path; + mnt_has_regular_mtab; + mnt_init_debug; + mnt_iter_get_direction; + mnt_lock_file; + mnt_mangle; + mnt_match_fstype; + mnt_match_options; + mnt_new_cache; + mnt_new_context; + mnt_new_fs; + mnt_new_iter; + mnt_new_lock; + mnt_new_table; + mnt_new_table_from_dir; + mnt_new_table_from_file; + mnt_new_update; + mnt_optstr_append_option; + mnt_optstr_apply_flags; + mnt_optstr_get_flags; + mnt_optstr_get_option; + mnt_optstr_get_options; + mnt_optstr_next_option; + mnt_optstr_prepend_option; + mnt_optstr_remove_option; + mnt_optstr_set_option; + mnt_parse_version_string; + mnt_reset_context; + mnt_reset_fs; + mnt_reset_iter; + mnt_resolve_path; + mnt_resolve_spec; + mnt_resolve_tag; + mnt_split_optstr; + mnt_table_add_fs; + mnt_table_find_next_fs; + mnt_table_find_pair; + mnt_table_find_source; + mnt_table_find_srcpath; + mnt_table_find_tag; + mnt_table_find_target; + mnt_table_get_cache; + mnt_table_get_nents; + mnt_table_get_root_fs; + mnt_table_next_child_fs; + mnt_table_next_fs; + mnt_table_parse_file; + mnt_table_parse_fstab; + mnt_table_parse_mtab; + mnt_table_parse_stream; + mnt_table_remove_fs; + mnt_table_set_cache; + mnt_table_set_iter; + mnt_table_set_parser_errcb; + mnt_unlock_file; + mnt_unmangle; + mnt_update_force_rdonly; + mnt_update_get_filename; + mnt_update_get_fs; + mnt_update_get_mflags; + mnt_update_is_ready; + mnt_update_set_fs; + mnt_update_table; +local: + *; +}; + +MOUNT_2.20 { +global: + mnt_context_get_table; + mnt_context_is_fs_mounted; + mnt_context_next_mount; + mnt_context_set_tables_errcb; + mnt_diff_tables; + mnt_free_tabdiff; + mnt_fs_get_options; + mnt_lock_block_signals; + mnt_new_tabdiff; + mnt_pretty_path; + mnt_reset_table; + mnt_tabdiff_next_change; + mnt_table_is_fs_mounted; +} MOUNT_2.19; + +MOUNT_2.21 { +global: + mnt_context_enable_fork; + mnt_context_get_helper_status; + mnt_context_get_syscall_errno; + mnt_context_helper_executed; + mnt_context_is_child; + mnt_context_is_fork; + mnt_context_is_parent; + mnt_context_next_umount; + mnt_context_reset_status; + mnt_context_set_passwd_cb; + mnt_context_syscall_called; + mnt_context_wait_for_children; + mnt_fs_is_netfs; + mnt_fs_is_pseudofs; + mnt_fs_is_swaparea; + mnt_get_library_features; + mnt_table_parse_dir; +} MOUNT_2.20; + +MOUNT_2.22 { +global: + mnt_context_disable_swapmatch; + mnt_context_get_options; + mnt_context_is_loopdel; + mnt_context_is_nocanonicalize; + mnt_context_is_nohelpers; + mnt_context_is_swapmatch; + mnt_context_tab_applied; + mnt_fs_get_priority; + mnt_fs_get_size; + mnt_fs_get_swaptype; + mnt_fs_get_tid; + mnt_fs_get_usedsize; + mnt_fs_streq_srcpath; + mnt_fs_streq_target; + mnt_get_mountpoint; + mnt_get_swaps_path; + mnt_optstr_deduplicate_option; + mnt_table_find_devno; + mnt_table_parse_swaps; +} MOUNT_2.21; + +MOUNT_2.23 { +global: + mnt_fs_get_optional_fields; + mnt_fs_get_propagation; + mnt_context_find_umount_fs; + mnt_table_find_mountpoint; +} MOUNT_2.22; + +MOUNT_2.24 { +global: + mnt_context_get_fstab_userdata; + mnt_context_get_fs_userdata; + mnt_context_get_mtab_userdata; + mnt_fs_append_comment; + mnt_fs_get_comment; + mnt_fs_set_comment; + mnt_ref_cache; + mnt_ref_fs; + mnt_ref_table; + mnt_table_append_intro_comment; + mnt_table_append_trailing_comment; + mnt_table_enable_comments; + mnt_table_first_fs; + mnt_table_get_intro_comment; + mnt_table_get_trailing_comment; + mnt_table_get_userdata; + mnt_table_is_empty; + mnt_table_last_fs; + mnt_table_replace_file; + mnt_table_set_intro_comment; + mnt_table_set_trailing_comment; + mnt_table_set_userdata; + mnt_table_with_comments; + mnt_table_write_file; + mnt_unref_cache; + mnt_unref_fs; + mnt_unref_table; +} MOUNT_2.23; + +MOUNT_2.25 { + mnt_cache_set_targets; + mnt_resolve_target; + mnt_table_uniq_fs; + mnt_tag_is_valid; +} MOUNT_2.24; + +MOUNT_2.26 { + mnt_monitor_close_fd; + mnt_monitor_enable_userspace; + mnt_monitor_enable_kernel; + mnt_monitor_event_cleanup; + mnt_monitor_get_fd; + mnt_monitor_next_change; + mnt_monitor_wait; + mnt_new_monitor; + mnt_ref_monitor; + mnt_unref_monitor; +} MOUNT_2.25; + +MOUNT_2.28 { + mnt_table_find_target_with_option; + mnt_fs_set_priority; +} MOUNT_2.26; + +MOUNT_2.30 { + mnt_context_is_rwonly_mount; + mnt_context_forced_rdonly; + mnt_context_enable_rwonly_mount; + mnt_context_get_excode; +} MOUNT_2.28; + +MOUNT_2.33 { + mnt_context_get_origin_ns; + mnt_context_get_target_ns; + mnt_context_set_target_ns; + mnt_context_switch_ns; + mnt_context_switch_origin_ns; + mnt_context_switch_target_ns; +} MOUNT_2.30; + +MOUNT_2.34 { + mnt_context_next_remount; + mnt_fs_get_table; + mnt_guess_system_root; + mnt_table_find_fs; + mnt_table_insert_fs; + mnt_table_move_fs; +} MOUNT_2.33; + +MOUNT_2_35 { + mnt_context_force_unrestricted; + mnt_context_get_target_prefix; + mnt_context_set_target_prefix; +} MOUNT_2.34; + +MOUNT_2_37 { + mnt_fs_get_vfs_options_all; + mnt_table_over_fs; +} MOUNT_2_35; + + +MOUNT_2_38 { + mnt_fs_is_regularfs; +} MOUNT_2_37; + +MOUNT_2_39 { + mnt_cache_set_sbprobe; + mnt_context_enable_onlyonce; + mnt_context_is_lazy; + mnt_context_enable_noautofs; + mnt_table_enable_noautofs; + mnt_table_is_noautofs; +} MOUNT_2_38; diff --git a/libmount/src/lock.c b/libmount/src/lock.c new file mode 100644 index 0000000..4835406 --- /dev/null +++ b/libmount/src/lock.c @@ -0,0 +1,399 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2009-2018 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. + */ + +/** + * SECTION: lock + * @title: Locking + * @short_description: locking methods for utab or another libmount files + * + * Since v2.39 libmount does nto support classic mtab locking. Now all is based + * on flock only. + * + */ +#include <sys/time.h> +#include <time.h> +#include <signal.h> +#include <fcntl.h> +#include <limits.h> +#include <sys/file.h> + +#include "strutils.h" +#include "closestream.h" +#include "pathnames.h" +#include "mountP.h" +#include "monotonic.h" + +/* + * lock handler + */ +struct libmnt_lock { + char *lockfile; /* path to lock file (e.g. /etc/mtab~) */ + int lockfile_fd; /* lock file descriptor */ + + unsigned int locked :1, /* do we own the lock? */ + sigblock :1; /* block signals when locked */ + + sigset_t oldsigmask; +}; + + +/** + * mnt_new_lock: + * @datafile: the file that should be covered by the lock + * @id: ignored by library + * + * Returns: newly allocated lock handler or NULL on case of error. + */ +struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id __attribute__((__unused__))) +{ + struct libmnt_lock *ml = NULL; + char *lo = NULL; + size_t losz; + + if (!datafile) + return NULL; + + losz = strlen(datafile) + sizeof(".lock"); + lo = malloc(losz); + if (!lo) + goto err; + + snprintf(lo, losz, "%s.lock", datafile); + + ml = calloc(1, sizeof(*ml) ); + if (!ml) + goto err; + + ml->lockfile_fd = -1; + ml->lockfile = lo; + + DBG(LOCKS, ul_debugobj(ml, "alloc: lockfile=%s", lo)); + return ml; +err: + free(lo); + free(ml); + return NULL; +} + + +/** + * mnt_free_lock: + * @ml: struct libmnt_lock handler + * + * Deallocates mnt_lock. + */ +void mnt_free_lock(struct libmnt_lock *ml) +{ + if (!ml) + return; + DBG(LOCKS, ul_debugobj(ml, "free%s", ml->locked ? " !!! LOCKED !!!" : "")); + free(ml->lockfile); + free(ml); +} + +/** + * mnt_lock_block_signals: + * @ml: struct libmnt_lock handler + * @enable: TRUE/FALSE + * + * Block/unblock signals when the lock is locked, the signals are not blocked + * by default. + * + * Returns: <0 on error, 0 on success. + */ +int mnt_lock_block_signals(struct libmnt_lock *ml, int enable) +{ + if (!ml) + return -EINVAL; + DBG(LOCKS, ul_debugobj(ml, "signals: %s", enable ? "BLOCKED" : "UNBLOCKED")); + ml->sigblock = enable ? 1 : 0; + return 0; +} + +/* + * Returns path to lockfile. + */ +static const char *mnt_lock_get_lockfile(struct libmnt_lock *ml) +{ + return ml ? ml->lockfile : NULL; +} + +/* + * Simple flocking + */ +static void unlock_simplelock(struct libmnt_lock *ml) +{ + assert(ml); + + if (ml->lockfile_fd >= 0) { + DBG(LOCKS, ul_debugobj(ml, "%s: unflocking", + mnt_lock_get_lockfile(ml))); + close(ml->lockfile_fd); + } +} + +static int lock_simplelock(struct libmnt_lock *ml) +{ + const char *lfile; + int rc; + struct stat sb; + const mode_t lock_mask = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; + + assert(ml); + + lfile = mnt_lock_get_lockfile(ml); + + DBG(LOCKS, ul_debugobj(ml, "%s: locking", lfile)); + + if (ml->sigblock) { + sigset_t sigs; + sigemptyset(&ml->oldsigmask); + sigfillset(&sigs); + sigprocmask(SIG_BLOCK, &sigs, &ml->oldsigmask); + } + + ml->lockfile_fd = open(lfile, O_RDONLY|O_CREAT|O_CLOEXEC, + S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); + if (ml->lockfile_fd < 0) { + rc = -errno; + goto err; + } + + rc = fstat(ml->lockfile_fd, &sb); + if (rc < 0) { + rc = -errno; + goto err; + } + + if ((sb.st_mode & lock_mask) != lock_mask) { + rc = fchmod(ml->lockfile_fd, lock_mask); + if (rc < 0) { + rc = -errno; + goto err; + } + } + + while (flock(ml->lockfile_fd, LOCK_EX) < 0) { + int errsv; + if ((errno == EAGAIN) || (errno == EINTR)) + continue; + errsv = errno; + close(ml->lockfile_fd); + ml->lockfile_fd = -1; + rc = -errsv; + goto err; + } + ml->locked = 1; + return 0; +err: + if (ml->sigblock) + sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL); + return rc; +} + +/** + * mnt_lock_file + * @ml: pointer to struct libmnt_lock instance + * + * Creates a lock file. +* + * Note that when the lock is used by mnt_update_table() interface then libmount + * uses flock() for private library file /run/mount/utab. + * + * Returns: 0 on success or negative number in case of error (-ETIMEOUT is case + * of stale lock file). + */ +int mnt_lock_file(struct libmnt_lock *ml) +{ + if (!ml) + return -EINVAL; + + return lock_simplelock(ml); +} + +/** + * mnt_unlock_file: + * @ml: lock struct + * + * Unlocks the file. The function could be called independently of the + * lock status (for example from exit(3)). + */ +void mnt_unlock_file(struct libmnt_lock *ml) +{ + if (!ml) + return; + + DBG(LOCKS, ul_debugobj(ml, "(%d) %s", getpid(), + ml->locked ? "unlocking" : "cleaning")); + + unlock_simplelock(ml); + + ml->locked = 0; + ml->lockfile_fd = -1; + + if (ml->sigblock) { + DBG(LOCKS, ul_debugobj(ml, "restoring sigmask")); + sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL); + } +} + +#ifdef TEST_PROGRAM + +static struct libmnt_lock *lock; + +/* + * read number from @filename, increment the number and + * write the number back to the file + */ +static void increment_data(const char *filename, int verbose, int loopno) +{ + long num; + FILE *f; + char buf[256]; + + if (!(f = fopen(filename, "r" UL_CLOEXECSTR))) + err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename); + + if (!fgets(buf, sizeof(buf), f)) + err(EXIT_FAILURE, "%d failed read: %s", getpid(), filename); + + fclose(f); + num = atol(buf) + 1; + + if (!(f = fopen(filename, "w" UL_CLOEXECSTR))) + err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename); + + fprintf(f, "%ld", num); + + if (close_stream(f) != 0) + err(EXIT_FAILURE, "write failed: %s", filename); + + if (verbose) + fprintf(stderr, "%d: %s: %ld --> %ld (loop=%d)\n", getpid(), + filename, num - 1, num, loopno); +} + +static void clean_lock(void) +{ + if (!lock) + return; + mnt_unlock_file(lock); + mnt_free_lock(lock); +} + +static void __attribute__((__noreturn__)) sig_handler(int sig) +{ + errx(EXIT_FAILURE, "\n%d: catch signal: %s\n", getpid(), strsignal(sig)); +} + +static int test_lock(struct libmnt_test *ts, int argc, char *argv[]) +{ + time_t synctime = 0; + unsigned int usecs; + const char *datafile = NULL; + int verbose = 0, loops = 0, l, idx = 1; + + if (argc < 3) + return -EINVAL; + + if (strcmp(argv[idx], "--synctime") == 0) { + synctime = (time_t) atol(argv[idx + 1]); + idx += 2; + } + if (idx < argc && strcmp(argv[idx], "--verbose") == 0) { + verbose = 1; + idx++; + } + + if (idx < argc) + datafile = argv[idx++]; + if (idx < argc) + loops = atoi(argv[idx++]); + + if (!datafile || !loops) + return -EINVAL; + + if (verbose) + fprintf(stderr, "%d: start: synctime=%u, datafile=%s, loops=%d\n", + getpid(), (int) synctime, datafile, loops); + + atexit(clean_lock); + + /* be paranoid and call exit() (=clean_lock()) for all signals */ + { + int sig = 0; + struct sigaction sa; + + sa.sa_handler = sig_handler; + sa.sa_flags = 0; + sigfillset(&sa.sa_mask); + + while (sigismember(&sa.sa_mask, ++sig) != -1 && sig != SIGCHLD) + sigaction (sig, &sa, (struct sigaction *) 0); + } + + /* start the test in exactly defined time */ + if (synctime) { + struct timeval tv; + + gettimeofday(&tv, NULL); + if (synctime && synctime - tv.tv_sec > 1) { + usecs = ((synctime - tv.tv_sec) * 1000000UL) - + (1000000UL - tv.tv_usec); + xusleep(usecs); + } + } + + for (l = 0; l < loops; l++) { + lock = mnt_new_lock(datafile, 0); + if (!lock) + return -1; + + if (mnt_lock_file(lock) != 0) { + fprintf(stderr, "%d: failed to lock %s file\n", + getpid(), datafile); + return -1; + } + + increment_data(datafile, verbose, l); + + mnt_unlock_file(lock); + mnt_free_lock(lock); + lock = NULL; + + /* The mount command usually finishes after a mtab update. We + * simulate this via short sleep -- it's also enough to make + * concurrent processes happy. + */ + if (synctime) + xusleep(25000); + } + + return 0; +} + +/* + * Note that this test should be executed from a script that creates many + * parallel processes, otherwise this test does not make sense. + */ +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--lock", test_lock, " [--synctime <time_t>] [--verbose] <datafile> <loops> " + "increment a number in datafile" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/monitor.c b/libmount/src/monitor.c new file mode 100644 index 0000000..f99751e --- /dev/null +++ b/libmount/src/monitor.c @@ -0,0 +1,982 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2014-2018 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. + */ + +/** + * SECTION: monitor + * @title: Monitor + * @short_description: interface to monitor mount tables + * + * For example monitor VFS (/proc/self/mountinfo) for changes: + * + * <informalexample> + * <programlisting> + * const char *filename; + * struct libmount_monitor *mn = mnt_new_monitor(); + * + * mnt_monitor_enable_kernel(mn, TRUE)); + * + * printf("waiting for changes...\n"); + * while (mnt_monitor_wait(mn, -1) > 0) { + * while (mnt_monitor_next_change(mn, &filename, NULL) == 0) + * printf(" %s: change detected\n", filename); + * } + * mnt_unref_monitor(mn); + * </programlisting> + * </informalexample> + * + */ + +#include "fileutils.h" +#include "mountP.h" +#include "pathnames.h" + +#include <sys/inotify.h> +#include <sys/epoll.h> + + +struct monitor_opers; + +struct monitor_entry { + int fd; /* private entry file descriptor */ + char *path; /* path to the monitored file */ + int type; /* MNT_MONITOR_TYPE_* */ + uint32_t events; /* wanted epoll events */ + + const struct monitor_opers *opers; + + unsigned int enable : 1, + changed : 1; + + struct list_head ents; +}; + +struct libmnt_monitor { + int refcount; + int fd; /* public monitor file descriptor */ + + struct list_head ents; +}; + +struct monitor_opers { + int (*op_get_fd)(struct libmnt_monitor *, struct monitor_entry *); + int (*op_close_fd)(struct libmnt_monitor *, struct monitor_entry *); + int (*op_event_verify)(struct libmnt_monitor *, struct monitor_entry *); +}; + +static int monitor_modify_epoll(struct libmnt_monitor *mn, + struct monitor_entry *me, int enable); + +/** + * mnt_new_monitor: + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the filesystem. + * + * Returns: newly allocated struct libmnt_monitor. + */ +struct libmnt_monitor *mnt_new_monitor(void) +{ + struct libmnt_monitor *mn = calloc(1, sizeof(*mn)); + if (!mn) + return NULL; + + mn->refcount = 1; + mn->fd = -1; + INIT_LIST_HEAD(&mn->ents); + + DBG(MONITOR, ul_debugobj(mn, "alloc")); + return mn; +} + +/** + * mnt_ref_monitor: + * @mn: monitor pointer + * + * Increments reference counter. + */ +void mnt_ref_monitor(struct libmnt_monitor *mn) +{ + if (mn) + mn->refcount++; +} + +static void free_monitor_entry(struct monitor_entry *me) +{ + if (!me) + return; + list_del(&me->ents); + if (me->fd >= 0) + close(me->fd); + free(me->path); + free(me); +} + +/** + * mnt_unref_monitor: + * @mn: monitor pointer + * + * Decrements the reference counter, on zero the @mn is automatically + * deallocated. + */ +void mnt_unref_monitor(struct libmnt_monitor *mn) +{ + if (!mn) + return; + + mn->refcount--; + if (mn->refcount <= 0) { + mnt_monitor_close_fd(mn); /* destroys all file descriptors */ + + while (!list_empty(&mn->ents)) { + struct monitor_entry *me = list_entry(mn->ents.next, + struct monitor_entry, ents); + free_monitor_entry(me); + } + + free(mn); + } +} + +static struct monitor_entry *monitor_new_entry(struct libmnt_monitor *mn) +{ + struct monitor_entry *me; + + assert(mn); + + me = calloc(1, sizeof(*me)); + if (!me) + return NULL; + INIT_LIST_HEAD(&me->ents); + list_add_tail(&me->ents, &mn->ents); + + me->fd = -1; + + return me; +} + +static int monitor_next_entry(struct libmnt_monitor *mn, + struct libmnt_iter *itr, + struct monitor_entry **me) +{ + int rc = 1; + + assert(mn); + assert(itr); + + if (me) + *me = NULL; + + if (!itr->head) + MNT_ITER_INIT(itr, &mn->ents); + if (itr->p != itr->head) { + if (me) + *me = MNT_ITER_GET_ENTRY(itr, struct monitor_entry, ents); + MNT_ITER_ITERATE(itr); + rc = 0; + } + + return rc; +} + +/* returns entry by type */ +static struct monitor_entry *monitor_get_entry(struct libmnt_monitor *mn, int type) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (monitor_next_entry(mn, &itr, &me) == 0) { + if (me->type == type) + return me; + } + return NULL; +} + + +/* + * Userspace monitor + */ + +static int userspace_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)), + struct monitor_entry *me) +{ + assert(me); + + if (me->fd >= 0) + close(me->fd); + me->fd = -1; + return 0; +} + +static int userspace_add_watch(struct monitor_entry *me, int *final, int *fd) +{ + char *filename = NULL; + int wd, rc = -EINVAL; + + assert(me); + assert(me->path); + + /* + * libmount uses rename(2) to atomically update utab, monitor + * rename changes is too tricky. It seems better to monitor utab + * lockfile close. + */ + if (asprintf(&filename, "%s.lock", me->path) <= 0) { + rc = -errno; + goto done; + } + + /* try lock file if already exists */ + errno = 0; + wd = inotify_add_watch(me->fd, filename, IN_CLOSE_NOWRITE); + if (wd >= 0) { + DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd)); + rc = 0; + if (final) + *final = 1; + if (fd) + *fd = wd; + goto done; + } else if (errno != ENOENT) { + rc = -errno; + goto done; + } + + while (strchr(filename, '/')) { + stripoff_last_component(filename); + if (!*filename) + break; + + /* try directory where is the lock file */ + errno = 0; + wd = inotify_add_watch(me->fd, filename, IN_CREATE|IN_ISDIR); + if (wd >= 0) { + DBG(MONITOR, ul_debug(" added inotify watch for %s [fd=%d]", filename, wd)); + rc = 0; + if (fd) + *fd = wd; + break; + } + + if (errno != ENOENT) { + rc = -errno; + break; + } + } +done: + free(filename); + return rc; +} + +static int userspace_monitor_get_fd(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + int rc; + + if (!me || me->enable == 0) /* not-initialized or disabled */ + return -EINVAL; + if (me->fd >= 0) + return me->fd; /* already initialized */ + + assert(me->path); + DBG(MONITOR, ul_debugobj(mn, " open userspace monitor for %s", me->path)); + + me->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (me->fd < 0) + goto err; + + if (userspace_add_watch(me, NULL, NULL) < 0) + goto err; + + return me->fd; +err: + rc = -errno; + if (me->fd >= 0) + close(me->fd); + me->fd = -1; + DBG(MONITOR, ul_debugobj(mn, "failed to create userspace monitor [rc=%d]", rc)); + return rc; +} + +/* + * verify and drain inotify buffer + */ +static int userspace_event_verify(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; + int status = 0; + + if (!me || me->fd < 0) + return 0; + + DBG(MONITOR, ul_debugobj(mn, "drain and verify userspace monitor inotify")); + + /* the me->fd is non-blocking */ + do { + ssize_t len; + char *p; + const struct inotify_event *e; + + len = read(me->fd, buf, sizeof(buf)); + if (len < 0) + break; + + for (p = buf; p < buf + len; + p += sizeof(struct inotify_event) + e->len) { + + int fd = -1; + + e = (const struct inotify_event *) p; + DBG(MONITOR, ul_debugobj(mn, " inotify event 0x%x [%s]\n", e->mask, e->len ? e->name : "")); + + if (e->mask & IN_CLOSE_NOWRITE) + status = 1; + else { + /* event on lock file */ + userspace_add_watch(me, &status, &fd); + + if (fd != e->wd) { + DBG(MONITOR, ul_debugobj(mn, " removing watch [fd=%d]", e->wd)); + inotify_rm_watch(me->fd, e->wd); + } + } + } + } while (1); + + DBG(MONITOR, ul_debugobj(mn, "%s", status == 1 ? " success" : " nothing")); + return status; +} + +/* + * userspace monitor operations + */ +static const struct monitor_opers userspace_opers = { + .op_get_fd = userspace_monitor_get_fd, + .op_close_fd = userspace_monitor_close_fd, + .op_event_verify = userspace_event_verify +}; + +/** + * mnt_monitor_enable_userspace: + * @mn: monitor + * @enable: 0 or 1 + * @filename: overwrites default + * + * Enables or disables userspace monitoring. If the userspace monitor does not + * exist and enable=1 then allocates new resources necessary for the monitor. + * + * If the top-level monitor has been already created (by mnt_monitor_get_fd() + * or mnt_monitor_wait()) then it's updated according to @enable. + * + * The @filename is used only the first time when you enable the monitor. It's + * impossible to have more than one userspace monitor. The recommended is to + * use NULL as filename. + * + * The userspace monitor is unsupported for systems with classic regular + * /etc/mtab file. + * + * Return: 0 on success and <0 on error + */ +int mnt_monitor_enable_userspace(struct libmnt_monitor *mn, int enable, const char *filename) +{ + struct monitor_entry *me; + int rc = 0; + + if (!mn) + return -EINVAL; + + me = monitor_get_entry(mn, MNT_MONITOR_TYPE_USERSPACE); + if (me) { + rc = monitor_modify_epoll(mn, me, enable); + if (!enable) + userspace_monitor_close_fd(mn, me); + return rc; + } + if (!enable) + return 0; + + DBG(MONITOR, ul_debugobj(mn, "allocate new userspace monitor")); + + if (!filename) + filename = mnt_get_utab_path(); /* /run/mount/utab */ + if (!filename) { + DBG(MONITOR, ul_debugobj(mn, "failed to get userspace mount table path")); + return -EINVAL; + } + + me = monitor_new_entry(mn); + if (!me) + goto err; + + me->type = MNT_MONITOR_TYPE_USERSPACE; + me->opers = &userspace_opers; + me->events = EPOLLIN; + me->path = strdup(filename); + if (!me->path) + goto err; + + return monitor_modify_epoll(mn, me, TRUE); +err: + rc = -errno; + free_monitor_entry(me); + DBG(MONITOR, ul_debugobj(mn, "failed to allocate userspace monitor [rc=%d]", rc)); + return rc; +} + + +/* + * Kernel monitor + */ + +static int kernel_monitor_close_fd(struct libmnt_monitor *mn __attribute__((__unused__)), + struct monitor_entry *me) +{ + assert(me); + + if (me->fd >= 0) + close(me->fd); + me->fd = -1; + return 0; +} + +static int kernel_monitor_get_fd(struct libmnt_monitor *mn, + struct monitor_entry *me) +{ + int rc; + + if (!me || me->enable == 0) /* not-initialized or disabled */ + return -EINVAL; + if (me->fd >= 0) + return me->fd; /* already initialized */ + + assert(me->path); + DBG(MONITOR, ul_debugobj(mn, " open kernel monitor for %s", me->path)); + + me->fd = open(me->path, O_RDONLY|O_CLOEXEC); + if (me->fd < 0) + goto err; + + return me->fd; +err: + rc = -errno; + DBG(MONITOR, ul_debugobj(mn, "failed to create kernel monitor [rc=%d]", rc)); + return rc; +} + +/* + * kernel monitor operations + */ +static const struct monitor_opers kernel_opers = { + .op_get_fd = kernel_monitor_get_fd, + .op_close_fd = kernel_monitor_close_fd, +}; + +/** + * mnt_monitor_enable_kernel: + * @mn: monitor + * @enable: 0 or 1 + * + * Enables or disables kernel VFS monitoring. If the monitor does not exist and + * enable=1 then allocates new resources necessary for the monitor. + * + * If the top-level monitor has been already created (by mnt_monitor_get_fd() + * or mnt_monitor_wait()) then it's updated according to @enable. + * + * Return: 0 on success and <0 on error + */ +int mnt_monitor_enable_kernel(struct libmnt_monitor *mn, int enable) +{ + struct monitor_entry *me; + int rc = 0; + + if (!mn) + return -EINVAL; + + me = monitor_get_entry(mn, MNT_MONITOR_TYPE_KERNEL); + if (me) { + rc = monitor_modify_epoll(mn, me, enable); + if (!enable) + kernel_monitor_close_fd(mn, me); + return rc; + } + if (!enable) + return 0; + + DBG(MONITOR, ul_debugobj(mn, "allocate new kernel monitor")); + + /* create a new entry */ + me = monitor_new_entry(mn); + if (!me) + goto err; + + /* If you want to use epoll FD in another epoll then top level + * epoll_wait() will drain all events from low-level FD if the + * low-level FD is not added with EPOLLIN. It means without EPOLLIN it + * it's impossible to detect which low-level FD has been active. + * + * Unfortunately, use EPOLLIN for mountinfo is tricky because in this + * case kernel returns events all time (we don't read from the FD). + * The solution is to use also edge-triggered (EPOLLET) flag, then + * kernel generate events on mountinfo changes only. The disadvantage is + * that we have to drain initial event generated by EPOLLIN after + * epoll_ctl(ADD). See monitor_modify_epoll(). + */ + me->events = EPOLLIN | EPOLLET; + + me->type = MNT_MONITOR_TYPE_KERNEL; + me->opers = &kernel_opers; + me->path = strdup(_PATH_PROC_MOUNTINFO); + if (!me->path) + goto err; + + return monitor_modify_epoll(mn, me, TRUE); +err: + rc = -errno; + free_monitor_entry(me); + DBG(MONITOR, ul_debugobj(mn, "failed to allocate kernel monitor [rc=%d]", rc)); + return rc; +} + +/* + * Add/Remove monitor entry to/from monitor epoll. + */ +static int monitor_modify_epoll(struct libmnt_monitor *mn, + struct monitor_entry *me, int enable) +{ + assert(mn); + assert(me); + + me->enable = enable ? 1 : 0; + me->changed = 0; + + if (mn->fd < 0) + return 0; /* no epoll, ignore request */ + + if (enable) { + struct epoll_event ev = { .events = me->events }; + int fd = me->opers->op_get_fd(mn, me); + + if (fd < 0) + goto err; + + DBG(MONITOR, ul_debugobj(mn, " add fd=%d (for %s)", fd, me->path)); + + ev.data.ptr = (void *) me; + + if (epoll_ctl(mn->fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + if (errno != EEXIST) + goto err; + } + if (me->events & (EPOLLIN | EPOLLET)) { + /* Drain initial events generated for /proc/self/mountinfo */ + struct epoll_event events[1]; + while (epoll_wait(mn->fd, events, 1, 0) > 0); + } + } else if (me->fd) { + DBG(MONITOR, ul_debugobj(mn, " remove fd=%d (for %s)", me->fd, me->path)); + if (epoll_ctl(mn->fd, EPOLL_CTL_DEL, me->fd, NULL) < 0) { + if (errno != ENOENT) + goto err; + } + } + + return 0; +err: + return -errno; +} + +/** + * mnt_monitor_close_fd: + * @mn: monitor + * + * Close monitor file descriptor. This is usually unnecessary, because + * mnt_unref_monitor() cleanups all. + * + * The function is necessary only if you want to reset monitor setting. The + * next mnt_monitor_get_fd() or mnt_monitor_wait() will use newly initialized + * monitor. This restart is unnecessary for mnt_monitor_enable_*() functions. + * + * Returns: 0 on success, <0 on error. + */ +int mnt_monitor_close_fd(struct libmnt_monitor *mn) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + + if (!mn) + return -EINVAL; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + /* disable all monitor entries */ + while (monitor_next_entry(mn, &itr, &me) == 0) { + + /* remove entry from epoll */ + if (mn->fd >= 0) + monitor_modify_epoll(mn, me, FALSE); + + /* close entry FD */ + me->opers->op_close_fd(mn, me); + } + + if (mn->fd >= 0) { + DBG(MONITOR, ul_debugobj(mn, "closing top-level monitor fd")); + close(mn->fd); + } + mn->fd = -1; + return 0; +} + +/** + * mnt_monitor_get_fd: + * @mn: monitor + * + * The file descriptor is associated with all monitored files and it's usable + * for example for epoll. You have to call mnt_monitor_event_cleanup() or + * mnt_monitor_next_change() after each event. + * + * Returns: >=0 (fd) on success, <0 on error + */ +int mnt_monitor_get_fd(struct libmnt_monitor *mn) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + int rc = 0; + + if (!mn) + return -EINVAL; + if (mn->fd >= 0) + return mn->fd; + + DBG(MONITOR, ul_debugobj(mn, "create top-level monitor fd")); + mn->fd = epoll_create1(EPOLL_CLOEXEC); + if (mn->fd < 0) + return -errno; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + DBG(MONITOR, ul_debugobj(mn, "adding monitor entries to epoll (fd=%d)", mn->fd)); + while (monitor_next_entry(mn, &itr, &me) == 0) { + if (!me->enable) + continue; + rc = monitor_modify_epoll(mn, me, TRUE); + if (rc) + goto err; + } + + DBG(MONITOR, ul_debugobj(mn, "successfully created monitor")); + return mn->fd; +err: + rc = errno ? -errno : -EINVAL; + close(mn->fd); + mn->fd = -1; + DBG(MONITOR, ul_debugobj(mn, "failed to create monitor [rc=%d]", rc)); + return rc; +} + +/** + * mnt_monitor_wait: + * @mn: monitor + * @timeout: number of milliseconds, -1 block indefinitely, 0 return immediately + * + * Waits for the next change, after the event it's recommended to use + * mnt_monitor_next_change() to get more details about the change and to + * avoid false positive events. + * + * Returns: 1 success (something changed), 0 timeout, <0 error. + */ +int mnt_monitor_wait(struct libmnt_monitor *mn, int timeout) +{ + int rc; + struct monitor_entry *me; + struct epoll_event events[1]; + + if (!mn) + return -EINVAL; + + if (mn->fd < 0) { + rc = mnt_monitor_get_fd(mn); + if (rc < 0) + return rc; + } + + do { + DBG(MONITOR, ul_debugobj(mn, "calling epoll_wait(), timeout=%d", timeout)); + rc = epoll_wait(mn->fd, events, 1, timeout); + if (rc < 0) + return -errno; /* error */ + if (rc == 0) + return 0; /* timeout */ + + me = (struct monitor_entry *) events[0].data.ptr; + if (!me) + return -EINVAL; + + if (me->opers->op_event_verify == NULL || + me->opers->op_event_verify(mn, me) == 1) { + me->changed = 1; + break; + } + } while (1); + + return 1; /* success */ +} + + +static struct monitor_entry *get_changed(struct libmnt_monitor *mn) +{ + struct libmnt_iter itr; + struct monitor_entry *me; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (monitor_next_entry(mn, &itr, &me) == 0) { + if (me->changed) + return me; + } + return NULL; +} + +/** + * mnt_monitor_next_change: + * @mn: monitor + * @filename: returns changed file (optional argument) + * @type: returns MNT_MONITOR_TYPE_* (optional argument) + * + * The function does not wait and it's designed to provide details about changes. + * It's always recommended to use this function to avoid false positives. + * + * Returns: 0 on success, 1 no change, <0 on error + */ +int mnt_monitor_next_change(struct libmnt_monitor *mn, + const char **filename, + int *type) +{ + int rc; + struct monitor_entry *me; + + if (!mn || mn->fd < 0) + return -EINVAL; + + /* + * if we previously called epoll_wait() (e.g. mnt_monitor_wait()) then + * info about unread change is already stored in monitor_entry. + * + * If we get nothing, then ask kernel. + */ + me = get_changed(mn); + while (!me) { + struct epoll_event events[1]; + + DBG(MONITOR, ul_debugobj(mn, "asking for next changed")); + + rc = epoll_wait(mn->fd, events, 1, 0); /* no timeout! */ + if (rc < 0) { + DBG(MONITOR, ul_debugobj(mn, " *** error")); + return -errno; + } + if (rc == 0) { + DBG(MONITOR, ul_debugobj(mn, " *** nothing")); + return 1; + } + + me = (struct monitor_entry *) events[0].data.ptr; + if (!me) + return -EINVAL; + + if (me->opers->op_event_verify != NULL && + me->opers->op_event_verify(mn, me) != 1) + me = NULL; + } + + me->changed = 0; + + if (filename) + *filename = me->path; + if (type) + *type = me->type; + + DBG(MONITOR, ul_debugobj(mn, " *** success [changed: %s]", me->path)); + return 0; +} + +/** + * mnt_monitor_event_cleanup: + * @mn: monitor + * + * This function cleanups (drain) internal buffers. It's necessary to call + * this function after event if you do not call mnt_monitor_next_change(). + * + * Returns: 0 on success, <0 on error + */ +int mnt_monitor_event_cleanup(struct libmnt_monitor *mn) +{ + int rc; + + if (!mn || mn->fd < 0) + return -EINVAL; + + while ((rc = mnt_monitor_next_change(mn, NULL, NULL)) == 0); + return rc < 0 ? rc : 0; +} + +#ifdef TEST_PROGRAM + +static struct libmnt_monitor *create_test_monitor(int argc, char *argv[]) +{ + struct libmnt_monitor *mn; + int i; + + mn = mnt_new_monitor(); + if (!mn) { + warn("failed to allocate monitor"); + goto err; + } + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "userspace") == 0) { + if (mnt_monitor_enable_userspace(mn, TRUE, NULL)) { + warn("failed to initialize userspace monitor"); + goto err; + } + + } else if (strcmp(argv[i], "kernel") == 0) { + if (mnt_monitor_enable_kernel(mn, TRUE)) { + warn("failed to initialize kernel monitor"); + goto err; + } + } + } + if (i == 1) { + warnx("No monitor type specified"); + goto err; + } + + return mn; +err: + mnt_unref_monitor(mn); + return NULL; +} + +/* + * create a monitor and add the monitor fd to epoll + */ +static int __test_epoll(struct libmnt_test *ts, int argc, char *argv[], int cleanup) +{ + int fd, efd = -1, rc = -1; + struct epoll_event ev; + struct libmnt_monitor *mn = create_test_monitor(argc, argv); + + if (!mn) + return -1; + + fd = mnt_monitor_get_fd(mn); + if (fd < 0) { + warn("failed to initialize monitor fd"); + goto done; + } + + efd = epoll_create1(EPOLL_CLOEXEC); + if (efd < 0) { + warn("failed to create epoll"); + goto done; + } + + ev.events = EPOLLIN; + ev.data.fd = fd; + + rc = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev); + if (rc < 0) { + warn("failed to add fd to epoll"); + goto done; + } + + printf("waiting for changes...\n"); + do { + const char *filename = NULL; + struct epoll_event events[1]; + int n = epoll_wait(efd, events, 1, -1); + + if (n < 0) { + rc = -errno; + warn("polling error"); + goto done; + } + if (n == 0 || events[0].data.fd != fd) + continue; + + printf(" top-level FD active\n"); + if (cleanup) + mnt_monitor_event_cleanup(mn); + else { + while (mnt_monitor_next_change(mn, &filename, NULL) == 0) + printf(" %s: change detected\n", filename); + } + } while (1); + + rc = 0; +done: + if (efd >= 0) + close(efd); + mnt_unref_monitor(mn); + return rc; +} + +/* + * create a monitor and add the monitor fd to epoll + */ +static int test_epoll(struct libmnt_test *ts, int argc, char *argv[]) +{ + return __test_epoll(ts, argc, argv, 0); +} + +static int test_epoll_cleanup(struct libmnt_test *ts, int argc, char *argv[]) +{ + return __test_epoll(ts, argc, argv, 1); +} + +/* + * create a monitor and wait for a change + */ +static int test_wait(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *filename; + struct libmnt_monitor *mn = create_test_monitor(argc, argv); + + if (!mn) + return -1; + + printf("waiting for changes...\n"); + while (mnt_monitor_wait(mn, -1) > 0) { + printf("notification detected\n"); + + while (mnt_monitor_next_change(mn, &filename, NULL) == 0) + printf(" %s: change detected\n", filename); + + } + mnt_unref_monitor(mn); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--epoll", test_epoll, "<userspace kernel ...> monitor in epoll" }, + { "--epoll-clean", test_epoll_cleanup, "<userspace kernel ...> monitor in epoll and clean events" }, + { "--wait", test_wait, "<userspace kernel ...> monitor wait function" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h new file mode 100644 index 0000000..339e276 --- /dev/null +++ b/libmount/src/mountP.h @@ -0,0 +1,685 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * mountP.h - private library header file + * + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 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. + */ +#ifndef _LIBMOUNT_PRIVATE_H +#define _LIBMOUNT_PRIVATE_H + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/vfs.h> +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> + +#include "c.h" +#include "list.h" +#include "debug.h" +#include "buffer.h" +#include "libmount.h" + +/* + * Debug + */ +#define MNT_DEBUG_HELP (1 << 0) +#define MNT_DEBUG_INIT (1 << 1) +#define MNT_DEBUG_CACHE (1 << 2) +#define MNT_DEBUG_OPTIONS (1 << 3) +#define MNT_DEBUG_LOCKS (1 << 4) +#define MNT_DEBUG_TAB (1 << 5) +#define MNT_DEBUG_FS (1 << 6) +#define MNT_DEBUG_UPDATE (1 << 7) +#define MNT_DEBUG_UTILS (1 << 8) +#define MNT_DEBUG_CXT (1 << 9) +#define MNT_DEBUG_DIFF (1 << 10) +#define MNT_DEBUG_MONITOR (1 << 11) +#define MNT_DEBUG_BTRFS (1 << 12) +#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 0xFFFFFF + +UL_DEBUG_DECLARE_MASK(libmount); +#define DBG(m, x) __UL_DBG(libmount, MNT_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(libmount, MNT_DEBUG_, m, x) +#define DBG_FLUSH __UL_DBG_FLUSH(libmount, MNT_DEBUG_) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libmount) +#include "debugobj.h" + +/* + * NLS -- the library has to be independent on main program, so define + * UL_TEXTDOMAIN_EXPLICIT before you include nls.h. + * + * Now we use util-linux.po (=PACKAGE), rather than maintain the texts + * in the separate libmount.po file. + */ +#define LIBMOUNT_TEXTDOMAIN PACKAGE +#define UL_TEXTDOMAIN_EXPLICIT LIBMOUNT_TEXTDOMAIN +#include "nls.h" + + +/* extension for files in the directory */ +#define MNT_MNTTABDIR_EXT ".fstab" + +/* library private paths */ +#define MNT_RUNTIME_TOPDIR "/run" +/* private userspace mount table */ +#define MNT_PATH_UTAB MNT_RUNTIME_TOPDIR "/mount/utab" +/* temporary mount target */ +#define MNT_PATH_TMPTGT MNT_RUNTIME_TOPDIR "/mount/tmptgt" + +#define MNT_UTAB_HEADER "# libmount utab file\n" + +#ifdef TEST_PROGRAM +struct libmnt_test { + const char *name; + int (*body)(struct libmnt_test *ts, int argc, char *argv[]); + const char *usage; +}; + +/* test.c */ +extern int mnt_run_test(struct libmnt_test *tests, int argc, char *argv[]); +#endif + +/* utils.c */ +extern int mnt_valid_tagname(const char *tagname); + +extern const char *mnt_statfs_get_fstype(struct statfs *vfs); +extern int is_file_empty(const char *name); + +extern int mnt_is_readonly(const char *path) + __attribute__((nonnull)); + +extern int mnt_parse_offset(const char *str, size_t len, uintmax_t *res); + +extern int mnt_chdir_to_parent(const char *target, char **filename); + +extern char *mnt_get_username(const uid_t uid); +extern int mnt_get_uid(const char *username, uid_t *uid); +extern int mnt_get_gid(const char *groupname, gid_t *gid); +extern int mnt_parse_uid(const char *user, size_t user_len, uid_t *gid); +extern int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid); +extern int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *gid); +extern int mnt_in_group(gid_t gid); + +extern int mnt_open_uniq_filename(const char *filename, char **name); + +extern int mnt_has_regular_utab(const char **utab, int *writable); +extern const char *mnt_get_utab_path(void); + +extern int mnt_get_filesystems(char ***filesystems, const char *pattern); +extern void mnt_free_filesystems(char **filesystems); + +extern char *mnt_get_kernel_cmdline_option(const char *name); + +extern int mnt_safe_stat(const char *target, struct stat *st); +extern int mnt_safe_lstat(const char *target, struct stat *st); +extern int mnt_is_path(const char *target); + +extern int mnt_tmptgt_unshare(int *old_ns_fd); +extern int mnt_tmptgt_cleanup(int old_ns_fd); + +/* tab.c */ +extern int is_mountinfo(struct libmnt_table *tb); +extern int mnt_table_set_parser_fltrcb( struct libmnt_table *tb, + int (*cb)(struct libmnt_fs *, void *), + void *data); + +extern int __mnt_table_parse_mountinfo(struct libmnt_table *tb, + const char *filename, + struct libmnt_table *u_tb); + +extern struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb, + struct libmnt_fs *fs, + unsigned long mountflags, + char **fsroot); + +extern int __mnt_table_is_fs_mounted( struct libmnt_table *tb, + struct libmnt_fs *fstab_fs, + const char *tgt_prefix); + +extern int mnt_table_enable_noautofs(struct libmnt_table *tb, int ignore); +extern int mnt_table_is_noautofs(struct libmnt_table *tb); + +/* + * Generic iterator + */ +struct libmnt_iter { + struct list_head *p; /* current position */ + struct list_head *head; /* start position */ + int direction; /* MNT_ITER_{FOR,BACK}WARD */ +}; + +#define IS_ITER_FORWARD(_i) ((_i)->direction == MNT_ITER_FORWARD) +#define IS_ITER_BACKWARD(_i) ((_i)->direction == MNT_ITER_BACKWARD) + +#define MNT_ITER_INIT(itr, list) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (list)->next : (list)->prev; \ + (itr)->head = (list); \ + } while(0) + +#define MNT_ITER_GET_ENTRY(itr, restype, member) \ + list_entry((itr)->p, restype, member) + +#define MNT_ITER_ITERATE(itr) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (itr)->p->next : (itr)->p->prev; \ + } while(0) + + +/* + * This struct represents one entry in a fstab/mountinfo file. + * (note that fstab[1] means the first column from fstab, and so on...) + */ +struct libmnt_fs { + struct list_head ents; + struct libmnt_table *tab; + + int refcount; /* reference counter */ + + unsigned int opts_age; /* to sync with optlist */ + struct libmnt_optlist *optlist; + + int id; /* mountinfo[1]: ID */ + int parent; /* mountinfo[2]: parent */ + dev_t devno; /* mountinfo[3]: st_dev */ + + char *bindsrc; /* utab, full path from fstab[1] for bind mounts */ + + char *source; /* fstab[1], mountinfo[10], swaps[1]: + * source dev, file, dir or TAG */ + char *tagname; /* fstab[1]: tag name - "LABEL", "UUID", ..*/ + char *tagval; /* tag value */ + + char *root; /* mountinfo[4]: root of the mount within the FS */ + char *target; /* mountinfo[5], fstab[2]: mountpoint */ + char *fstype; /* mountinfo[9], fstab[3]: filesystem type */ + + char *optstr; /* fstab[4], merged options */ + char *vfs_optstr; /* mountinfo[6]: fs-independent (VFS) options */ + char *opt_fields; /* mountinfo[7]: optional fields */ + char *fs_optstr; /* mountinfo[11]: fs-dependent options */ + char *user_optstr; /* userspace mount options */ + char *attrs; /* mount attributes */ + + int freq; /* fstab[5]: dump frequency in days */ + int passno; /* fstab[6]: pass number on parallel fsck */ + + /* /proc/swaps */ + char *swaptype; /* swaps[2]: device type (partition, file, ...) */ + off_t size; /* swaps[3]: swaparea size */ + off_t usedsize; /* swaps[4]: used size */ + int priority; /* swaps[5]: swap priority */ + + int flags; /* MNT_FS_* flags */ + pid_t tid; /* /proc/<tid>/mountinfo otherwise zero */ + + char *comment; /* fstab comment */ + + void *userdata; /* library independent data */ +}; + +/* + * fs flags + */ +#define MNT_FS_PSEUDO (1 << 1) /* pseudo filesystem */ +#define MNT_FS_NET (1 << 2) /* network filesystem */ +#define MNT_FS_SWAP (1 << 3) /* swap device */ +#define MNT_FS_KERNEL (1 << 4) /* data from /proc/{mounts,self/mountinfo} */ +#define MNT_FS_MERGED (1 << 5) /* already merged data from /run/mount/utab */ + +/* + * fstab/mountinfo file + */ +struct libmnt_table { + int fmt; /* MNT_FMT_* file format */ + int nents; /* number of entries */ + int refcount; /* reference counter */ + int comms; /* enable/disable comment parsing */ + char *comm_intro; /* First comment in file */ + char *comm_tail; /* Last comment in file */ + + struct libmnt_cache *cache; /* canonicalized paths/tags cache */ + + int (*errcb)(struct libmnt_table *tb, + const char *filename, int line); + + int (*fltrcb)(struct libmnt_fs *fs, void *data); + void *fltrcb_data; + + int noautofs; /* ignore autofs mounts */ + + struct list_head ents; /* list of entries (libmnt_fs) */ + void *userdata; +}; + +extern struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt, int empty_for_enoent); + +/* + * Tab file format + */ +enum { + MNT_FMT_GUESS, + MNT_FMT_FSTAB, /* /etc/{fs,m}tab */ + MNT_FMT_MTAB = MNT_FMT_FSTAB, /* alias */ + MNT_FMT_MOUNTINFO, /* /proc/#/mountinfo */ + MNT_FMT_UTAB, /* /run/mount/utab */ + MNT_FMT_SWAPS /* /proc/swaps */ +}; + +/* + * Context hooks + * + * TODO: this will be public one day when libmount will support modules for + * stuff like veritydev.c. + */ +enum { + MNT_STAGE_PREP_SOURCE = 1, /* mount source preparation */ + MNT_STAGE_PREP_TARGET, /* mount target preparation */ + MNT_STAGE_PREP_OPTIONS, /* mount options preparation */ + MNT_STAGE_PREP, /* all prepared */ + + MNT_STAGE_MOUNT_PRE = 100, /* before mount */ + MNT_STAGE_MOUNT, /* mount(2) or fsmount(2) or tree-clone */ + MNT_STAGE_MOUNT_POST, /* after mount */ + + MNT_STAGE_POST = 200 /* all is done */ +}; + +struct libmnt_hookset { + const char *name; /* hook set name */ + + int firststage; + int (*firstcall)(struct libmnt_context *, const struct libmnt_hookset *, void *); + + int (*deinit)(struct libmnt_context *, const struct libmnt_hookset *); /* cleanup function */ +}; + +/* built-in hooks */ +extern const struct libmnt_hookset hookset_mount_legacy; +extern const struct libmnt_hookset hookset_mount; +extern const struct libmnt_hookset hookset_mkdir; +extern const struct libmnt_hookset hookset_subdir; +extern const struct libmnt_hookset hookset_owner; +extern const struct libmnt_hookset hookset_idmap; +extern const struct libmnt_hookset hookset_loopdev; +#ifdef HAVE_CRYPTSETUP +extern const struct libmnt_hookset hookset_veritydev; +#endif +#ifdef HAVE_LIBSELINUX +extern const struct libmnt_hookset hookset_selinux; +#endif + +extern int mnt_context_deinit_hooksets(struct libmnt_context *cxt); +extern const struct libmnt_hookset *mnt_context_get_hookset(struct libmnt_context *cxt, const char *name); + +extern int mnt_context_set_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + void *data); + +extern void *mnt_context_get_hookset_data(struct libmnt_context *cxt, + const struct libmnt_hookset *hs); + +extern int mnt_context_has_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data); + +extern int mnt_context_append_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)); +extern int mnt_context_insert_hook(struct libmnt_context *cxt, + const char *after, + const struct libmnt_hookset *hs, + int stage, + void *data, + int (*func)(struct libmnt_context *, + const struct libmnt_hookset *, + void *)); + +extern int mnt_context_remove_hook(struct libmnt_context *cxt, + const struct libmnt_hookset *hs, + int stage, + void **data); +extern int mnt_context_call_hooks(struct libmnt_context *cxt, int stage); + +/* + * Namespace + */ +struct libmnt_ns { + int fd; /* file descriptor of namespace, -1 when inactive */ + struct libmnt_cache *cache; /* paths cache associated with NS */ +}; + +/* + * Mount context -- high-level API + */ +struct libmnt_context +{ + int action; /* MNT_ACT_{MOUNT,UMOUNT} */ + int restricted; /* root or not? */ + + char *fstype_pattern; /* for mnt_match_fstype() */ + char *optstr_pattern; /* for mnt_match_options() */ + + struct libmnt_fs *fs; /* filesystem description (type, mountpoint, device, ...) */ + + struct libmnt_table *fstab; /* fstab entries */ + struct libmnt_table *mountinfo; /* already mounted filesystems */ + struct libmnt_table *utab; /* rarely used by umount only */ + + int (*table_errcb)(struct libmnt_table *tb, /* callback for libmnt_table structs */ + const char *filename, int line); + + int (*table_fltrcb)(struct libmnt_fs *fs, void *data); /* callback for libmnt_table structs */ + void *table_fltrcb_data; + + char *(*pwd_get_cb)(struct libmnt_context *); /* get encryption password */ + void (*pwd_release_cb)(struct libmnt_context *, char *); /* release password */ + + int optsmode; /* fstab optstr mode MNT_OPTSMODE_{AUTO,FORCE,IGNORE} */ + + const void *mountdata; /* final mount(2) data, string or binary data */ + + struct libmnt_cache *cache; /* paths cache */ + struct libmnt_lock *lock; /* utab lock */ + struct libmnt_update *update; /* utab update */ + + struct libmnt_optlist *optlist; /* parsed mount options */ + struct libmnt_optlist *optlist_saved; /* save/apply context template */ + + const struct libmnt_optmap *map_linux; /* system options map */ + const struct libmnt_optmap *map_userspace; /* userspace options map */ + + const char *mountinfo_path; /* usualy /proc/self/moutinfo */ + + const char *utab_path; /* path to utab */ + int utab_writable; /* is utab writable */ + + char *tgt_prefix; /* path used for all targets */ + + int flags; /* private context flags */ + + char *helper; /* name of the used /sbin/[u]mount.<type> helper */ + int helper_status; /* helper wait(2) status */ + int helper_exec_status; /* 1: not called yet, 0: success, <0: -errno */ + + pid_t *children; /* "mount -a --fork" PIDs */ + int nchildren; /* number of children */ + pid_t pid; /* 0=parent; PID=child */ + + int syscall_status; /* 1: not called yet, 0: success, <0: -errno */ + const char *syscall_name; /* failed syscall name */ + + struct libmnt_ns ns_orig; /* original namespace */ + struct libmnt_ns ns_tgt; /* target namespace */ + struct libmnt_ns *ns_cur; /* pointer to current namespace */ + + unsigned int enabled_textdomain : 1; /* bindtextdomain() called */ + unsigned int noautofs : 1; /* ignore autofs mounts */ + unsigned int has_selinux_opt : 1; /* temporary for broken fsconfig() syscall */ + unsigned int force_clone : 1; /* OPEN_TREE_CLONE */ + + struct list_head hooksets_datas; /* global hooksets data */ + struct list_head hooksets_hooks; /* global hooksets data */ +}; + +/* flags */ +#define MNT_FL_NOMTAB (1 << 1) +#define MNT_FL_FAKE (1 << 2) +#define MNT_FL_SLOPPY (1 << 3) +#define MNT_FL_VERBOSE (1 << 4) +#define MNT_FL_NOHELPERS (1 << 5) +#define MNT_FL_LOOPDEL (1 << 6) +#define MNT_FL_LAZY (1 << 7) +#define MNT_FL_FORCE (1 << 8) +#define MNT_FL_NOCANONICALIZE (1 << 9) +#define MNT_FL_RDONLY_UMOUNT (1 << 11) /* remount,ro after EBUSY umount(2) */ +#define MNT_FL_FORK (1 << 12) +#define MNT_FL_NOSWAPMATCH (1 << 13) +#define MNT_FL_RWONLY_MOUNT (1 << 14) /* explicit mount -w; never try read-only */ +#define MNT_FL_ONLYONCE (1 << 15) + +#define MNT_FL_MOUNTDATA (1 << 20) +#define MNT_FL_TAB_APPLIED (1 << 21) /* fstab merged to cxt->fs */ +#define MNT_FL_MOUNTFLAGS_MERGED (1 << 22) /* MS_* flags was read from optstr */ +#define MNT_FL_SAVED_USER (1 << 23) +#define MNT_FL_PREPARED (1 << 24) +#define MNT_FL_HELPER (1 << 25) /* [u]mount.<type> */ +#define MNT_FL_MOUNTOPTS_FIXED (1 << 27) +#define MNT_FL_TABPATHS_CHECKED (1 << 28) +#define MNT_FL_FORCED_RDONLY (1 << 29) /* mounted read-only on write-protected device */ +#define MNT_FL_VERITYDEV_READY (1 << 30) /* /dev/mapper/<FOO> initialized by the library */ + +/* default flags */ +#define MNT_FL_DEFAULT 0 + +/* Flags usable with MS_BIND|MS_REMOUNT */ +#define MNT_BIND_SETTABLE (MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_NOATIME|MS_NODIRATIME|MS_RELATIME|MS_RDONLY|MS_NOSYMFOLLOW) + +#define set_syscall_status(_cxt, _name, _x) __extension__ ({ \ + if (!(_x)) { \ + DBG(CXT, ul_debug("syscall '%s' [%m]", _name)); \ + (_cxt)->syscall_status = -errno; \ + (_cxt)->syscall_name = (_name); \ + } else { \ + DBG(CXT, ul_debug("syscall '%s' [success]", _name)); \ + (_cxt)->syscall_status = 0; \ + } \ + }) + +#define reset_syscall_status(_cxt) __extension__ ({ \ + DBG(CXT, ul_debug("reset syscall status")); \ + (_cxt)->syscall_status = 0; \ + (_cxt)->syscall_name = NULL; \ + }) + +/* optmap.c */ +extern const struct libmnt_optmap *mnt_optmap_get_entry( + struct libmnt_optmap const **maps, + int nmaps, + const char *name, + size_t namelen, + const struct libmnt_optmap **mapent); + +/* optstr.c */ +extern int mnt_optstr_remove_option_at(char **optstr, char *begin, char *end); + +extern int mnt_buffer_append_option(struct ul_buffer *buf, + const char *name, size_t namesz, + const char *val, size_t valsz, int quoted); + +/* 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 struct libmnt_optlist *mnt_copy_optlist(struct libmnt_optlist *ls); +extern int mnt_optlist_is_empty(struct libmnt_optlist *ls); +extern unsigned int mnt_optlist_get_age(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_remove_named(struct libmnt_optlist *ls, const char *name, + const struct libmnt_optmap *map); +extern int mnt_optlist_remove_flags(struct libmnt_optlist *ls, unsigned long flags, + const struct libmnt_optmap *map); +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, + const 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); +/* "what" argument */ +enum { + /* Default -- if @map specified then returns all options for the map, otherwise + * returns all options including uknonwn options, exclude external options */ + MNT_OL_FLTR_DFLT = 0, + /* Options as expected by mount.<type> helpers */ + MNT_OL_FLTR_HELPERS, + /* Options as expected in mtab */ + MNT_OL_FLTR_MTAB, + /* All options -- include mapped, unknown and external options */ + MNT_OL_FLTR_ALL, + /* All unknown options -- exclude external (usually FS specific options) */ + MNT_OL_FLTR_UNKNOWN, + + __MNT_OL_FLTR_COUNT /* keep it last */ +}; + + +extern int mnt_optlist_get_flags(struct libmnt_optlist *ls, unsigned long *flags, + const struct libmnt_optmap *map, unsigned int what); + +/* recursive status for mnt_optlist_get_attrs() */ +#define MNT_OL_REC 1 +#define MNT_OL_NOREC 2 + +extern int mnt_optlist_get_attrs(struct libmnt_optlist *ls, uint64_t *set, uint64_t *clr, int rec); + +extern int mnt_optlist_get_optstr(struct libmnt_optlist *ol, const char **optstr, + const struct libmnt_optmap *map, unsigned int what); +extern int mnt_optlist_strdup_optstr(struct libmnt_optlist *ls, char **optstr, + const struct libmnt_optmap *map, unsigned int what); + +extern int mnt_optlist_get_propagation(struct libmnt_optlist *ls); +extern int mnt_optlist_is_propagation_only(struct libmnt_optlist *ls); +extern int mnt_optlist_is_remount(struct libmnt_optlist *ls); +extern int mnt_optlist_is_recursive(struct libmnt_optlist *ls); +extern int mnt_optlist_is_bind(struct libmnt_optlist *ls); +extern int mnt_optlist_is_rbind(struct libmnt_optlist *ls); +extern int mnt_optlist_is_move(struct libmnt_optlist *ls); +extern int mnt_optlist_is_rdonly(struct libmnt_optlist *ls); +extern int mnt_optlist_is_silent(struct libmnt_optlist *ls); + +extern int mnt_optlist_merge_opts(struct libmnt_optlist *ls); + +extern int mnt_opt_has_value(struct libmnt_opt *opt); +extern const char *mnt_opt_get_value(struct libmnt_opt *opt); +extern const char *mnt_opt_get_name(struct libmnt_opt *opt); +extern const struct libmnt_optmap *mnt_opt_get_map(struct libmnt_opt *opt); +extern const struct libmnt_optmap *mnt_opt_get_mapent(struct libmnt_opt *opt); +extern int mnt_opt_set_external(struct libmnt_opt *opt, int enable); +extern int mnt_opt_set_value(struct libmnt_opt *opt, const char *str); +extern int mnt_opt_set_u64value(struct libmnt_opt *opt, uint64_t num); +extern int mnt_opt_set_quoted_value(struct libmnt_opt *opt, const char *str); +extern int mnt_opt_is_external(struct libmnt_opt *opt); + +/* fs.c */ +extern int mnt_fs_follow_optlist(struct libmnt_fs *fs, struct libmnt_optlist *ol); +extern struct libmnt_fs *mnt_copy_mtab_fs(struct libmnt_fs *fs); +extern int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source) + __attribute__((nonnull(1))); +extern int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype) + __attribute__((nonnull(1))); +extern int __mnt_fs_set_target_ptr(struct libmnt_fs *fs, char *tgt) + __attribute__((nonnull(1))); + +/* context.c */ +extern struct libmnt_context *mnt_copy_context(struct libmnt_context *o); +extern int mnt_context_utab_writable(struct libmnt_context *cxt); +extern const char *mnt_context_get_writable_tabpath(struct libmnt_context *cxt); + +extern int mnt_context_get_mountinfo(struct libmnt_context *cxt, struct libmnt_table **tb); +extern int mnt_context_get_mountinfo_for_target(struct libmnt_context *cxt, + struct libmnt_table **mountinfo, const char *tgt); + +extern int mnt_context_prepare_srcpath(struct libmnt_context *cxt); +extern int mnt_context_guess_srcpath_fstype(struct libmnt_context *cxt, char **type); +extern int mnt_context_guess_fstype(struct libmnt_context *cxt); +extern int mnt_context_prepare_helper(struct libmnt_context *cxt, + const char *name, const char *type); +extern int mnt_context_prepare_update(struct libmnt_context *cxt); +extern int mnt_context_merge_mflags(struct libmnt_context *cxt); +extern int mnt_context_update_tabs(struct libmnt_context *cxt); + +extern int mnt_context_umount_setopt(struct libmnt_context *cxt, int c, char *arg); +extern int mnt_context_mount_setopt(struct libmnt_context *cxt, int c, char *arg); + +extern int mnt_context_propagation_only(struct libmnt_context *cxt) + __attribute__((nonnull)); + +extern int mnt_context_delete_loopdev(struct libmnt_context *cxt); + +extern int mnt_fork_context(struct libmnt_context *cxt); + +extern int mnt_context_set_tabfilter(struct libmnt_context *cxt, + int (*fltr)(struct libmnt_fs *, void *), + void *data); + +extern int mnt_context_get_generic_excode(int rc, char *buf, size_t bufsz, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 4, 5))); +extern int mnt_context_get_mount_excode(struct libmnt_context *cxt, int mntrc, char *buf, size_t bufsz); +extern int mnt_context_get_umount_excode(struct libmnt_context *cxt, int mntrc, char *buf, size_t bufsz); + +extern int mnt_context_has_template(struct libmnt_context *cxt); +extern int mnt_context_apply_template(struct libmnt_context *cxt); +extern int mnt_context_save_template(struct libmnt_context *cxt); + +extern int mnt_context_apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs); + +extern struct libmnt_optlist *mnt_context_get_optlist(struct libmnt_context *cxt); + +/* tab_update.c */ +extern int mnt_update_set_filename(struct libmnt_update *upd, const char *filename); +extern int mnt_update_already_done(struct libmnt_update *upd, + struct libmnt_lock *lc); + +#if __linux__ +/* btrfs.c */ +extern uint64_t btrfs_get_default_subvol_id(const char *path); +#endif + +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT +/* fsconfig/fsopen based stuff */ +struct libmnt_sysapi { + int fd_fs; /* FD from fsopen() or fspick() */ + int fd_tree; /* FD from fsmount() or open_tree() */ + + unsigned int is_new_fs : 1 ; /* fd_fs comes from fsopen() */ +}; + +static inline struct libmnt_sysapi *mnt_context_get_sysapi(struct libmnt_context *cxt) +{ + return mnt_context_get_hookset_data(cxt, &hookset_mount); +} +#endif + +#endif /* _LIBMOUNT_PRIVATE_H */ 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 */ diff --git a/libmount/src/optmap.c b/libmount/src/optmap.c new file mode 100644 index 0000000..d7569a0 --- /dev/null +++ b/libmount/src/optmap.c @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2010-2018 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. + */ + +/** + * SECTION: optmap + * @title: Option maps + * @short_description: description for mount options + * + * The mount(2) linux syscall uses two arguments for mount options: + * + * @mountflags: (see MS_* macros in linux/fs.h) + * + * @mountdata: (usually a comma separated string of options) + * + * The libmount uses options-map(s) to describe mount options. + * + * The option description (map entry) includes: + * + * @name: and argument name + * + * @id: (in the map unique identifier or a mountflags, e.g MS_RDONLY) + * + * @mask: (MNT_INVERT, MNT_NOMTAB) + * + * The option argument value is defined by: + * + * "=" -- required argument, e.g "comment=" + * + * "[=]" -- optional argument, e.g. "loop[=]" + * + * Example: + * + * <informalexample> + * <programlisting> + * #define MY_MS_FOO (1 << 1) + * #define MY_MS_BAR (1 << 2) + * + * libmnt_optmap myoptions[] = { + * { "foo", MY_MS_FOO }, + * { "nofoo", MY_MS_FOO | MNT_INVERT }, + * { "bar=", MY_MS_BAR }, + * { NULL } + * }; + * </programlisting> + * </informalexample> + * + * The libmount defines two basic built-in options maps: + * + * @MNT_LINUX_MAP: fs-independent kernel mount options (usually MS_* flags) + * + * @MNT_USERSPACE_MAP: userspace specific mount options (e.g. "user", "loop") + * + * For more details about option map struct see "struct mnt_optmap" in + * mount/mount.h. + */ +#include "mountP.h" +#include "strutils.h" + +/* + * fs-independent mount flags (built-in MNT_LINUX_MAP) + */ +static const struct libmnt_optmap linux_flags_map[] = +{ + { "ro", MS_RDONLY }, /* read-only */ + { "rw", MS_RDONLY, MNT_INVERT }, /* read-write */ + { "exec", MS_NOEXEC, MNT_INVERT }, /* permit execution of binaries */ + { "noexec", MS_NOEXEC }, /* don't execute binaries */ + { "suid", MS_NOSUID, MNT_INVERT }, /* honor suid executables */ + { "nosuid", MS_NOSUID }, /* don't honor suid executables */ + { "dev", MS_NODEV, MNT_INVERT }, /* interpret device files */ + { "nodev", MS_NODEV }, /* don't interpret devices */ + + { "sync", MS_SYNCHRONOUS, MNT_SUPERBLOCK }, /* synchronous I/O */ + { "async", MS_SYNCHRONOUS, MNT_INVERT | MNT_SUPERBLOCK },/* asynchronous I/O */ + + { "dirsync", MS_DIRSYNC, MNT_SUPERBLOCK },/* synchronous directory modifications */ + { "remount", MS_REMOUNT, MNT_NOMTAB }, /* alter flags of mounted FS */ + { "bind", MS_BIND }, /* Remount part of the tree elsewhere */ + { "rbind", MS_BIND | MS_REC }, /* Idem, plus mounted subtrees */ +#ifdef MS_NOSUB + { "sub", MS_NOSUB, MNT_INVERT }, /* allow submounts */ + { "nosub", MS_NOSUB }, /* don't allow submounts */ +#endif +#ifdef MS_SILENT + { "silent", MS_SILENT }, /* be quiet */ + { "loud", MS_SILENT, MNT_INVERT }, /* print out messages. */ +#endif +#ifdef MS_MANDLOCK + { "mand", MS_MANDLOCK, MNT_SUPERBLOCK }, /* Allow mandatory locks on this FS */ + { "nomand", MS_MANDLOCK, MNT_INVERT | MNT_SUPERBLOCK}, /* Forbid mandatory locks on this FS */ +#endif +#ifdef MS_NOATIME + { "atime", MS_NOATIME, MNT_INVERT }, /* Update access time */ + { "noatime", MS_NOATIME }, /* Do not update access time */ +#endif +#ifdef MS_I_VERSION + { "iversion", MS_I_VERSION }, /* Update inode I_version time */ + { "noiversion", MS_I_VERSION, MNT_INVERT},/* Don't update inode I_version time */ +#endif +#ifdef MS_NODIRATIME + { "diratime", MS_NODIRATIME, MNT_INVERT }, /* Update dir access times */ + { "nodiratime", MS_NODIRATIME }, /* Do not update dir access times */ +#endif +#ifdef MS_RELATIME + { "relatime", MS_RELATIME }, /* Update access times relative to mtime/ctime */ + { "norelatime", MS_RELATIME, MNT_INVERT }, /* Update access time without regard to mtime/ctime */ +#endif +#ifdef MS_STRICTATIME + { "strictatime", MS_STRICTATIME }, /* Strict atime semantics */ + { "nostrictatime", MS_STRICTATIME, MNT_INVERT }, /* kernel default atime */ +#endif +#ifdef MS_LAZYTIME + { "lazytime", MS_LAZYTIME, MNT_SUPERBLOCK }, /* Update {a,m,c}time on the in-memory inode only */ + { "nolazytime", MS_LAZYTIME, MNT_INVERT | MNT_SUPERBLOCK }, +#endif +#ifdef MS_PROPAGATION + { "unbindable", MS_UNBINDABLE, MNT_NOHLPS | MNT_NOMTAB }, /* Unbindable */ + { "runbindable", MS_UNBINDABLE | MS_REC, MNT_NOHLPS | MNT_NOMTAB }, + { "private", MS_PRIVATE, MNT_NOHLPS | MNT_NOMTAB }, /* Private */ + { "rprivate", MS_PRIVATE | MS_REC, MNT_NOHLPS | MNT_NOMTAB }, + { "slave", MS_SLAVE, MNT_NOHLPS | MNT_NOMTAB }, /* Slave */ + { "rslave", MS_SLAVE | MS_REC, MNT_NOHLPS | MNT_NOMTAB }, + { "shared", MS_SHARED, MNT_NOHLPS | MNT_NOMTAB }, /* Shared */ + { "rshared", MS_SHARED | MS_REC, MNT_NOHLPS | MNT_NOMTAB }, +#endif +#ifdef MS_NOSYMFOLLOW + { "symfollow", MS_NOSYMFOLLOW, MNT_INVERT }, /* Don't follow symlinks */ + { "nosymfollow", MS_NOSYMFOLLOW }, +#endif +#ifdef MS_MOVE + { "move", MS_MOVE, MNT_NOHLPS | MNT_NOMTAB | MNT_NOFSTAB }, /* --move */ +#endif + { NULL, 0, 0 } +}; + +/* + * userspace mount option (built-in MNT_USERSPACE_MAP) + */ +static const struct libmnt_optmap userspace_opts_map[] = +{ + { "defaults", 0, MNT_NOHLPS }, /* default options */ + + { "auto", MNT_MS_NOAUTO, MNT_NOHLPS | MNT_INVERT | MNT_NOMTAB }, /* Can be mounted using -a */ + { "noauto", MNT_MS_NOAUTO, MNT_NOHLPS | MNT_NOMTAB }, /* Can only be mounted explicitly */ + + { "user[=]", MNT_MS_USER }, /* Allow ordinary user to mount (mtab) */ + { "nouser", MNT_MS_USER, MNT_INVERT | MNT_NOMTAB }, /* Forbid ordinary user to mount */ + + { "users", MNT_MS_USERS, MNT_NOMTAB }, /* Allow ordinary users to mount */ + { "nousers", MNT_MS_USERS, MNT_INVERT | MNT_NOMTAB }, /* Forbid ordinary users to mount */ + + { "owner", MNT_MS_OWNER, MNT_NOMTAB }, /* Let the owner of the device mount */ + { "noowner", MNT_MS_OWNER, MNT_INVERT | MNT_NOMTAB }, /* Device owner has no special privs */ + + { "group", MNT_MS_GROUP, MNT_NOMTAB }, /* Let the group of the device mount */ + { "nogroup", MNT_MS_GROUP, MNT_INVERT | MNT_NOMTAB }, /* Device group has no special privs */ + + /* + * Note that traditional init scripts assume the _netdev option in /etc/mtab to + * umount network block devices on shutdown. + */ + { "_netdev", MNT_MS_NETDEV }, /* Device requires network */ + + { "comment=", MNT_MS_COMMENT, MNT_NOHLPS | MNT_NOMTAB },/* fstab comment only */ + + { "x-", MNT_MS_XCOMMENT, MNT_NOHLPS | MNT_PREFIX }, /* persistent comments (utab) */ + { "X-", MNT_MS_XFSTABCOMM, MNT_NOHLPS | MNT_NOMTAB | MNT_PREFIX }, /* fstab only comments */ + + { "loop[=]", MNT_MS_LOOP, MNT_NOHLPS }, /* use the loop device */ + { "offset=", MNT_MS_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* loop device offset */ + { "sizelimit=", MNT_MS_SIZELIMIT, MNT_NOHLPS | MNT_NOMTAB }, /* loop device size limit */ + { "encryption=", MNT_MS_ENCRYPTION, MNT_NOHLPS | MNT_NOMTAB }, /* loop device encryption */ + + { "nofail", MNT_MS_NOFAIL, MNT_NOMTAB }, /* Do not fail if ENOENT on dev */ + + { "uhelper=", MNT_MS_UHELPER }, /* /sbin/umount.<helper> */ + + { "helper=", MNT_MS_HELPER }, /* /sbin/mount.<helper> */ + + { "verity.hashdevice=", MNT_MS_HASH_DEVICE, MNT_NOHLPS | MNT_NOMTAB }, /* mount a verity device */ + { "verity.roothash=", MNT_MS_ROOT_HASH, MNT_NOHLPS | MNT_NOMTAB }, /* verity device root hash */ + { "verity.hashoffset=", MNT_MS_HASH_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* verity device hash offset */ + { "verity.roothashfile=", MNT_MS_ROOT_HASH_FILE, MNT_NOHLPS | MNT_NOMTAB },/* verity device root hash (read from file) */ + { "verity.fecdevice=", MNT_MS_FEC_DEVICE, MNT_NOHLPS | MNT_NOMTAB }, /* verity FEC device */ + { "verity.fecoffset=", MNT_MS_FEC_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* verity FEC area offset */ + { "verity.fecroots=", MNT_MS_FEC_ROOTS, MNT_NOHLPS | MNT_NOMTAB }, /* verity FEC roots */ + { "verity.roothashsig=", MNT_MS_ROOT_HASH_SIG, MNT_NOHLPS | MNT_NOMTAB }, /* verity device root hash signature file */ + { "verity.oncorruption=", MNT_MS_VERITY_ON_CORRUPTION, MNT_NOHLPS | MNT_NOMTAB }, /* verity: action the kernel takes on corruption */ + + { NULL, 0, 0 } +}; + +/** + * mnt_get_builtin_map: + * @id: map id -- MNT_LINUX_MAP or MNT_USERSPACE_MAP + * + * MNT_LINUX_MAP - Linux kernel fs-independent mount options + * (usually MS_* flags, see linux/fs.h) + * + * MNT_USERSPACE_MAP - userspace mount(8) specific mount options + * (e.g user=, _netdev, ...) + * + * Returns: static built-in libmount map. + */ +const struct libmnt_optmap *mnt_get_builtin_optmap(int id) +{ + assert(id); + + if (id == MNT_LINUX_MAP) + return linux_flags_map; + if (id == MNT_USERSPACE_MAP) + return userspace_opts_map; + return NULL; +} + +/* + * Looks up the @name in @maps and returns a map and in @mapent + * returns the map entry + */ +const struct libmnt_optmap *mnt_optmap_get_entry( + struct libmnt_optmap const **maps, + int nmaps, + const char *name, + size_t namelen, + const struct libmnt_optmap **mapent) +{ + int i; + + assert(maps); + assert(nmaps); + assert(name); + assert(namelen); + + if (mapent) + *mapent = NULL; + + for (i = 0; i < nmaps; i++) { + const struct libmnt_optmap *map = maps[i]; + const struct libmnt_optmap *ent; + const char *p; + + for (ent = map; ent && ent->name; ent++) { + if (ent->mask & MNT_PREFIX) { + if (startswith(name, ent->name)) { + if (mapent) + *mapent = ent; + return map; + } + continue; + } + if (strncmp(ent->name, name, namelen) != 0) + continue; + p = ent->name + namelen; + if (*p == '\0' || *p == '=' || *p == '[') { + if (mapent) + *mapent = ent; + return map; + } + } + } + return NULL; +} + diff --git a/libmount/src/optstr.c b/libmount/src/optstr.c new file mode 100644 index 0000000..e86b962 --- /dev/null +++ b/libmount/src/optstr.c @@ -0,0 +1,1170 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2009-2018 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. + */ + +/** + * SECTION: optstr + * @title: Options string + * @short_description: low-level API for working with mount options + * + * This is a simple and low-level API to working with mount options that are stored + * in a string. + */ +#include <ctype.h> + +#include "strutils.h" +#include "mountP.h" + +/* + * Option location + */ +struct libmnt_optloc { + char *begin; + char *end; + char *value; + size_t valsz; + size_t namesz; +}; + +#define MNT_INIT_OPTLOC { .begin = NULL } + +#define mnt_optmap_entry_novalue(e) \ + (e && (e)->name && !strchr((e)->name, '=') && !((e)->mask & MNT_PREFIX)) + +/* + * Locates the first option that matches @name. The @end is set to the + * char behind the option (it means ',' or \0). + * + * Returns negative number on parse error, 1 when not found and 0 on success. + */ +static int mnt_optstr_locate_option(char *optstr, const char *name, + struct libmnt_optloc *ol) +{ + char *n; + size_t namesz, nsz; + int rc; + + if (!optstr) + return 1; + + assert(name); + + namesz = strlen(name); + if (!namesz) + return 1; + + do { + rc = ul_optstr_next(&optstr, &n, &nsz, + &ol->value, &ol->valsz); + if (rc) + break; + + if (namesz == nsz && strncmp(n, name, nsz) == 0) { + ol->begin = n; + ol->end = *(optstr - 1) == ',' ? optstr - 1 : optstr; + ol->namesz = nsz; + return 0; + } + } while(1); + + return rc; +} + +/** + * mnt_optstr_next_option: + * @optstr: option string, returns the position of the next option + * @name: returns the option name + * @namesz: returns the option name length + * @value: returns the option value or NULL + * @valuesz: returns the option value length or zero + * + * Parses the first option in @optstr. + * + * Returns: 0 on success, 1 at the end of @optstr or negative number in case of + * error. + */ +int mnt_optstr_next_option(char **optstr, char **name, size_t *namesz, + char **value, size_t *valuesz) +{ + if (!optstr || !*optstr) + return -EINVAL; + + return ul_optstr_next(optstr, name, namesz, value, valuesz); +} + +int mnt_buffer_append_option(struct ul_buffer *buf, + const char *name, size_t namesz, + const char *val, size_t valsz, + int quoted) +{ + int rc = 0; + + if (!ul_buffer_is_empty(buf)) + rc = ul_buffer_append_data(buf, ",", 1); + if (!rc) + rc = ul_buffer_append_data(buf, name, namesz); + if (val && !rc) { + /* we need to append '=' is value is empty string, see + * 727c689908c5e68c92aa1dd65e0d3bdb6d91c1e5 */ + rc = ul_buffer_append_data(buf, "=", 1); + if (!rc && valsz) { + if (quoted) + rc = ul_buffer_append_data(buf, "\"", 1); + if (!rc) + rc = ul_buffer_append_data(buf, val, valsz); + if (quoted) + rc = ul_buffer_append_data(buf, "\"", 1); + } + } + return rc; +} + +/** + * mnt_optstr_append_option: + * @optstr: option string or NULL, returns a reallocated string + * @name: value name + * @value: value + * + * Returns: 0 on success or <0 in case of error. After an error the @optstr should + * be unmodified. + */ +int mnt_optstr_append_option(char **optstr, const char *name, const char *value) +{ + struct ul_buffer buf = UL_INIT_BUFFER; + int rc; + size_t nsz, vsz, osz; + + if (!optstr) + return -EINVAL; + if (!name || !*name) + return 0; + + nsz = strlen(name); + osz = *optstr ? strlen(*optstr) : 0; + vsz = value ? strlen(value) : 0; + + ul_buffer_refer_string(&buf, *optstr); + ul_buffer_set_chunksize(&buf, osz + nsz + vsz + 3); /* to call realloc() only once */ + + rc = mnt_buffer_append_option(&buf, name, nsz, value, vsz, 0); + if (!rc) + *optstr = ul_buffer_get_data(&buf, NULL, NULL); + else if (osz == 0) + ul_buffer_free_data(&buf); + + return rc; +} +/** + * mnt_optstr_prepend_option: + * @optstr: option string or NULL, returns a reallocated string + * @name: value name + * @value: value + * + * Returns: 0 on success or <0 in case of error. After an error the @optstr should + * be unmodified. + */ +int mnt_optstr_prepend_option(char **optstr, const char *name, const char *value) +{ + struct ul_buffer buf = UL_INIT_BUFFER; + size_t nsz, vsz, osz; + int rc; + + if (!optstr) + return -EINVAL; + if (!name || !*name) + return 0; + + nsz = strlen(name); + osz = *optstr ? strlen(*optstr) : 0; + vsz = value ? strlen(value) : 0; + + ul_buffer_set_chunksize(&buf, osz + nsz + vsz + 3); /* to call realloc() only once */ + + rc = mnt_buffer_append_option(&buf, name, nsz, value, vsz, 0); + if (*optstr && !rc) { + rc = ul_buffer_append_data(&buf, ",", 1); + if (!rc) + rc = ul_buffer_append_data(&buf, *optstr, osz); + free(*optstr); + } + + if (!rc) + *optstr = ul_buffer_get_data(&buf, NULL, NULL); + else + ul_buffer_free_data(&buf); + + return rc; +} + +/** + * mnt_optstr_get_option: + * @optstr: string with a comma separated list of options + * @name: requested option name + * @value: returns a pointer to the beginning of the value (e.g. name=VALUE) or NULL + * @valsz: returns size of the value or 0 + * + * Returns: 0 on success, 1 when not found the @name or negative number in case + * of error. + */ +int mnt_optstr_get_option(const char *optstr, const char *name, + char **value, size_t *valsz) +{ + struct libmnt_optloc ol = MNT_INIT_OPTLOC; + int rc; + + if (!optstr || !name) + return -EINVAL; + + rc = mnt_optstr_locate_option((char *) optstr, name, &ol); + if (!rc) { + if (value) + *value = ol.value; + if (valsz) + *valsz = ol.valsz; + } + return rc; +} + +/** + * mnt_optstr_deduplicate_option: + * @optstr: string with a comma separated list of options + * @name: requested option name + * + * Removes all instances of @name except the last one. + * + * Returns: 0 on success, 1 when not found the @name or negative number in case + * of error. + */ +int mnt_optstr_deduplicate_option(char **optstr, const char *name) +{ + int rc; + char *begin = NULL, *end = NULL, *opt; + + if (!optstr || !name) + return -EINVAL; + + opt = *optstr; + do { + struct libmnt_optloc ol = MNT_INIT_OPTLOC; + + rc = mnt_optstr_locate_option(opt, name, &ol); + if (!rc) { + if (begin) { + /* remove the previous instance */ + size_t shift = strlen(*optstr); + + mnt_optstr_remove_option_at(optstr, begin, end); + + /* now all the offsets are not valid anymore - recount */ + shift -= strlen(*optstr); + ol.begin -= shift; + ol.end -= shift; + } + begin = ol.begin; + end = ol.end; + opt = end && *end ? end + 1 : NULL; + } + if (opt == NULL) + break; + } while (rc == 0 && *opt); + + return rc < 0 ? rc : begin ? 0 : 1; +} + +/* + * The result never starts or ends with a comma or contains two commas + * (e.g. ",aaa,bbb" or "aaa,,bbb" or "aaa,") + */ +int mnt_optstr_remove_option_at(char **optstr, char *begin, char *end) +{ + size_t sz; + + if (!optstr || !begin || !end) + return -EINVAL; + + if ((begin == *optstr || *(begin - 1) == ',') && *end == ',') + end++; + + sz = strlen(end); + + memmove(begin, end, sz + 1); + if (!*begin && (begin > *optstr) && *(begin - 1) == ',') + *(begin - 1) = '\0'; + + return 0; +} + +/* insert 'substr' or '=substr' to @str on position @pos */ +static int __attribute__((nonnull(1,2,3))) +insert_value(char **str, char *pos, const char *substr, char **next) +{ + size_t subsz = strlen(substr); /* substring size */ + size_t strsz = strlen(*str); + size_t possz = strlen(pos); + size_t posoff; + char *p; + int sep; + + /* is it necessary to prepend '=' before the substring ? */ + sep = !(pos > *str && *(pos - 1) == '='); + + /* save an offset of the place where we need to add substr */ + posoff = pos - *str; + + p = realloc(*str, strsz + sep + subsz + 1); + if (!p) + return -ENOMEM; + + /* zeroize the newly allocated memory -- valgrind loves us... */ + memset(p + strsz, 0, sep + subsz + 1); + + /* set pointers to the reallocated string */ + *str = p; + pos = p + posoff; + + if (possz) + /* create a room for the new substring */ + memmove(pos + subsz + sep, pos, possz + 1); + if (sep) + *pos++ = '='; + + memcpy(pos, substr, subsz); + + if (next) { + /* set pointer to the next option */ + *next = pos + subsz; + if (**next == ',') + (*next)++; + } + return 0; +} + +/** + * mnt_optstr_set_option: + * @optstr: string with a comma separated list of options + * @name: requested option + * @value: new value or NULL + * + * Set or unset the option @value. + * + * Returns: 0 on success, 1 when not found the @name or negative number in case + * of error. + */ +int mnt_optstr_set_option(char **optstr, const char *name, const char *value) +{ + struct libmnt_optloc ol = MNT_INIT_OPTLOC; + char *nameend; + int rc = 1; + + if (!optstr || !name) + return -EINVAL; + + if (*optstr) + rc = mnt_optstr_locate_option(*optstr, name, &ol); + if (rc < 0) + return rc; /* parse error */ + if (rc == 1) + return mnt_optstr_append_option(optstr, name, value); /* not found */ + + nameend = ol.begin + ol.namesz; + + if (value == NULL && ol.value && ol.valsz) + /* remove unwanted "=value" */ + mnt_optstr_remove_option_at(optstr, nameend, ol.end); + + else if (value && ol.value == NULL) + /* insert "=value" */ + rc = insert_value(optstr, nameend, value, NULL); + + else if (value && ol.value && strlen(value) == ol.valsz) + /* simply replace =value */ + memcpy(ol.value, value, ol.valsz); + + else if (value && ol.value) { + mnt_optstr_remove_option_at(optstr, nameend, ol.end); + rc = insert_value(optstr, nameend, value, NULL); + } + return rc; +} + +/** + * mnt_optstr_remove_option: + * @optstr: string with a comma separated list of options + * @name: requested option name + * + * Returns: 0 on success, 1 when not found the @name or negative number in case + * of error. + */ +int mnt_optstr_remove_option(char **optstr, const char *name) +{ + struct libmnt_optloc ol = MNT_INIT_OPTLOC; + int rc; + + if (!optstr || !name) + return -EINVAL; + + rc = mnt_optstr_locate_option(*optstr, name, &ol); + if (rc != 0) + return rc; + + mnt_optstr_remove_option_at(optstr, ol.begin, ol.end); + return 0; +} + +/** + * mnt_split_optstr: + * @optstr: string with comma separated list of options + * @user: returns newly allocated string with userspace options + * @vfs: returns newly allocated string with VFS options + * @fs: returns newly allocated string with FS options + * @ignore_user: option mask for options that should be ignored + * @ignore_vfs: option mask for options that should be ignored + * + * For example: + * + * mnt_split_optstr(optstr, &u, NULL, NULL, MNT_NOMTAB, 0); + * + * returns all userspace options, the options that do not belong to + * mtab are ignored. + * + * Note that FS options are all options that are undefined in MNT_USERSPACE_MAP + * or MNT_LINUX_MAP. + * + * Returns: 0 on success, or a negative number in case of error. + */ +int mnt_split_optstr(const char *optstr, char **user, char **vfs, + char **fs, int ignore_user, int ignore_vfs) +{ + int rc = 0; + char *name, *val, *str = (char *) optstr; + size_t namesz, valsz, chunsz; + struct libmnt_optmap const *maps[2]; + struct ul_buffer xvfs = UL_INIT_BUFFER, + xfs = UL_INIT_BUFFER, + xuser = UL_INIT_BUFFER; + + if (!optstr) + return -EINVAL; + + maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP); + maps[1] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP); + + chunsz = strlen(optstr) / 2; + + while (!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) { + struct ul_buffer *buf = NULL; + const struct libmnt_optmap *ent = NULL; + const struct libmnt_optmap *m = + mnt_optmap_get_entry(maps, 2, name, namesz, &ent); + + if (ent && !ent->id) + continue; /* ignore undefined options (comments) */ + + /* ignore name=<value> if options map expects <name> only */ + if (valsz && mnt_optmap_entry_novalue(ent)) + m = NULL; + + if (ent && m && m == maps[0] && vfs) { + if (ignore_vfs && (ent->mask & ignore_vfs)) + continue; + if (vfs) + buf = &xvfs; + } else if (ent && m && m == maps[1] && user) { + if (ignore_user && (ent->mask & ignore_user)) + continue; + if (user) + buf = &xuser; + } else if (!m && fs) { + if (fs) + buf = &xfs; + } + + if (buf) { + if (ul_buffer_is_empty(buf)) + ul_buffer_set_chunksize(buf, chunsz); + rc = mnt_buffer_append_option(buf, name, namesz, val, valsz, 0); + } + if (rc) + break; + } + + if (vfs) + *vfs = rc ? NULL : ul_buffer_get_data(&xvfs, NULL, NULL); + if (fs) + *fs = rc ? NULL : ul_buffer_get_data(&xfs, NULL, NULL); + if (user) + *user = rc ? NULL : ul_buffer_get_data(&xuser, NULL, NULL); + if (rc) { + ul_buffer_free_data(&xvfs); + ul_buffer_free_data(&xfs); + ul_buffer_free_data(&xuser); + } + + return rc; +} + +/** + * mnt_optstr_get_options + * @optstr: string with a comma separated list of options + * @subset: returns newly allocated string with options + * @map: options map + * @ignore: mask of the options that should be ignored + * + * Extracts options from @optstr that belong to the @map, for example: + * + * mnt_optstr_get_options(optstr, &p, + * mnt_get_builtin_optmap(MNT_LINUX_MAP), + * MNT_NOMTAB); + * + * the 'p' returns all VFS options, the options that do not belong to mtab + * are ignored. + * + * Returns: 0 on success, or a negative number in case of error. + */ +int mnt_optstr_get_options(const char *optstr, char **subset, + const struct libmnt_optmap *map, int ignore) +{ + struct libmnt_optmap const *maps[1]; + struct ul_buffer buf = UL_INIT_BUFFER; + char *name, *val, *str = (char *) optstr; + size_t namesz, valsz; + int rc = 0; + + if (!optstr || !subset) + return -EINVAL; + + maps[0] = map; + + ul_buffer_set_chunksize(&buf, strlen(optstr)/2); + + while (!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) { + const struct libmnt_optmap *ent; + + mnt_optmap_get_entry(maps, 1, name, namesz, &ent); + + if (!ent || !ent->id) + continue; /* ignore undefined options (comments) */ + + if (ignore && (ent->mask & ignore)) + continue; + + /* ignore name=<value> if options map expects <name> only */ + if (valsz && mnt_optmap_entry_novalue(ent)) + continue; + + rc = mnt_buffer_append_option(&buf, name, namesz, val, valsz, 0); + if (rc) + break; + } + + *subset = rc ? NULL : ul_buffer_get_data(&buf, NULL, NULL); + if (rc) + ul_buffer_free_data(&buf); + return rc; +} + + +/** + * mnt_optstr_get_flags: + * @optstr: string with comma separated list of options + * @flags: returns mount flags + * @map: options map + * + * Returns in @flags IDs of options from @optstr as defined in the @map. + * + * For example: + * + * "bind,exec,foo,bar" --returns-> MS_BIND + * + * "bind,noexec,foo,bar" --returns-> MS_BIND|MS_NOEXEC + * + * Note that @flags are not zeroized by this function! This function sets/unsets + * bits in the @flags only. + * + * Returns: 0 on success or negative number in case of error + */ +int mnt_optstr_get_flags(const char *optstr, unsigned long *flags, + const struct libmnt_optmap *map) +{ + struct libmnt_optmap const *maps[2]; + char *name, *str = (char *) optstr; + size_t namesz = 0, valsz = 0; + int nmaps = 0; + + if (!optstr || !flags || !map) + return -EINVAL; + + maps[nmaps++] = map; + + if (map == mnt_get_builtin_optmap(MNT_LINUX_MAP)) + /* + * Add userspace map -- the "user" is interpreted as + * MS_NO{EXEC,SUID,DEV}. + */ + maps[nmaps++] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP); + + while(!mnt_optstr_next_option(&str, &name, &namesz, NULL, &valsz)) { + const struct libmnt_optmap *ent; + const struct libmnt_optmap *m; + + m = mnt_optmap_get_entry(maps, nmaps, name, namesz, &ent); + if (!m || !ent || !ent->id) + continue; + + /* ignore name=<value> if options map expects <name> only */ + if (valsz && mnt_optmap_entry_novalue(ent)) + continue; + + if (m == map) { /* requested map */ + if (ent->mask & MNT_INVERT) + *flags &= ~ent->id; + else + *flags |= ent->id; + + } else if (nmaps == 2 && m == maps[1] && valsz == 0) { + /* + * Special case -- translate "user" (but no user=) to + * MS_ options + */ + if (ent->mask & MNT_INVERT) + continue; + if (ent->id & (MNT_MS_OWNER | MNT_MS_GROUP)) + *flags |= MS_OWNERSECURE; + else if (ent->id & (MNT_MS_USER | MNT_MS_USERS)) + *flags |= MS_SECURE; + } + } + + return 0; +} + +/** + * mnt_optstr_apply_flags: + * @optstr: string with comma separated list of options + * @flags: returns mount flags + * @map: options map + * + * Removes/adds options to the @optstr according to flags. For example: + * + * MS_NOATIME and "foo,bar,noexec" --returns-> "foo,bar,noatime" + * + * Returns: 0 on success or negative number in case of error. + * + * Deprecated: since v2.39. + */ +int mnt_optstr_apply_flags(char **optstr, unsigned long flags, + const struct libmnt_optmap *map) +{ + struct libmnt_optmap const *maps[1]; + char *name, *next, *val; + size_t namesz = 0, valsz = 0, multi = 0; + unsigned long fl; + int rc = 0; + + if (!optstr || !map) + return -EINVAL; + + DBG(CXT, ul_debug("applying 0x%08lx flags to '%s'", flags, *optstr)); + + maps[0] = map; + next = *optstr; + fl = flags; + + /* + * There is a convention that 'rw/ro' flags are always at the beginning of + * the string (although the 'rw' is unnecessary). + */ + if (map == mnt_get_builtin_optmap(MNT_LINUX_MAP)) { + const char *o = (fl & MS_RDONLY) ? "ro" : "rw"; + + if (next && + (!strncmp(next, "rw", 2) || !strncmp(next, "ro", 2)) && + (*(next + 2) == '\0' || *(next + 2) == ',')) { + + /* already set, be paranoid and fix it */ + memcpy(next, o, 2); + } else { + rc = mnt_optstr_prepend_option(optstr, o, NULL); + if (rc) + goto err; + next = *optstr; /* because realloc() */ + } + fl &= ~MS_RDONLY; + next += 2; + if (*next == ',') + next++; + } + + if (next && *next) { + /* + * scan @optstr and remove options that are missing in + * @flags + */ + while(!mnt_optstr_next_option(&next, &name, &namesz, + &val, &valsz)) { + const struct libmnt_optmap *ent; + + if (mnt_optmap_get_entry(maps, 1, name, namesz, &ent)) { + /* + * remove unwanted option (rw/ro is already set) + */ + if (!ent || !ent->id) + continue; + /* ignore name=<value> if options map expects <name> only */ + if (valsz && mnt_optmap_entry_novalue(ent)) + continue; + + if (ent->id == MS_RDONLY || + (ent->mask & MNT_INVERT) || + (fl & ent->id) != (unsigned long) ent->id) { + + char *end = val ? val + valsz : + name + namesz; + next = name; + rc = mnt_optstr_remove_option_at( + optstr, name, end); + if (rc) + goto err; + } + if (!(ent->mask & MNT_INVERT)) { + /* allow options with prefix (X-mount.foo,X-mount.bar) more than once */ + if (ent->mask & MNT_PREFIX) + multi |= ent->id; + else + fl &= ~ent->id; + if (ent->id & MS_REC) + fl |= MS_REC; + } + } + } + } + + /* remove from flags options which are allowed more than once */ + fl &= ~multi; + + /* add missing options (but ignore fl if contains MS_REC only) */ + if (fl && fl != MS_REC) { + + const struct libmnt_optmap *ent; + struct ul_buffer buf = UL_INIT_BUFFER; + size_t sz; + char *p; + + ul_buffer_refer_string(&buf, *optstr); + + for (ent = map; ent && ent->name; ent++) { + if ((ent->mask & MNT_INVERT) + || ent->id == 0 + || (fl & 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); + + rc = mnt_buffer_append_option(&buf, ent->name, sz, NULL, 0, 0); + if (rc) + break; + } + + if (rc) { + ul_buffer_free_data(&buf); + goto err; + } else + *optstr = ul_buffer_get_data(&buf, NULL, NULL); + } + + DBG(CXT, ul_debug("new optstr '%s'", *optstr)); + return rc; +err: + DBG(CXT, ul_debug("failed to apply flags [rc=%d]", rc)); + return rc; +} + +/** + * mnt_match_options: + * @optstr: options string + * @pattern: comma delimited list of options + * + * The "no" could be used for individual items in the @options list. The "no" + * prefix does not have a global meaning. + * + * Unlike fs type matching, nonetdev,user and nonetdev,nouser have + * DIFFERENT meanings; each option is matched explicitly as specified. + * + * The "no" prefix interpretation could be disabled by the "+" prefix, for example + * "+noauto" matches if @optstr literally contains the "noauto" string. + * + * The alone "no" is error and all matching ends with False. + * + * "xxx,yyy,zzz" : "nozzz" -> False + * + * "xxx,yyy,zzz" : "xxx,noeee" -> True + * + * "bar,zzz" : "nofoo" -> True (does not contain "foo") + * + * "nofoo,bar" : "nofoo" -> True (does not contain "foo") + * + * "nofoo,bar" : "+nofoo" -> True (contains "nofoo") + * + * "bar,zzz" : "+nofoo" -> False (does not contain "nofoo") + * + * "bar,zzz" : "" or "+" -> True (empty pattern is matching) + * + * "" : "" -> True + * + * "" : "foo" -> False + * + * "" : "nofoo" -> True + * + * "" : "no,foo" -> False (alone "no" is error) + * + * "no" : "+no" -> True ("no" is an option due to "+") + * + * Returns: 1 if pattern is matching, else 0. This function also returns 0 + * if @pattern is NULL and @optstr is non-NULL. + */ +int mnt_match_options(const char *optstr, const char *pattern) +{ + char *name, *pat = (char *) pattern; + char *buf = NULL, *patval; + size_t namesz = 0, patvalsz = 0; + int match = 1; + + if (!pattern && !optstr) + return 1; + if (pattern && optstr && !*pattern && !*optstr) + return 1; + if (!pattern) + return 0; + + /* walk on pattern string + */ + while (match && !mnt_optstr_next_option(&pat, &name, &namesz, + &patval, &patvalsz)) { + char *val; + size_t sz = 0; + int no = 0, rc; + + if (*name == '+') + name++, namesz--; + else if ((no = (startswith(name, "no") != NULL))) { + name += 2, namesz -= 2; + if (!*name || *name == ',') { + match = 0; + break; /* alone "no" keyword is error */ + } + } + + if (optstr && *optstr && *name) { + if (!buf) { + buf = malloc(strlen(pattern) + 1); + if (!buf) + return 0; + } + + xstrncpy(buf, name, namesz + 1); + rc = mnt_optstr_get_option(optstr, buf, &val, &sz); + + } else if (!*name) { + rc = 0; /* empty pattern matches */ + } else { + rc = 1; /* not found in empty string */ + } + + /* check also value (if the pattern is "foo=value") */ + if (rc == 0 && patvalsz > 0 && + (patvalsz != sz || strncmp(patval, val, sz) != 0)) + rc = 1; + + switch (rc) { + case 0: /* found */ + match = no == 0 ? 1 : 0; + break; + case 1: /* not found */ + match = no == 1 ? 1 : 0; + break; + default: /* parse error */ + match = 0; + break; + } + } + + free(buf); + return match; +} + +#ifdef TEST_PROGRAM +static int test_append(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *value = NULL, *name; + char *optstr; + int rc; + + if (argc < 3) + return -EINVAL; + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + name = argv[2]; + + if (argc == 4) + value = argv[3]; + + rc = mnt_optstr_append_option(&optstr, name, value); + if (!rc) + printf("result: >%s<\n", optstr); + free(optstr); + return rc; +} + +static int test_prepend(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *value = NULL, *name; + char *optstr; + int rc; + + if (argc < 3) + return -EINVAL; + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + name = argv[2]; + + if (argc == 4) + value = argv[3]; + + rc = mnt_optstr_prepend_option(&optstr, name, value); + if (!rc) + printf("result: >%s<\n", optstr); + free(optstr); + return rc; +} + +static int test_split(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr, *user = NULL, *fs = NULL, *vfs = NULL; + int rc; + + if (argc < 2) + return -EINVAL; + + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + + rc = mnt_split_optstr(optstr, &user, &vfs, &fs, 0, 0); + if (!rc) { + printf("user : %s\n", user); + printf("vfs : %s\n", vfs); + printf("fs : %s\n", fs); + } + + free(user); + free(vfs); + free(fs); + free(optstr); + return rc; +} + +static int test_flags(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr; + int rc; + unsigned long fl = 0; + + if (argc < 2) + return -EINVAL; + + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + + rc = mnt_optstr_get_flags(optstr, &fl, mnt_get_builtin_optmap(MNT_LINUX_MAP)); + if (rc) + return rc; + printf("mountflags: 0x%08lx\n", fl); + + fl = 0; + rc = mnt_optstr_get_flags(optstr, &fl, mnt_get_builtin_optmap(MNT_USERSPACE_MAP)); + if (rc) + return rc; + printf("userspace-mountflags: 0x%08lx\n", fl); + + free(optstr); + return rc; +} + +static int test_apply(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr; + int rc, map; + unsigned long flags; + + if (argc < 4) + return -EINVAL; + + if (!strcmp(argv[1], "--user")) + map = MNT_USERSPACE_MAP; + else if (!strcmp(argv[1], "--linux")) + map = MNT_LINUX_MAP; + else { + fprintf(stderr, "unknown option '%s'\n", argv[1]); + return -EINVAL; + } + + optstr = strdup(argv[2]); + if (!optstr) + err_oom(); + flags = strtoul(argv[3], NULL, 16); + + printf("flags: 0x%08lx\n", flags); + + rc = mnt_optstr_apply_flags(&optstr, flags, mnt_get_builtin_optmap(map)); + printf("optstr: %s\n", optstr); + + free(optstr); + return rc; +} + +static int test_set(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *value = NULL, *name; + char *optstr; + int rc; + + if (argc < 3) + return -EINVAL; + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + name = argv[2]; + + if (argc == 4) + value = argv[3]; + + rc = mnt_optstr_set_option(&optstr, name, value); + if (!rc) + printf("result: >%s<\n", optstr); + free(optstr); + return rc; +} + +static int test_get(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr; + const char *name; + char *val = NULL; + size_t sz = 0; + int rc; + + if (argc < 2) + return -EINVAL; + optstr = argv[1]; + name = argv[2]; + + rc = mnt_optstr_get_option(optstr, name, &val, &sz); + if (rc == 0) { + printf("found; name: %s", name); + if (sz) { + printf(", argument: size=%zd data=", sz); + if (fwrite(val, 1, sz, stdout) != sz) + return -1; + } + printf("\n"); + } else if (rc == 1) + printf("%s: not found\n", name); + else + printf("parse error: %s\n", optstr); + return rc; +} + +static int test_remove(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *name; + char *optstr; + int rc; + + if (argc < 3) + return -EINVAL; + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + name = argv[2]; + + rc = mnt_optstr_remove_option(&optstr, name); + if (!rc) + printf("result: >%s<\n", optstr); + free(optstr); + return rc; +} + +static int test_dedup(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *name; + char *optstr; + int rc; + + if (argc < 3) + return -EINVAL; + optstr = strdup(argv[1]); + if (!optstr) + err_oom(); + name = argv[2]; + + rc = mnt_optstr_deduplicate_option(&optstr, name); + if (!rc) + printf("result: >%s<\n", optstr); + free(optstr); + return rc; +} + +static int test_match(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr, *pattern; + + if (argc < 3) + return -EINVAL; + + optstr = argv[1]; + pattern = argv[2]; + printf("%-6s: \"%s\"\t:\t\"%s\"\n", + mnt_match_options(optstr, pattern) == 1 ? "true" : "false", + optstr, pattern); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--append", test_append, "<optstr> <name> [<value>] append value to optstr" }, + { "--prepend",test_prepend,"<optstr> <name> [<value>] prepend value to optstr" }, + { "--set", test_set, "<optstr> <name> [<value>] (un)set value" }, + { "--get", test_get, "<optstr> <name> search name in optstr" }, + { "--remove", test_remove, "<optstr> <name> remove name in optstr" }, + { "--dedup", test_dedup, "<optstr> <name> deduplicate name in optstr" }, + { "--match", test_match, "<optstr> <pattern> compare optstr with pattern" }, + { "--split", test_split, "<optstr> split into FS, VFS and userspace" }, + { "--flags", test_flags, "<optstr> convert options to MS_* flags" }, + { "--apply", test_apply, "--{linux,user} <optstr> <mask> apply mask to optstr" }, + + { NULL } + }; + return mnt_run_test(tss, argc, argv); +} +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/tab.c b/libmount/src/tab.c new file mode 100644 index 0000000..9725664 --- /dev/null +++ b/libmount/src/tab.c @@ -0,0 +1,2278 @@ + +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 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. + */ + +/** + * 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 a 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 return the first entry (if UUID matches with the device). + */ +#include <blkid.h> + +#include "mountP.h" +#include "strutils.h" +#include "loopdev.h" +#include "fileutils.h" +#include "canonicalize.h" + +int is_mountinfo(struct libmnt_table *tb) +{ + struct libmnt_fs *fs; + + if (!tb) + return 0; + + fs = list_first_entry(&tb->ents, struct libmnt_fs, ents); + if (fs && mnt_fs_is_kernel(fs) && mnt_fs_get_root(fs)) + return 1; + + return 0; +} + +/** + * mnt_new_table: + * + * 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, ul_debugobj(tb, "alloc")); + tb->refcount = 1; + INIT_LIST_HEAD(&tb->ents); + return tb; +} + +/** + * mnt_reset_table: + * @tb: tab pointer + * + * Removes all entries (filesystems) from the table. The filesystems with zero + * reference count will be deallocated. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_reset_table(struct libmnt_table *tb) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "reset")); + + while (!list_empty(&tb->ents)) { + struct libmnt_fs *fs = list_entry(tb->ents.next, + struct libmnt_fs, ents); + mnt_table_remove_fs(tb, fs); + } + + tb->nents = 0; + return 0; +} + +/** + * mnt_ref_table: + * @tb: table pointer + * + * Increments reference counter. + */ +void mnt_ref_table(struct libmnt_table *tb) +{ + if (tb) { + tb->refcount++; + /*DBG(FS, ul_debugobj(tb, "ref=%d", tb->refcount));*/ + } +} + +/** + * mnt_unref_table: + * @tb: table pointer + * + * De-increments reference counter, on zero the @tb is automatically + * deallocated by mnt_free_table(). + */ +void mnt_unref_table(struct libmnt_table *tb) +{ + if (tb) { + tb->refcount--; + /*DBG(FS, ul_debugobj(tb, "unref=%d", tb->refcount));*/ + if (tb->refcount <= 0) + mnt_free_table(tb); + } +} + + +/** + * mnt_free_table: + * @tb: tab pointer + * + * Deallocates the table. This function does not care about reference count. Don't + * use this function directly -- it's better to use mnt_unref_table(). + * + * The table entries (filesystems) are unreferenced by mnt_reset_table() and + * cache by mnt_unref_cache(). + */ +void mnt_free_table(struct libmnt_table *tb) +{ + if (!tb) + return; + + mnt_reset_table(tb); + DBG(TAB, ul_debugobj(tb, "free [refcount=%d]", tb->refcount)); + + mnt_unref_cache(tb->cache); + free(tb->comm_intro); + free(tb->comm_tail); + free(tb); +} + +/** + * mnt_table_get_nents: + * @tb: pointer to tab + * + * Returns: number of entries in table. + */ +int mnt_table_get_nents(struct libmnt_table *tb) +{ + return tb ? tb->nents : 0; +} + +/** + * mnt_table_is_empty: + * @tb: pointer to tab + * + * Returns: 1 if the table is without filesystems, or 0. + */ +int mnt_table_is_empty(struct libmnt_table *tb) +{ + return tb == NULL || list_empty(&tb->ents) ? 1 : 0; +} + +/** + * mnt_table_set_userdata: + * @tb: pointer to tab + * @data: pointer to user data + * + * Sets pointer to the private user data. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_userdata(struct libmnt_table *tb, void *data) +{ + if (!tb) + return -EINVAL; + + tb->userdata = data; + return 0; +} + +/** + * mnt_table_get_userdata: + * @tb: pointer to tab + * + * Returns: pointer to user's data. + */ +void *mnt_table_get_userdata(struct libmnt_table *tb) +{ + return tb ? tb->userdata : NULL; +} + +/** + * mnt_table_enable_comments: + * @tb: pointer to tab + * @enable: TRUE or FALSE + * + * Enables parsing of comments. + * + * The initial (intro) file comment is accessible by + * mnt_table_get_intro_comment(). The intro and the comment of the first fstab + * entry has to be separated by blank line. The filesystem comments are + * accessible by mnt_fs_get_comment(). The trailing fstab comment is accessible + * by mnt_table_get_trailing_comment(). + * + * <informalexample> + * <programlisting> + * # + * # Intro comment + * # + * + * # this comments belongs to the first fs + * LABEL=foo /mnt/foo auto defaults 1 2 + * # this comments belongs to the second fs + * LABEL=bar /mnt/bar auto defaults 1 2 + * # tailing comment + * </programlisting> + * </informalexample> + */ +void mnt_table_enable_comments(struct libmnt_table *tb, int enable) +{ + if (tb) + tb->comms = enable; +} + +/** + * mnt_table_with_comments: + * @tb: pointer to table + * + * Returns: 1 if comments parsing is enabled, or 0. + */ +int mnt_table_with_comments(struct libmnt_table *tb) +{ + assert(tb); + return tb ? tb->comms : 0; +} + +/** + * mnt_table_get_intro_comment: + * @tb: pointer to tab + * + * Returns: initial comment in tb + */ +const char *mnt_table_get_intro_comment(struct libmnt_table *tb) +{ + return tb ? tb->comm_intro : NULL; +} + +/** + * mnt_table_set_into_comment: + * @tb: pointer to tab + * @comm: comment or NULL + * + * Sets the initial comment in tb. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm) +{ + return strdup_to_struct_member(tb, comm_intro, comm); +} + +/** + * mnt_table_append_into_comment: + * @tb: pointer to tab + * @comm: comment of NULL + * + * Appends the initial comment in tb. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm) +{ + if (!tb) + return -EINVAL; + return strappend(&tb->comm_intro, comm); +} + +/** + * mnt_table_get_trailing_comment: + * @tb: pointer to tab + * + * Returns: table trailing comment + */ +const char *mnt_table_get_trailing_comment(struct libmnt_table *tb) +{ + return tb ? tb->comm_tail : NULL; +} + +/** + * mnt_table_set_trailing_comment + * @tb: pointer to tab + * @comm: comment string + * + * Sets the trailing comment in table. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm) +{ + return strdup_to_struct_member(tb, comm_tail, comm); +} + +/** + * mnt_table_append_trailing_comment: + * @tb: pointer to tab + * @comm: comment of NULL + * + * Appends to the trailing table comment. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm) +{ + if (!tb) + return -EINVAL; + return strappend(&tb->comm_tail, comm); +} + +/** + * mnt_table_set_cache: + * @tb: pointer to tab + * @mpc: pointer to struct libmnt_cache instance + * + * Sets up 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. + * + * This function increments cache reference counter. It's recommended to use + * mnt_unref_cache() after mnt_table_set_cache() if you want to keep the cache + * referenced by @tb only. + * + * See also mnt_new_cache(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc) +{ + if (!tb) + return -EINVAL; + + mnt_ref_cache(mpc); /* new */ + mnt_unref_cache(tb->cache); /* old */ + 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) +{ + return tb ? tb->cache : NULL; +} + +/** + * mnt_table_find_fs: + * @tb: tab pointer + * @fs: entry to look for + * + * Checks if @fs is part of table @tb. + * + * Returns: index of @fs in table, 0 if not found or negative number in case of error. + * + * Since: 2.34 + */ +int mnt_table_find_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + struct list_head *p; + int i = 0; + + if (!tb || !fs) + return -EINVAL; + + if (list_empty(&fs->ents)) + return 0; + + /* Let's use directly list rather than mnt_table_next_fs() as we + * compare list entry with fs only. + */ + list_for_each(p, &tb->ents) { + ++i; + if (list_entry(p, struct libmnt_fs, ents) == fs) + return i; + } + + return 0; +} + +/** + * mnt_table_add_fs: + * @tb: tab pointer + * @fs: new entry + * + * Adds a new entry to tab and increment @fs reference counter. Don't forget to + * use mnt_unref_fs() after mnt_table_add_fs() you want to keep the @fs + * referenced by the table only. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + if (!tb || !fs) + return -EINVAL; + + if (fs->tab) + return -EBUSY; + + mnt_ref_fs(fs); + list_add_tail(&fs->ents, &tb->ents); + fs->tab = tb; + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "add entry: %s %s", + mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + return 0; +} + +static int __table_insert_fs( + struct libmnt_table *tb, int before, + struct libmnt_fs *pos, struct libmnt_fs *fs) +{ + struct list_head *head = pos ? &pos->ents : &tb->ents; + + if (before) + list_add(&fs->ents, head); + else + list_add_tail(&fs->ents, head); + + fs->tab = tb; + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "insert entry: %s %s", + mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + return 0; +} + +/** + * mnt_table_insert_fs: + * @tb: tab pointer + * @before: 1 to insert before pos, 0 to insert after pos + * @pos: entry to specify position or NULL + * @fs: new entry + * + * Adds a new entry to @tb before or after a specific table entry @pos. If the + * @pos is NULL than add the begin of the @tab if @before is 1; or to the tail + * of the @tb if @before is 0. + * + * This function increments reference to @fs. Don't forget to use + * mnt_unref_fs() after mnt_table_insert_fs() if you want to keep the @fs + * referenced by the table only. + * + * Returns: 0 on success or negative number in case of error. + * + * Since: 2.34 + */ +int mnt_table_insert_fs(struct libmnt_table *tb, int before, + struct libmnt_fs *pos, struct libmnt_fs *fs) +{ + if (!tb || !fs) + return -EINVAL; + + if (fs->tab) + return -EBUSY; + + if (pos && pos->tab != tb) + return -ENOENT; + + mnt_ref_fs(fs); + return __table_insert_fs(tb, before, pos, fs); +} + +/** + * mnt_table_move_fs: + * @src: tab pointer of source table + * @dst: tab pointer of destination table + * @before: 1 to move before position, 0 to move after position + * @pos: entry to specify position or NULL + * @fs: entry to move + * + * Removes @fs from @src table and adds it before/after a specific entry @pos + * of @dst table. If the @pos is NULL than add the begin of the @dst if @before + * is 1; or to the tail of the @dst if @before is 0. + * + * The reference counter of @fs is not modified. + * + * Returns: 0 on success or negative number in case of error. + * + * Since: 2.34 + */ +int mnt_table_move_fs(struct libmnt_table *src, struct libmnt_table *dst, + int before, struct libmnt_fs *pos, struct libmnt_fs *fs) +{ + if (!src || !dst || !fs) + return -EINVAL; + + if (fs->tab != src || (pos && pos->tab != dst)) + return -ENOENT; + + /* remove from source */ + list_del_init(&fs->ents); + src->nents--; + + /* insert to the destination */ + return __table_insert_fs(dst, before, pos, fs); +} + + +/** + * mnt_table_remove_fs: + * @tb: tab pointer + * @fs: new entry + * + * Removes the @fs from the table and de-increment reference counter of the @fs. The + * filesystem with zero reference counter will be deallocated. Don't forget to use + * mnt_ref_fs() before call mnt_table_remove_fs() if you want to use @fs later. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + if (!tb || !fs || fs->tab != tb) + return -EINVAL; + + fs->tab = NULL; + list_del_init(&fs->ents); + + mnt_unref_fs(fs); + tb->nents--; + return 0; +} + +static inline struct libmnt_fs *get_parent_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + struct libmnt_iter itr; + struct libmnt_fs *x; + int parent_id = mnt_fs_get_parent_id(fs); + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while (mnt_table_next_fs(tb, &itr, &x) == 0) { + if (mnt_fs_get_id(x) == parent_id) + return x; + } + + return NULL; +} + +/** + * mnt_table_get_root_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @root: NULL or returns pointer to the root filesystem (/) + * + * The function uses the parent ID from the mountinfo file to determine the + * root filesystem (the filesystem with the smallest ID with parent ID missing + * in the table). The function is designed mostly for applications where it is + * necessary to sort mountpoints by IDs to get the tree of the mountpoints + * (e.g. findmnt default output). + * + * If you're not sure, then use + * + * mnt_table_find_target(tb, "/", MNT_ITER_BACKWARD); + * + * this is more robust and usable for arbitrary tab files (including fstab). + * + * Returns: 0 on success or negative number in 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, *root_fs = NULL; + int root_id = 0; + + if (!tb || !is_mountinfo(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "lookup root fs")); + + /* get smallest possible ID from the table */ + 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 (!root_fs || id < root_id) { + root_fs = fs; + root_id = id; + } + } + + /* go to the root node by "parent_id -> id" relation */ + while (root_fs) { + struct libmnt_fs *x = get_parent_fs(tb, root_fs); + if (!x || x == root_fs) + break; + DBG(TAB, ul_debugobj(tb, " messy mountinfo, walk to %s", mnt_fs_get_target(x))); + root_fs = x; + } + + if (root) + *root = root_fs; + + return root_fs ? 0 : -EINVAL; +} + +/** + * mnt_table_next_child_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @itr: iterator + * @parent: parental FS + * @chld: NULL or returns the next child filesystem + * + * Since version 2.40, the filesystems are returned in the order specified by + * @itr. In the old versions the derection is always MNT_ITER_FORWARD. + * + * Returns: 0 on success, negative number in case of error or 1 at the 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, *chfs = NULL; + int parent_id, lastchld_id = 0, chld_id = 0; + int direction; + + if (!tb || !itr || !parent || !is_mountinfo(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "lookup next child of '%s'", + mnt_fs_get_target(parent))); + parent_id = mnt_fs_get_id(parent); + direction = mnt_iter_get_direction(itr); + + /* get ID of the previously returned child */ + if (itr->head && itr->p != itr->head) { + fs = MNT_ITER_GET_ENTRY(itr, struct libmnt_fs, ents); + MNT_ITER_ITERATE(itr); + lastchld_id = mnt_fs_get_id(fs); + } + + mnt_reset_iter(itr, direction); + 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 an 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 (direction == MNT_ITER_FORWARD) { + /* return in the order of mounting */ + if ((!lastchld_id || id > lastchld_id) && + (!chfs || id < chld_id)) { + chfs = fs; + chld_id = id; + } + } else { + /* return last child first */ + if ((!lastchld_id || id < lastchld_id) && + (!chfs || id > chld_id)) { + chfs = fs; + chld_id = id; + } + } + } + + if (chld) + *chld = chfs; + if (!chfs) + return 1; /* end of iterator */ + + /* set the iterator to the @chfs for the next call */ + mnt_table_set_iter(tb, itr, chfs); + + return 0; +} + +/** + * mnt_table_over_fs: + * @tb: tab pointer + * @parent: pointer to parental FS + * @child: returns pointer to FS which over-mounting parent (optional) + * + * This function returns by @child the first filesystenm which is over-mounted + * on @parent. It means the mountpoint of @child is the same as for @parent and + * parent->id is the same as child->parent_id. + * + * Note that you need to call this function in loop until it returns 1 to get + * the highest filesystem. + * + * Example: + * <informalexample> + * <programlisting> + * while (mnt_table_over_fs(tb, cur, &over) == 0) { + * printf("%s overmounted by %d\n", mnt_fs_get_target(cur), mnt_fs_get_id(over)); + * cur = over; + * } + * </programlisting> + * </informalexample> + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_over_fs(struct libmnt_table *tb, struct libmnt_fs *parent, + struct libmnt_fs **child) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + int id; + const char *tgt; + + if (!tb || !parent || !is_mountinfo(tb)) + return -EINVAL; + + if (child) + *child = NULL; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + id = mnt_fs_get_id(parent); + tgt = mnt_fs_get_target(parent); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_get_parent_id(fs) == id && + mnt_fs_streq_target(fs, tgt) == 1) { + if (child) + *child = fs; + return 0; + } + } + + return 1; /* nothing */ +} + +/** + * mnt_table_next_fs: + * @tb: tab pointer + * @itr: iterator + * @fs: NULL or returns the next tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the 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); + * } + * </programlisting> + * </informalexample> + * + * lists all mountpoints from fstab in reverse order. + */ +int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs **fs) +{ + int rc = 1; + + if (!tb || !itr) + return -EINVAL; + if (fs) + *fs = NULL; + + if (!itr->head) + MNT_ITER_INIT(itr, &tb->ents); + if (itr->p != itr->head) { + if (fs) + *fs = MNT_ITER_GET_ENTRY(itr, struct libmnt_fs, ents); + MNT_ITER_ITERATE(itr); + rc = 0; + } + + return rc; +} + +/** + * mnt_table_first_fs: + * @tb: tab pointer + * @fs: NULL or returns the first tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_first_fs(struct libmnt_table *tb, struct libmnt_fs **fs) +{ + if (!tb) + return -EINVAL; + if (list_empty(&tb->ents)) + return 1; + if (fs) + *fs = list_first_entry(&tb->ents, struct libmnt_fs, ents); + return 0; +} + +/** + * mnt_table_last_fs: + * @tb: tab pointer + * @fs: NULL or returns the last tab entry + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_table_last_fs(struct libmnt_table *tb, struct libmnt_fs **fs) +{ + if (!tb) + return -EINVAL; + if (list_empty(&tb->ents)) + return 1; + if (fs) + *fs = list_last_entry(&tb->ents, struct libmnt_fs, ents); + return 0; +} + +/** + * mnt_table_find_next_fs: + * @tb: table + * @itr: iterator + * @match_func: function returning 1 or 0 + * @userdata: extra data for match_func + * @fs: NULL or returns pointer to the next matching table entry + * + * This function allows searching 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) +{ + struct libmnt_fs *re = NULL; + int match = 0; + + if (!tb || !itr || !match_func) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "lookup next fs")); + + if (fs) + *fs = NULL; + if (!itr->head) + MNT_ITER_INIT(itr, &tb->ents); + + while (!match) { + if (itr->p != itr->head) { + re = MNT_ITER_GET_ENTRY(itr, struct libmnt_fs, ents); + MNT_ITER_ITERATE(itr); + } else + return 1; /*end */ + + match = match_func(re, userdata); + } + + if (fs) + *fs = re; + return 0; +} + +static int mnt_table_move_parent(struct libmnt_table *tb, int oldid, int newid) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + + if (!tb) + return -EINVAL; + if (list_empty(&tb->ents)) + return 0; + + DBG(TAB, ul_debugobj(tb, "moving parent ID from %d -> %d", oldid, newid)); + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (fs->parent == oldid) + fs->parent = newid; + } + return 0; +} + +/** + * mnt_table_uniq_fs: + * @tb: table + * @flags: MNT_UNIQ_* + * @cmp: function to compare filesystems + * + * This function de-duplicate the @tb, but does not change order of the + * filesystems. The @cmp function has to return 0 if the filesystems are + * equal, otherwise non-zero. + * + * The default is to keep in the table later mounted filesystems (function uses + * backward mode iterator). + * + * @MNT_UNIQ_FORWARD: remove later mounted filesystems + * @MNT_UNIQ_KEEPTREE: keep parent->id relationship still valid + * + * Returns: negative number in case of error, or 0 o success. + */ +int mnt_table_uniq_fs(struct libmnt_table *tb, int flags, + int (*cmp)(struct libmnt_table *, + struct libmnt_fs *, + struct libmnt_fs *)) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + int direction = MNT_ITER_BACKWARD; + + if (!tb || !cmp) + return -EINVAL; + if (list_empty(&tb->ents)) + return 0; + + if (flags & MNT_UNIQ_FORWARD) + direction = MNT_ITER_FORWARD; + + DBG(TAB, ul_debugobj(tb, "de-duplicate")); + mnt_reset_iter(&itr, direction); + + if ((flags & MNT_UNIQ_KEEPTREE) && !is_mountinfo(tb)) + flags &= ~MNT_UNIQ_KEEPTREE; + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + int want = 1; + struct libmnt_iter xtr; + struct libmnt_fs *x; + + mnt_reset_iter(&xtr, direction); + while (want && mnt_table_next_fs(tb, &xtr, &x) == 0) { + if (fs == x) + break; + want = cmp(tb, x, fs) != 0; + } + + if (!want) { + if (flags & MNT_UNIQ_KEEPTREE) + mnt_table_move_parent(tb, mnt_fs_get_id(fs), + mnt_fs_get_parent_id(fs)); + + DBG(TAB, ul_debugobj(tb, "remove duplicate %s", + mnt_fs_get_target(fs))); + mnt_table_remove_fs(tb, fs); + } + } + + return 0; +} + +/** + * mnt_table_set_iter: + * @tb: tab pointer + * @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) +{ + if (!tb || !itr || !fs) + return -EINVAL; + + if (fs->tab != tb) + return -ENOENT; + + 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 as mnt_get_mountpoint(), except 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, ul_debugobj(tb, "lookup MOUNTPOINT: '%s'", path)); + + if (!mnt_is_path(path)) + return NULL; + + 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) + 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 the given tab, three iterations are possible, the first + * with @path, the second with realpath(@path) and the third with realpath(@path) + * against realpath(fs->target). The 2nd and 3rd iterations are not performed when + * the @tb cache is not set (see mnt_table_set_cache()). If + * mnt_cache_set_targets(cache, mtab) was called, the 3rd iteration skips any + * @fs->target found in @mtab (see mnt_resolve_target()). + * + * Returns: a tab entry or NULL. + */ +struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *path, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + char *cn; + + if (!tb || !path || !*path) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(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; + } + + /* try absolute path */ + if (is_relative_path(path) && (cn = absolute_path(path))) { + DBG(TAB, ul_debugobj(tb, "lookup absolute TARGET: '%s'", cn)); + mnt_reset_iter(&itr, direction); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, cn)) { + free(cn); + return fs; + } + } + free(cn); + } + + if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache))) + return NULL; + + DBG(TAB, ul_debugobj(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-canonical path in struct libmnt_table + * -- note that mountpoint in /proc/self/mountinfo is already + * canonicalized by the 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_target(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 the given tab, four iterations are possible, the first + * with @path, the second with realpath(@path), the third with tags (LABEL, UUID, ..) + * from @path and the fourth with realpath(@path) against realpath(entry->srcpath). + * + * The 2nd, 3rd and 4th iterations are not performed when the @tb cache is not + * set (see mnt_table_set_cache()). + * + * For btrfs returns tab entry for default id. + * + * 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, nents; + char *cn; + const char *p; + + if (!tb || !path || !*path) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(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)) { +#ifdef HAVE_BTRFS_SUPPORT + if (fs->fstype && !strcmp(fs->fstype, "btrfs")) { + uint64_t default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs)); + char *val; + size_t len; + + if (default_id == UINT64_MAX) + DBG(TAB, ul_debug("not found btrfs volume setting")); + + else if (mnt_fs_get_option(fs, "subvolid", &val, &len) == 0) { + uint64_t subvol_id; + + if (mnt_parse_offset(val, len, &subvol_id)) { + DBG(TAB, ul_debugobj(tb, "failed to parse subvolid=")); + continue; + } + if (subvol_id != default_id) + continue; + } + } +#endif /* HAVE_BTRFS_SUPPORT */ + 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, ul_debugobj(tb, "lookup canonical SRCPATH: '%s'", cn)); + + nents = mnt_table_get_nents(tb); + + /* canonicalized paths in struct libmnt_table */ + if (ntags < nents) { + 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 inaccessible, try evaluating all TAGs in @tb + * by udev symlinks -- this could be expensive on systems + * with a 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 <= nents) { + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_is_netfs(fs) || mnt_fs_is_pseudofs(fs)) + 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 the given tab, the first attempt is to lookup by @tag and + * @val, for the second attempt the tag is evaluated (converted to the device + * name) and mnt_table_find_srcpath() is performed. 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; + + if (!tb || !tag || !*tag || !val) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(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_target_with_option: + * @tb: tab pointer + * @path: mountpoint directory + * @option: option name (e.g "subvol", "subvolid", ...) + * @val: option value or NULL + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Try to lookup an entry in the given tab that matches combination of @path + * and @option. In difference to mnt_table_find_target(), only @path iteration + * is done. No lookup by device name, no canonicalization. + * + * Returns: a tab entry or NULL. + * + * Since: 2.28 + */ +struct libmnt_fs *mnt_table_find_target_with_option( + struct libmnt_table *tb, const char *path, + const char *option, const char *val, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + char *optval = NULL; + size_t optvalsz = 0, valsz = val ? strlen(val) : 0; + + if (!tb || !path || !*path || !option || !*option || !val) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s' with OPTION %s %s", path, option, val)); + + /* look up by native @target with OPTION */ + mnt_reset_iter(&itr, direction); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + if (mnt_fs_streq_target(fs, path) + && mnt_fs_get_option(fs, option, &optval, &optvalsz) == 0 + && (!val || (optvalsz == valsz + && strncmp(optval, val, optvalsz) == 0))) + return fs; + } + return NULL; +} + +/** + * mnt_table_find_source: + * @tb: tab pointer + * @source: TAG or path + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * This is a high-level API for mnt_table_find_{srcpath,tag}. You needn't care + * about the @source format (device, LABEL, UUID, ...). This function parses + * the @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; + char *t = NULL, *v = NULL; + + if (!tb) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup SOURCE: '%s'", source)); + + if (blkid_parse_tag_string(source, &t, &v) || !mnt_valid_tagname(t)) + fs = mnt_table_find_srcpath(tb, source, direction); + else + fs = mnt_table_find_tag(tb, t, v, direction); + + free(t); + free(v); + + 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 than + * 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; + + if (!tb || !target || !*target || !source || !*source) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(tb, "lookup SOURCE: %s TARGET: %s", source, target)); + + mnt_reset_iter(&itr, direction); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + + 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 a valid device number for the 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; + + if (!tb) + return NULL; + if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD) + return NULL; + + DBG(TAB, ul_debugobj(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; +} + +static char *remove_mountpoint_from_path(const char *path, const char *mnt) +{ + char *res; + const char *p; + size_t sz; + + sz = strlen(mnt); + p = sz > 1 ? path + sz : path; + + res = *p ? strdup(p) : strdup("/"); + DBG(UTILS, ul_debug("%s fs-root is %s", path, res)); + return res; +} + +#ifdef HAVE_BTRFS_SUPPORT +static int get_btrfs_fs_root(struct libmnt_table *tb, struct libmnt_fs *fs, char **root) +{ + char *vol = NULL, *p; + size_t sz, volsz = 0; + + DBG(BTRFS, ul_debug("lookup for btrfs FS root")); + *root = NULL; + + if (mnt_fs_get_option(fs, "subvolid", &vol, &volsz) == 0) { + char *target; + struct libmnt_fs *f; + char subvolidstr[sizeof(stringify_value(UINT64_MAX))]; + + DBG(BTRFS, ul_debug(" found subvolid=%s, checking", vol)); + + assert (volsz + 1 < sizeof(stringify_value(UINT64_MAX))); + memcpy(subvolidstr, vol, volsz); + subvolidstr[volsz] = '\0'; + + target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache); + if (!target) + goto err; + + DBG(BTRFS, ul_debug(" trying target=%s subvolid=%s", target, subvolidstr)); + f = mnt_table_find_target_with_option(tb, target, + "subvolid", subvolidstr, + MNT_ITER_BACKWARD); + if (!tb->cache) + free(target); + if (!f) + goto not_found; + + /* Instead of set of BACKREF queries constructing subvol path + * corresponding to a particular subvolid, use the one in + * mountinfo. Kernel keeps subvol path up to date. + */ + if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0) + goto not_found; + + } else if (mnt_fs_get_option(fs, "subvol", &vol, &volsz) != 0) { + /* If fstab entry does not contain "subvol", we have to + * check, whether btrfs has default subvolume defined. + */ + uint64_t default_id; + char *target; + struct libmnt_fs *f; + char default_id_str[sizeof(stringify_value(UINT64_MAX))]; + + DBG(BTRFS, ul_debug(" subvolid/subvol not found, checking default")); + + default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs)); + if (default_id == UINT64_MAX) + goto not_found; + + /* Volume has default subvolume. Check if it matches to + * the one in mountinfo. + * + * Only kernel >= 4.2 reports subvolid. On older + * kernels, there is no reasonable way to detect which + * subvolume was mounted. + */ + target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache); + if (!target) + goto err; + + snprintf(default_id_str, sizeof(default_id_str), "%llu", + (unsigned long long int) default_id); + + DBG(BTRFS, ul_debug(" trying target=%s default subvolid=%s", + target, default_id_str)); + + f = mnt_table_find_target_with_option(tb, target, + "subvolid", default_id_str, + MNT_ITER_BACKWARD); + if (!tb->cache) + free(target); + if (!f) + goto not_found; + + /* Instead of set of BACKREF queries constructing + * subvol path, use the one in mountinfo. Kernel does + * the evaluation for us. + */ + DBG(BTRFS, ul_debug("setting FS root: btrfs default subvolid = %s", + default_id_str)); + + if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0) + goto not_found; + } + + DBG(BTRFS, ul_debug(" using subvol=%s", vol)); + sz = volsz; + if (*vol != '/') + sz++; + *root = malloc(sz + 1); + if (!*root) + goto err; + p = *root; + if (*vol != '/') + *p++ = '/'; + memcpy(p, vol, volsz); + *(*root + sz) = '\0'; + return 0; + +not_found: + DBG(BTRFS, ul_debug(" not found btrfs volume setting")); + return 1; +err: + DBG(BTRFS, ul_debug(" error on btrfs volume setting evaluation")); + return errno ? -errno : -1; +} +#endif /* HAVE_BTRFS_SUPPORT */ + +static const char *get_cifs_unc_subdir_path (const char *unc) +{ + /* + * 1 or more slash: %*[/] + * 1 or more non-slash: %*[^/] + * number of byte read: %n + */ + int share_end = 0; + int r = sscanf(unc, "%*[/]%*[^/]%*[/]%*[^/]%n", &share_end); + if (r == EOF || share_end == 0) + return NULL; + return unc + share_end; +} + +/* + * tb: /proc/self/mountinfo + * fs: filesystem + * mountflags: MS_BIND or 0 + * fsroot: fs-root that will probably be used in the mountinfo file + * for @fs after mount(2) + * + * For btrfs subvolumes this function returns NULL, but @fsroot properly set. + * + * If @tb is NULL then defaults to '/'. + * + * Returns: entry from @tb that will be used as a source for @fs if the @fs is + * bindmount. + * + * 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; + const char *mnt = NULL; + struct libmnt_fs *src_fs = NULL; + + assert(fs); + assert(fsroot); + + DBG(TAB, ul_debug("lookup fs-root for '%s'", mnt_fs_get_source(fs))); + + if (tb && (mountflags & MS_BIND)) { + const char *src, *src_root; + char *xsrc = NULL; + + DBG(TAB, ul_debug("fs-root for bind")); + + src = xsrc = mnt_resolve_spec(mnt_fs_get_source(fs), tb->cache); + if (src) { + struct libmnt_fs *f = mnt_table_find_mountpoint(tb, + src, MNT_ITER_BACKWARD); + if (f) + mnt = mnt_fs_get_target(f); + } + if (mnt) + root = remove_mountpoint_from_path(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, ul_debug("not found '%s' in mountinfo -- using default", mnt)); + goto dflt; + } + + /* It's possible that fstab_fs source is subdirectory on btrfs + * subvolume or another bind mount. For example: + * + * /dev/sdc /mnt/test btrfs subvol=/anydir + * /dev/sdc /mnt/test btrfs defaults + * /mnt/test/foo /mnt/test2 auto bind + * + * in this case, the root for /mnt/test2 will be /anydir/foo on + * /dev/sdc. It means we have to compose the final root from + * root and src_root. + */ + src_root = mnt_fs_get_root(src_fs); + + DBG(FS, ul_debugobj(fs, "source root: %s, source FS root: %s", root, src_root)); + + if (src_root && root && !startswith(root, src_root)) { + if (strcmp(root, "/") == 0) { + free(root); + root = strdup(src_root); + if (!root) + goto err; + } else { + char *tmp; + if (asprintf(&tmp, "%s%s", src_root, root) < 0) + goto err; + free(root); + root = tmp; + } + } + } + +#ifdef HAVE_BTRFS_SUPPORT + /* + * btrfs-subvolume mount -- get subvolume name and use it as a root-fs path + */ + else if (tb && fs->fstype && + (!strcmp(fs->fstype, "btrfs") || !strcmp(fs->fstype, "auto"))) { + if (get_btrfs_fs_root(tb, fs, &root) < 0) + goto err; + } +#endif /* HAVE_BTRFS_SUPPORT */ + +dflt: + if (!root) { + root = strdup("/"); + if (!root) + goto err; + } + *fsroot = root; + + DBG(TAB, ul_debug("FS root result: %s", root)); + + return src_fs; +err: + free(root); + return NULL; +} + + +int __mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs, + const char *tgt_prefix) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + + char *root = NULL; + char *src2 = NULL; + const char *src = NULL, *tgt = NULL; + char *xtgt = NULL, *tgt_buf = NULL; + int rc = 0; + dev_t devno = 0; + + DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: target=%s, source=%s", + mnt_fs_get_target(fstab_fs), + mnt_fs_get_source(fstab_fs))); + + if (mnt_fs_is_swaparea(fstab_fs) || mnt_table_is_empty(tb)) { + DBG(FS, ul_debugobj(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 || + mnt_fs_get_option(fstab_fs, "rbind", NULL, NULL) == 0) + flags = MS_BIND; + + rootfs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root); + if (rootfs) { + const char *fstype = mnt_fs_get_fstype(rootfs); + + src = mnt_fs_get_srcpath(rootfs); + if (fstype && strncmp(fstype, "nfs", 3) == 0 && root) { + /* NFS stores the root at the end of the source */ + src = src2 = strconcat(src, root); + free(root); + root = NULL; + } + } + } + + 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); + + if (src && root) { + struct stat st; + + devno = mnt_fs_get_devno(fstab_fs); + if (!devno && mnt_safe_stat(src, &st) == 0 && S_ISBLK(st.st_mode)) + devno = st.st_rdev; + } + + tgt = mnt_fs_get_target(fstab_fs); + + if (!tgt || !src) { + DBG(FS, ul_debugobj(fstab_fs, "- ignore (no source/target)")); + goto done; + } + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: src=%s, tgt=%s, root=%s", src, tgt, root)); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + + int eq = mnt_fs_streq_srcpath(fs, src); + + if (!eq && devno && mnt_fs_get_devno(fs) == devno) + eq = 1; + + if (!eq) { + /* 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 = 0; + + if (!mnt_fs_get_srcpath(fs) || + !startswith(mnt_fs_get_srcpath(fs), "/dev/loop")) + continue; /* does not look like loopdev */ + + if (mnt_fs_get_option(fstab_fs, "offset", &val, &len) == 0) { + if (mnt_parse_offset(val, len, &offset)) { + DBG(FS, ul_debugobj(fstab_fs, "failed to parse offset=")); + continue; + } + flags = LOOPDEV_FL_OFFSET; + } + + DBG(FS, ul_debugobj(fs, "checking for loop: src=%s", mnt_fs_get_srcpath(fs))); +#if __linux__ + if (!loopdev_is_used(mnt_fs_get_srcpath(fs), src, offset, 0, flags)) + continue; + + DBG(FS, ul_debugobj(fs, "used loop")); +#endif + } + + if (root) { + const char *fstype = mnt_fs_get_fstype(fs); + + if (fstype && (strcmp(fstype, "cifs") == 0 || + strcmp(fstype, "smb3") == 0)) { + + const char *sub = get_cifs_unc_subdir_path(src); + const char *r = mnt_fs_get_root(fs); + + if (!sub || !r || (!streq_paths(sub, r) && + !streq_paths("/", r))) + continue; + } else { + const char *r = mnt_fs_get_root(fs); + if (!r || strcmp(r, root) != 0) + continue; + } + } + + /* + * Compare target, try to minimize the number of situations when we + * need to canonicalize the path to avoid readlink() on + * mountpoints. + */ + if (!xtgt) { + if (tgt_prefix) { + const char *p = *tgt == '/' ? tgt + 1 : tgt; + if (!*p) + tgt = tgt_prefix; /* target is '/' */ + else { + if (asprintf(&tgt_buf, "%s/%s", tgt_prefix, p) <= 0) { + rc = -ENOMEM; + goto done; + } + tgt = tgt_buf; + } + } + + 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); + free(tgt_buf); + + DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc)); + free(src2); + return rc; +} + +/** + * mnt_table_is_fs_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 the 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 + * trigger automount on autofs mountpoints! + * + * Don't use it if you want to know if a device is mounted, just use + * mnt_table_find_source() on 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) +{ + return __mnt_table_is_fs_mounted(tb, fstab_fs, NULL); +} + + +#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 the default */ +} + +static struct libmnt_table *create_table(const char *file, int comments) +{ + struct libmnt_table *tb; + + if (!file) + return NULL; + tb = mnt_new_table(); + if (!tb) + goto err; + + mnt_table_enable_comments(tb, comments); + 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_unref_table(tb); + return NULL; +} + +static 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], FALSE); + 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_unref_fs(fs); + rc = 0; +done: + mnt_unref_table(tb); + return rc; +} + +static 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; + int parse_comments = FALSE; + + if (argc == 3 && !strcmp(argv[2], "--comments")) + parse_comments = TRUE; + + tb = create_table(argv[1], parse_comments); + if (!tb) + return -1; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto done; + + if (mnt_table_get_intro_comment(tb)) + fprintf(stdout, "Initial comment:\n\"%s\"\n", + mnt_table_get_intro_comment(tb)); + + while(mnt_table_next_fs(tb, itr, &fs) == 0) + mnt_fs_print_debug(fs, stdout); + + if (mnt_table_get_trailing_comment(tb)) + fprintf(stdout, "Trailing comment:\n\"%s\"\n", + mnt_table_get_trailing_comment(tb)); + rc = 0; +done: + mnt_free_iter(itr); + mnt_unref_table(tb); + return rc; +} + +static int test_find_idx(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + struct libmnt_fs *fs = NULL; + struct libmnt_cache *mpc = NULL; + const char *file, *what; + int rc = -1; + + if (argc != 3) { + fprintf(stderr, "try --help\n"); + return -EINVAL; + } + + file = argv[1], what = argv[2]; + + tb = create_table(file, FALSE); + if (!tb) + goto done; + + /* create a cache for canonicalized paths */ + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + fs = mnt_table_find_target(tb, what, MNT_ITER_BACKWARD); + + if (!fs) + fprintf(stderr, "%s: not found '%s'\n", file, what); + else { + int idx = mnt_table_find_fs(tb, fs); + + if (idx < 1) + fprintf(stderr, "%s: not found '%s' fs pointer", file, what); + else { + printf("%s index is %d\n", what, idx); + rc = 0; + } + } +done: + mnt_unref_table(tb); + return rc; +} + +static 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, FALSE); + if (!tb) + goto done; + + /* create a cache for canonicalized paths */ + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + if (strcasecmp(find, "source") == 0) + fs = mnt_table_find_source(tb, what, dr); + 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_unref_table(tb); + return rc; +} + +static int test_find_bw(struct libmnt_test *ts, int argc, char *argv[]) +{ + return test_find(ts, argc, argv, MNT_ITER_BACKWARD); +} + +static int test_find_fw(struct libmnt_test *ts, int argc, char *argv[]) +{ + return test_find(ts, argc, argv, MNT_ITER_FORWARD); +} + +static 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], FALSE); + if (!tb) + return -1; + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, mpc); + mnt_unref_cache(mpc); + + fs = mnt_table_find_pair(tb, argv[2], argv[3], MNT_ITER_FORWARD); + if (!fs) + goto done; + + mnt_fs_print_debug(fs, stdout); + rc = 0; +done: + mnt_unref_table(tb); + return rc; +} + +static 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); + mnt_unref_cache(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_unref_table(tb); + 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; + + 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], FALSE); + 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); + mnt_unref_cache(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)); + } + +done: + mnt_unref_table(tb); + mnt_unref_table(fstab); + mnt_free_iter(itr); + return 0; +} + +/* returns 0 if @a and @b targets are the same */ +static int test_uniq_cmp(struct libmnt_table *tb __attribute__((__unused__)), + struct libmnt_fs *a, + struct libmnt_fs *b) +{ + assert(a); + assert(b); + + return mnt_fs_streq_target(a, mnt_fs_get_target(b)) ? 0 : 1; +} + +static int test_uniq(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb; + int rc = -1; + + if (argc != 2) { + fprintf(stderr, "try --help\n"); + return -EINVAL; + } + + tb = create_table(argv[1], FALSE); + if (!tb) + goto done; + + if (mnt_table_uniq_fs(tb, 0, test_uniq_cmp) == 0) { + struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD); + struct libmnt_fs *fs; + if (!itr) + goto done; + while (mnt_table_next_fs(tb, itr, &fs) == 0) + mnt_fs_print_debug(fs, stdout); + mnt_free_iter(itr); + rc = 0; + } +done: + mnt_unref_table(tb); + return rc; +} + + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--parse", test_parse, "<file> [--comments] parse and print tab" }, + { "--find-forward", test_find_fw, "<file> <source|target> <string>" }, + { "--find-backward", test_find_bw, "<file> <source|target> <string>" }, + { "--uniq-target", test_uniq, "<file>" }, + { "--find-pair", test_find_pair, "<file> <source> <target>" }, + { "--find-fs", test_find_idx, "<file> <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 fstab is already mounted" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/tab_diff.c b/libmount/src/tab_diff.c new file mode 100644 index 0000000..d5fbc4d --- /dev/null +++ b/libmount/src/tab_diff.c @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2011-2018 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. + */ + +/** + * SECTION: tabdiff + * @title: Compare changes in mount tables + * @short_description: compare changes in the list of the mounted filesystems + */ +#include "mountP.h" + +struct tabdiff_entry { + int oper; /* MNT_TABDIFF_* flags; */ + + struct libmnt_fs *old_fs; /* pointer to the old FS */ + struct libmnt_fs *new_fs; /* pointer to the new FS */ + + struct list_head changes; +}; + +struct libmnt_tabdiff { + int nchanges; /* number of changes */ + + struct list_head changes; /* list with modified entries */ + struct list_head unused; /* list with unused entries */ +}; + +/** + * mnt_new_tabdiff: + * + * Allocates a new table diff struct. + * + * Returns: new diff handler or NULL. + */ +struct libmnt_tabdiff *mnt_new_tabdiff(void) +{ + struct libmnt_tabdiff *df = calloc(1, sizeof(*df)); + + if (!df) + return NULL; + + DBG(DIFF, ul_debugobj(df, "alloc")); + + INIT_LIST_HEAD(&df->changes); + INIT_LIST_HEAD(&df->unused); + return df; +} + +static void free_tabdiff_entry(struct tabdiff_entry *de) +{ + if (!de) + return; + list_del(&de->changes); + mnt_unref_fs(de->new_fs); + mnt_unref_fs(de->old_fs); + free(de); +} + +/** + * mnt_free_tabdiff: + * @df: tab diff + * + * Deallocates tab diff struct and all entries. + */ +void mnt_free_tabdiff(struct libmnt_tabdiff *df) +{ + if (!df) + return; + + DBG(DIFF, ul_debugobj(df, "free")); + + while (!list_empty(&df->changes)) { + struct tabdiff_entry *de = list_entry(df->changes.next, + struct tabdiff_entry, changes); + free_tabdiff_entry(de); + } + + free(df); +} + +/** + * mnt_tabdiff_next_change: + * @df: tabdiff pointer + * @itr: iterator + * @old_fs: returns the old entry or NULL if new entry added + * @new_fs: returns the new entry or NULL if old entry removed + * @oper: MNT_TABDIFF_{MOVE,UMOUNT,REMOUNT,MOUNT} flags + * + * The options @old_fs, @new_fs and @oper are optional. + * + * Returns: 0 on success, negative number in case of error or 1 at the end of list. + */ +int mnt_tabdiff_next_change(struct libmnt_tabdiff *df, struct libmnt_iter *itr, + struct libmnt_fs **old_fs, struct libmnt_fs **new_fs, int *oper) +{ + int rc = 1; + struct tabdiff_entry *de = NULL; + + if (!df || !itr) + return -EINVAL; + + if (!itr->head) + MNT_ITER_INIT(itr, &df->changes); + if (itr->p != itr->head) { + de = MNT_ITER_GET_ENTRY(itr, struct tabdiff_entry, changes); + MNT_ITER_ITERATE(itr); + rc = 0; + } + + if (old_fs) + *old_fs = de ? de->old_fs : NULL; + if (new_fs) + *new_fs = de ? de->new_fs : NULL; + if (oper) + *oper = de ? de->oper : 0; + + return rc; +} + +static int tabdiff_reset(struct libmnt_tabdiff *df) +{ + assert(df); + + DBG(DIFF, ul_debugobj(df, "resetting")); + + /* zeroize all entries and move them to the list of unused + */ + while (!list_empty(&df->changes)) { + struct tabdiff_entry *de = list_entry(df->changes.next, + struct tabdiff_entry, changes); + + list_del_init(&de->changes); + list_add_tail(&de->changes, &df->unused); + + mnt_unref_fs(de->new_fs); + mnt_unref_fs(de->old_fs); + + de->new_fs = de->old_fs = NULL; + de->oper = 0; + } + + df->nchanges = 0; + return 0; +} + +static int tabdiff_add_entry(struct libmnt_tabdiff *df, struct libmnt_fs *old, + struct libmnt_fs *new, int oper) +{ + struct tabdiff_entry *de; + + assert(df); + + DBG(DIFF, ul_debugobj(df, "add change on %s", + mnt_fs_get_target(new ? new : old))); + + if (!list_empty(&df->unused)) { + de = list_entry(df->unused.next, struct tabdiff_entry, changes); + list_del(&de->changes); + } else { + de = calloc(1, sizeof(*de)); + if (!de) + return -ENOMEM; + } + + INIT_LIST_HEAD(&de->changes); + + mnt_ref_fs(new); + mnt_ref_fs(old); + + mnt_unref_fs(de->new_fs); + mnt_unref_fs(de->old_fs); + + de->old_fs = old; + de->new_fs = new; + de->oper = oper; + + list_add_tail(&de->changes, &df->changes); + df->nchanges++; + return 0; +} + +static struct tabdiff_entry *tabdiff_get_mount(struct libmnt_tabdiff *df, + const char *src, + int id) +{ + struct list_head *p; + + assert(df); + + list_for_each(p, &df->changes) { + struct tabdiff_entry *de; + + de = list_entry(p, struct tabdiff_entry, changes); + + if (de->oper == MNT_TABDIFF_MOUNT && de->new_fs && + mnt_fs_get_id(de->new_fs) == id) { + + const char *s = mnt_fs_get_source(de->new_fs); + + if (s == NULL && src == NULL) + return de; + if (s && src && strcmp(s, src) == 0) + return de; + } + } + return NULL; +} + +/** + * mnt_diff_tables: + * @df: diff handler + * @old_tab: old table + * @new_tab: new table + * + * Compares @old_tab and @new_tab, the result is stored in @df and accessible by + * mnt_tabdiff_next_change(). + * + * Returns: number of changes, negative number in case of error. + */ +int mnt_diff_tables(struct libmnt_tabdiff *df, struct libmnt_table *old_tab, + struct libmnt_table *new_tab) +{ + struct libmnt_fs *fs; + struct libmnt_iter itr; + int no, nn; + + if (!df || !old_tab || !new_tab) + return -EINVAL; + + tabdiff_reset(df); + + no = mnt_table_get_nents(old_tab); + nn = mnt_table_get_nents(new_tab); + + if (!no && !nn) /* both tables are empty */ + return 0; + + DBG(DIFF, ul_debugobj(df, "analyze new (%d entries), " + "old (%d entries)", + nn, no)); + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + /* all mounted or umounted */ + if (!no && nn) { + while(mnt_table_next_fs(new_tab, &itr, &fs) == 0) + tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT); + goto done; + + } else if (no && !nn) { + while(mnt_table_next_fs(old_tab, &itr, &fs) == 0) + tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT); + goto done; + } + + /* search newly mounted or modified */ + while(mnt_table_next_fs(new_tab, &itr, &fs) == 0) { + struct libmnt_fs *o_fs; + const char *src = mnt_fs_get_source(fs), + *tgt = mnt_fs_get_target(fs); + + o_fs = mnt_table_find_pair(old_tab, src, tgt, MNT_ITER_FORWARD); + if (!o_fs) + /* 'fs' is not in the old table -- so newly mounted */ + tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT); + else { + /* is modified? */ + const char *v1 = mnt_fs_get_vfs_options(o_fs), + *v2 = mnt_fs_get_vfs_options(fs), + *f1 = mnt_fs_get_fs_options(o_fs), + *f2 = mnt_fs_get_fs_options(fs); + + if ((v1 && v2 && strcmp(v1, v2) != 0) || (f1 && f2 && strcmp(f1, f2) != 0)) + tabdiff_add_entry(df, o_fs, fs, MNT_TABDIFF_REMOUNT); + } + } + + /* search umounted or moved */ + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while(mnt_table_next_fs(old_tab, &itr, &fs) == 0) { + const char *src = mnt_fs_get_source(fs), + *tgt = mnt_fs_get_target(fs); + + if (!mnt_table_find_pair(new_tab, src, tgt, MNT_ITER_FORWARD)) { + struct tabdiff_entry *de; + + de = tabdiff_get_mount(df, src, mnt_fs_get_id(fs)); + if (de) { + mnt_ref_fs(fs); + mnt_unref_fs(de->old_fs); + de->oper = MNT_TABDIFF_MOVE; + de->old_fs = fs; + } else + tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT); + } + } +done: + DBG(DIFF, ul_debugobj(df, "%d changes detected", df->nchanges)); + return df->nchanges; +} + +#ifdef TEST_PROGRAM + +static int test_diff(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb_old, *tb_new; + struct libmnt_tabdiff *diff; + struct libmnt_iter *itr; + struct libmnt_fs *old, *new; + int rc = -1, change; + + tb_old = mnt_new_table_from_file(argv[1]); + tb_new = mnt_new_table_from_file(argv[2]); + diff = mnt_new_tabdiff(); + itr = mnt_new_iter(MNT_ITER_FORWARD); + + if (!tb_old || !tb_new || !diff || !itr) { + warnx("failed to allocate resources"); + goto done; + } + + rc = mnt_diff_tables(diff, tb_old, tb_new); + if (rc < 0) + goto done; + + while(mnt_tabdiff_next_change(diff, itr, &old, &new, &change) == 0) { + + printf("%s on %s: ", mnt_fs_get_source(new ? new : old), + mnt_fs_get_target(new ? new : old)); + + switch(change) { + case MNT_TABDIFF_MOVE: + printf("MOVED to %s\n", mnt_fs_get_target(new)); + break; + case MNT_TABDIFF_UMOUNT: + printf("UMOUNTED\n"); + break; + case MNT_TABDIFF_REMOUNT: + printf("REMOUNTED from '%s' to '%s'\n", + mnt_fs_get_options(old), + mnt_fs_get_options(new)); + break; + case MNT_TABDIFF_MOUNT: + printf("MOUNTED\n"); + break; + default: + printf("unknown change!\n"); + } + } + + rc = 0; +done: + mnt_unref_table(tb_old); + mnt_unref_table(tb_new); + mnt_free_tabdiff(diff); + mnt_free_iter(itr); + return rc; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--diff", test_diff, "<old> <new> prints change" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/tab_parse.c b/libmount/src/tab_parse.c new file mode 100644 index 0000000..b8ec244 --- /dev/null +++ b/libmount/src/tab_parse.c @@ -0,0 +1,1322 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2009-2018 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. + */ +#ifdef HAVE_SCANDIRAT +#ifndef __USE_GNU +#define __USE_GNU +#endif /* !__USE_GNU */ +#endif /* HAVE_SCANDIRAT */ + +#include <ctype.h> +#include <limits.h> +#include <dirent.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "fileutils.h" +#include "mangle.h" +#include "mountP.h" +#include "pathnames.h" +#include "strutils.h" + +struct libmnt_parser { + FILE *f; /* fstab, swaps or mountinfo ... */ + const char *filename; /* file name or NULL */ + char *buf; /* buffer (the current line content) */ + size_t bufsiz; /* size of the buffer */ + size_t line; /* current line */ + int sysroot_rc; /* rc from mnt_guess_system_root() */ + char *sysroot; /* guess from mmnt_guess_system_root() */ +}; + +static void parser_cleanup(struct libmnt_parser *pa) +{ + if (!pa) + return; + free(pa->buf); + free(pa->sysroot); + memset(pa, 0, sizeof(*pa)); +} + +static const char *next_s32(const char *s, int *num, int *rc) +{ + char *end = NULL; + + if (!s || !*s) + return s; + + errno = 0; + *rc = -EINVAL; + *num = strtol(s, &end, 10); + if (end == NULL || s == end) + return s; + if (errno == 0 && (*end == ' ' || *end == '\t' || *end == '\0')) + *rc = 0; + return end; +} + +static const char *next_u64(const char *s, uint64_t *num, int *rc) +{ + char *end = NULL; + + if (!s || !*s) + return s; + + errno = 0; + *rc = -EINVAL; + *num = (uint64_t) strtoumax(s, &end, 10); + if (end == NULL || s == end) + return s; + if (errno == 0 && (*end == ' ' || *end == '\t' || *end == '\0')) + *rc = 0; + return end; +} + +static inline const char *skip_separator(const char *p) +{ + while (p && (*p == ' ' || *p == '\t')) + ++p; + return p; +} + +static inline const char *skip_nonspearator(const char *p) +{ + while (p && *p && !(*p == ' ' || *p == '\t')) + p++; + return p; +} + +/* + * Parses one line from {fs,m}tab + */ +static int mnt_parse_table_line(struct libmnt_fs *fs, const char *s) +{ + int rc = 0; + char *p = NULL; + + fs->passno = fs->freq = 0; + + /* (1) source */ + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [source]")); + free(p); + goto fail; + } + + s = skip_separator(s); + + /* (2) target */ + fs->target = unmangle(s, &s); + if (!fs->target) { + DBG(TAB, ul_debug("tab parse error: [target]")); + goto fail; + } + + s = skip_separator(s); + + /* (3) FS type */ + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [fstype]")); + free(p); + goto fail; + } + + s = skip_separator(s); + + /* (4) options (optional) */ + p = unmangle(s, &s); + if (p && (rc = mnt_fs_set_options(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [options]")); + free(p); + goto fail; + } + if (!p) + goto done; + free(p); + + s = skip_separator(s); + if (!s || !*s) + goto done; + + /* (5) freq (optional) */ + s = next_s32(s, &fs->freq, &rc); + if (s && *s && rc) { + DBG(TAB, ul_debug("tab parse error: [freq]")); + goto fail; + } + + s = skip_separator(s); + if (!s || !*s) + goto done; + + /* (6) passno (optional) */ + s = next_s32(s, &fs->passno, &rc); + if (s && *s && rc) { + DBG(TAB, ul_debug("tab parse error: [passno]")); + goto fail; + } + +done: + return 0; +fail: + if (rc == 0) + rc = -EINVAL; + DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc)); + return rc; +} + + +/* + * Parses one line from a mountinfo file + */ +static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, const char *s) +{ + int rc = 0; + unsigned int maj, min; + char *p; + + fs->flags |= MNT_FS_KERNEL; + + /* (1) id */ + s = next_s32(s, &fs->id, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [id]")); + goto fail; + } + + s = skip_separator(s); + + /* (2) parent */ + s = next_s32(s, &fs->parent, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [parent]")); + goto fail; + } + + s = skip_separator(s); + + /* (3) maj:min */ + if (sscanf(s, "%u:%u", &maj, &min) != 2) { + DBG(TAB, ul_debug("tab parse error: [maj:min]")); + goto fail; + } + fs->devno = makedev(maj, min); + s = skip_nonspearator(s); + s = skip_separator(s); + + /* (4) mountroot */ + fs->root = unmangle(s, &s); + if (!fs->root) { + DBG(TAB, ul_debug("tab parse error: [mountroot]")); + goto fail; + } + + s = skip_separator(s); + + /* (5) target */ + fs->target = unmangle(s, &s); + if (!fs->target) { + DBG(TAB, ul_debug("tab parse error: [target]")); + goto fail; + } + + s = skip_separator(s); + + /* (6) vfs options (fs-independent) */ + fs->vfs_optstr = unmangle(s, &s); + if (!fs->vfs_optstr) { + DBG(TAB, ul_debug("tab parse error: [VFS options]")); + goto fail; + } + + /* (7) optional fields, terminated by " - " */ + p = strstr(s, " - "); + if (!p) { + DBG(TAB, ul_debug("mountinfo parse error: separator not found")); + return -EINVAL; + } + if (p > s + 1) + fs->opt_fields = strndup(s + 1, p - s - 1); + + s = skip_separator(p + 3); + + /* (8) FS type */ + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [fstype]")); + free(p); + goto fail; + } + + /* (9) source -- maybe empty string */ + if (!s || !*s) { + DBG(TAB, ul_debug("tab parse error: [source]")); + goto fail; + } else if (*s == ' ' && *(s+1) == ' ') { + if ((rc = mnt_fs_set_source(fs, ""))) { + DBG(TAB, ul_debug("tab parse error: [empty source]")); + goto fail; + } + } else { + s = skip_separator(s); + p = unmangle(s, &s); + if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [regular source]")); + free(p); + goto fail; + } + } + + s = skip_separator(s); + + /* (10) fs options (fs specific) */ + fs->fs_optstr = unmangle(s, &s); + if (!fs->fs_optstr) { + DBG(TAB, ul_debug("tab parse error: [FS options]")); + goto fail; + } + + /* merge VFS and FS options to one string */ + fs->optstr = mnt_fs_strdup_options(fs); + if (!fs->optstr) { + rc = -ENOMEM; + DBG(TAB, ul_debug("tab parse error: [merge VFS and FS options]")); + goto fail; + } + + return 0; +fail: + if (rc == 0) + rc = -EINVAL; + DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc)); + return rc; +} + +/* + * Parses one line from utab file + */ +static int mnt_parse_utab_line(struct libmnt_fs *fs, const char *s) +{ + const char *p = s; + + assert(fs); + assert(s); + assert(!fs->source); + assert(!fs->target); + + while (p && *p) { + const char *end = NULL; + + while (*p == ' ') p++; + if (!*p) + break; + + if (!fs->id && !strncmp(p, "ID=", 3)) { + int rc = 0; + + end = next_s32(p + 3, &fs->id, &rc); + if (!end || rc) + return rc; + + } else if (!fs->source && !strncmp(p, "SRC=", 4)) { + char *v = unmangle(p + 4, &end); + if (!v) + goto enomem; + if (__mnt_fs_set_source_ptr(fs, v)) + free(v); + + } else if (!fs->target && !strncmp(p, "TARGET=", 7)) { + fs->target = unmangle(p + 7, &end); + if (!fs->target) + goto enomem; + + } else if (!fs->root && !strncmp(p, "ROOT=", 5)) { + fs->root = unmangle(p + 5, &end); + if (!fs->root) + goto enomem; + + } else if (!fs->bindsrc && !strncmp(p, "BINDSRC=", 8)) { + fs->bindsrc = unmangle(p + 8, &end); + if (!fs->bindsrc) + goto enomem; + + } else if (!fs->user_optstr && !strncmp(p, "OPTS=", 5)) { + fs->user_optstr = unmangle(p + 5, &end); + if (!fs->user_optstr) + goto enomem; + + } else if (!fs->attrs && !strncmp(p, "ATTRS=", 6)) { + fs->attrs = unmangle(p + 6, &end); + if (!fs->attrs) + goto enomem; + + } else { + /* unknown variable */ + while (*p && *p != ' ') p++; + } + if (end) + p = end; + } + + return 0; +enomem: + DBG(TAB, ul_debug("utab parse error: ENOMEM")); + return -ENOMEM; +} + +/* + * Parses one line from /proc/swaps + */ +static int mnt_parse_swaps_line(struct libmnt_fs *fs, const char *s) +{ + uint64_t num; + int rc = 0; + char *p; + + /* (1) source */ + p = unmangle(s, &s); + if (p) { + char *x = (char *) endswith(p, PATH_DELETED_SUFFIX); + if (x && *x) + *x = '\0'; + } + if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) { + DBG(TAB, ul_debug("tab parse error: [source]")); + free(p); + goto fail; + } + + s = skip_separator(s); + + /* (2) type */ + fs->swaptype = unmangle(s, &s); + if (!fs->swaptype) { + DBG(TAB, ul_debug("tab parse error: [swaptype]")); + goto fail; + } + + s = skip_separator(s); + + /* (3) size */ + s = next_u64(s, &num, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [size]")); + goto fail; + } + fs->size = num; + + s = skip_separator(s); + + /* (4) size */ + s = next_u64(s, &num, &rc); + if (!s || !*s || rc) { + DBG(TAB, ul_debug("tab parse error: [used size]")); + goto fail; + } + fs->usedsize = num; + + s = skip_separator(s); + + /* (5) priority */ + s = next_s32(s, &fs->priority, &rc); + if (rc) { + DBG(TAB, ul_debug("tab parse error: [priority]")); + goto fail; + } + + mnt_fs_set_fstype(fs, "swap"); + return 0; +fail: + if (rc == 0) + rc = -EINVAL; + DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc)); + return rc; +} + + +/* + * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*) + * + * Note that we aren't trying to guess the utab file format, because this file + * always has to be parsed by private libmount routines with an explicitly defined + * format. + * + * mountinfo: "<number> <number> ... " + */ +static int guess_table_format(const char *line) +{ + unsigned int a, b; + + DBG(TAB, ul_debug("trying to guess table type")); + + if (sscanf(line, "%u %u", &a, &b) == 2) + return MNT_FMT_MOUNTINFO; + + if (strncmp(line, "Filename\t", 9) == 0) + return MNT_FMT_SWAPS; + + return MNT_FMT_FSTAB; /* fstab, or /proc/mounts */ +} + +static int is_comment_line(const char *line) +{ + const char *p = skip_blank(line); + + if (p && (*p == '#' || *p == '\n')) + return 1; + return 0; +} + +/* returns 1 if the last line in the @str is blank */ +static int is_terminated_by_blank(const char *str) +{ + size_t sz = str ? strlen(str) : 0; + const char *p = sz ? str + (sz - 1) : NULL; + + if (!sz || !p || *p != '\n') + return 0; /* empty or not terminated by '\n' */ + if (p == str) + return 1; /* only '\n' */ + p--; + while (p > str && (*p == ' ' || *p == '\t')) + p--; + return *p == '\n' ? 1 : 0; +} + +/* + * Reads the next line from the file. + * + * Returns 0 if the line is a comment + * 1 if the line is not a comment + * <0 on error + */ +static int next_comment_line(struct libmnt_parser *pa, char **last) +{ + if (getline(&pa->buf, &pa->bufsiz, pa->f) < 0) + return feof(pa->f) ? 1 : -errno; + + pa->line++; + *last = strchr(pa->buf, '\n'); + + return is_comment_line(pa->buf) ? 0 : 1; +} + +static int append_comment(struct libmnt_table *tb, + struct libmnt_fs *fs, + const char *comm, + int eof) +{ + int rc, intro = mnt_table_get_nents(tb) == 0; + + if (intro && is_terminated_by_blank(mnt_table_get_intro_comment(tb))) + intro = 0; + + DBG(TAB, ul_debugobj(tb, "appending %s comment", + intro ? "intro" : + eof ? "trailing" : "fs")); + if (intro) + rc = mnt_table_append_intro_comment(tb, comm); + else if (eof) { + rc = mnt_table_set_trailing_comment(tb, + mnt_fs_get_comment(fs)); + if (!rc) + rc = mnt_table_append_trailing_comment(tb, comm); + if (!rc) + rc = mnt_fs_set_comment(fs, NULL); + } else + rc = mnt_fs_append_comment(fs, comm); + return rc; +} + +/* + * Read and parse the next line from {fs,m}tab or mountinfo + */ +static int mnt_table_parse_next(struct libmnt_parser *pa, + struct libmnt_table *tb, + struct libmnt_fs *fs) +{ + char *s; + int rc; + + assert(tb); + assert(pa); + assert(fs); + + /* read the next non-blank non-comment line */ +next_line: + do { + if (getline(&pa->buf, &pa->bufsiz, pa->f) < 0) + return -EINVAL; + pa->line++; + s = strchr(pa->buf, '\n'); + if (!s) { + DBG(TAB, ul_debugobj(tb, "%s:%zu: no final newline", + pa->filename, pa->line)); + + /* Missing final newline? Otherwise an extremely */ + /* long line - assume file was corrupted */ + if (feof(pa->f)) + s = memchr(pa->buf, '\0', pa->bufsiz); + + /* comments parser */ + } else if (tb->comms + && (tb->fmt == MNT_FMT_GUESS || tb->fmt == MNT_FMT_FSTAB) + && is_comment_line(pa->buf)) { + do { + rc = append_comment(tb, fs, pa->buf, feof(pa->f)); + if (!rc) + rc = next_comment_line(pa, &s); + } while (rc == 0); + + if (rc == 1 && feof(pa->f)) + rc = append_comment(tb, fs, NULL, 1); + if (rc < 0) + return rc; + + } + + if (!s) + goto err; + *s = '\0'; + if (s > pa->buf && *(s - 1) == '\r') + *(--s) = '\0'; + s = (char *) skip_blank(pa->buf); + } while (*s == '\0' || *s == '#'); + + if (tb->fmt == MNT_FMT_GUESS) { + tb->fmt = guess_table_format(s); + if (tb->fmt == MNT_FMT_SWAPS) + goto next_line; /* skip swap header */ + } + + switch (tb->fmt) { + case MNT_FMT_FSTAB: + rc = mnt_parse_table_line(fs, s); + break; + case MNT_FMT_MOUNTINFO: + rc = mnt_parse_mountinfo_line(fs, s); + break; + case MNT_FMT_UTAB: + rc = mnt_parse_utab_line(fs, s); + break; + case MNT_FMT_SWAPS: + if (strncmp(s, "Filename\t", 9) == 0) + goto next_line; /* skip swap header */ + rc = mnt_parse_swaps_line(fs, s); + break; + default: + rc = -1; /* unknown format */ + break; + } + + if (rc == 0) + return 0; +err: + DBG(TAB, ul_debugobj(tb, "%s:%zu: %s parse error", pa->filename, pa->line, + tb->fmt == MNT_FMT_MOUNTINFO ? "mountinfo" : + tb->fmt == MNT_FMT_SWAPS ? "swaps" : + tb->fmt == MNT_FMT_FSTAB ? "tab" : "utab")); + + /* by default all errors are recoverable, otherwise behavior depends on + * the errcb() function. See mnt_table_set_parser_errcb(). + */ + return tb->errcb ? tb->errcb(tb, pa->filename, pa->line) : 1; +} + +static pid_t path_to_tid(const char *filename) +{ + char *path = mnt_resolve_path(filename, NULL); + char *p, *end = NULL; + pid_t tid = 0; + + if (!path) + goto done; + p = strrchr(path, '/'); + if (!p) + goto done; + *p = '\0'; + p = strrchr(path, '/'); + if (!p) + goto done; + p++; + + errno = 0; + tid = strtol(p, &end, 10); + if (errno || p == end || (end && *end)) { + tid = 0; + goto done; + } + DBG(TAB, ul_debug("TID for %s is %d", filename, tid)); +done: + free(path); + return tid; +} + +static int kernel_fs_postparse(struct libmnt_parser *pa, + struct libmnt_table *tb, + struct libmnt_fs *fs, pid_t *tid) +{ + int rc = 0; + const char *src = mnt_fs_get_srcpath(fs); + + /* This is a filesystem description from /proc, so we're in some process + * namespace. Let's remember the process PID. + */ + if (pa->filename && *tid == -1) + *tid = path_to_tid(pa->filename); + + fs->tid = *tid; + + /* + * Convert obscure /dev/root to something more usable + */ + if (src && strcmp(src, "/dev/root") == 0) { + + /* We will only call mnt_guess_system_root() if it has not + * been called before. Inside a container, mountinfo can contain + * many lines with /dev/root. + */ + if (pa->sysroot_rc == 0 && pa->sysroot == NULL) + pa->sysroot_rc = mnt_guess_system_root(fs->devno, + tb->cache, &pa->sysroot); + + rc = pa->sysroot_rc; + if (rc < 0) + return rc; + + /* This means that we already have run the mnt_guess_system_root() + * and that we want to reuse the result. + */ + if (rc == 0 && pa->sysroot != NULL) { + char *real = strdup(pa->sysroot); + + if (!real) + return -ENOMEM; + + DBG(TAB, ul_debugobj(tb, "canonical root FS: %s", real)); + rc = __mnt_fs_set_source_ptr(fs, real); + + } else if (rc == 1) { + /* mnt_guess_system_root() returns 1 if not able to convert to + * the real devname; ignore this problem */ + rc = 0; + } + } + + return rc; +} + +/** + * mnt_table_parse_stream: + * @tb: tab pointer + * @f: file stream + * @filename: filename used for debug and error messages + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_parse_stream(struct libmnt_table *tb, FILE *f, const char *filename) +{ + int rc = -1; + int flags = 0; + pid_t tid = -1; + struct libmnt_parser pa = { .line = 0 }; + + assert(tb); + assert(f); + assert(filename); + + DBG(TAB, ul_debugobj(tb, "%s: start parsing [entries=%d, filter=%s]", + filename, mnt_table_get_nents(tb), + tb->fltrcb ? "yes" : "not")); + + pa.filename = filename; + pa.f = f; + + /* necessary for /proc/mounts only, the /proc/self/mountinfo + * parser sets the flag properly + */ + if (tb->fmt == MNT_FMT_SWAPS) + flags = MNT_FS_SWAP; + else if (filename && strcmp(filename, _PATH_PROC_MOUNTS) == 0) + flags = MNT_FS_KERNEL; + + do { + struct libmnt_fs *fs; + + if (feof(f)) { + DBG(TAB, ul_debugobj(tb, "end-of-file")); + break; + } + fs = mnt_new_fs(); + if (!fs) + goto err; + + /* parse */ + rc = mnt_table_parse_next(&pa, tb, fs); + + if (rc == 0 && tb->fltrcb && tb->fltrcb(fs, tb->fltrcb_data)) + rc = 1; /* filtered out by callback... */ + + if (rc == 0 && mnt_table_is_noautofs(tb)) { + const char *fstype = mnt_fs_get_fstype(fs); + + if (fstype && strcmp(fstype, "autofs") == 0 && + mnt_fs_get_option(fs, "ignore", NULL, NULL) == 0) + rc = 1; /* Skip "ignore" autofs entry */ + } + + /* add to the table */ + if (rc == 0) { + rc = mnt_table_add_fs(tb, fs); + fs->flags |= flags; + + if (rc == 0 && tb->fmt == MNT_FMT_MOUNTINFO) { + rc = kernel_fs_postparse(&pa, tb, fs, &tid); + if (rc) + mnt_table_remove_fs(tb, fs); + } + } + + /* remove reference (or deallocate on error) */ + mnt_unref_fs(fs); + + /* recoverable error */ + if (rc > 0) { + DBG(TAB, ul_debugobj(tb, "recoverable error (continue)")); + continue; + } + + /* fatal errors */ + if (rc < 0 && !feof(f)) { + DBG(TAB, ul_debugobj(tb, "fatal error")); + goto err; + } + } while (1); + + DBG(TAB, ul_debugobj(tb, "%s: stop parsing (%d entries)", + filename, mnt_table_get_nents(tb))); + parser_cleanup(&pa); + return 0; +err: + DBG(TAB, ul_debugobj(tb, "%s: parse error (rc=%d)", filename, rc)); + parser_cleanup(&pa); + return rc; +} + +/** + * mnt_table_parse_file: + * @tb: tab pointer + * @filename: file + * + * Parses the whole table (e.g. /etc/fstab) and appends new records to the @tab. + * + * The libmount parser ignores broken (syntax error) lines, these lines are + * reported to the caller by the errcb() function (see mnt_table_set_parser_errcb()). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_parse_file(struct libmnt_table *tb, const char *filename) +{ + FILE *f; + int rc; + + if (!filename || !tb) + return -EINVAL; + + f = fopen(filename, "r" UL_CLOEXECSTR); + if (f) { + rc = mnt_table_parse_stream(tb, f, filename); + fclose(f); + } else + rc = -errno; + + DBG(TAB, ul_debugobj(tb, "parsing done [filename=%s, rc=%d]", filename, rc)); + return rc; +} + +static int mnt_table_parse_dir_filter(const struct dirent *d) +{ + size_t namesz; + +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && + d->d_type != DT_LNK) + return 0; +#endif + if (*d->d_name == '.') + return 0; + +#define MNT_MNTTABDIR_EXTSIZ (sizeof(MNT_MNTTABDIR_EXT) - 1) + + namesz = strlen(d->d_name); + if (!namesz || namesz < MNT_MNTTABDIR_EXTSIZ + 1 || + strcmp(d->d_name + (namesz - MNT_MNTTABDIR_EXTSIZ), + MNT_MNTTABDIR_EXT) != 0) + return 0; + + /* Accept this */ + return 1; +} + +#ifdef HAVE_SCANDIRAT +static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + int n = 0, i; + int dd; + struct dirent **namelist = NULL; + + dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dd < 0) + return -errno; + + n = scandirat(dd, ".", &namelist, mnt_table_parse_dir_filter, versionsort); + if (n <= 0) { + close(dd); + return 0; + } + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + struct stat st; + FILE *f; + + if (fstatat(dd, d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + mnt_table_parse_stream(tb, f, d->d_name); + fclose(f); + } + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + close(dd); + return 0; +} +#else +static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + int n = 0, i, r = 0; + DIR *dir = NULL; + struct dirent **namelist = NULL; + + n = scandir(dirname, &namelist, mnt_table_parse_dir_filter, versionsort); + if (n <= 0) + return 0; + + /* let's use "at" functions rather than playing crazy games with paths... */ + dir = opendir(dirname); + if (!dir) { + r = -errno; + goto out; + } + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + struct stat st; + FILE *f; + + if (fstatat(dirfd(dir), d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dirfd(dir), d->d_name, + O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + mnt_table_parse_stream(tb, f, d->d_name); + fclose(f); + } + } + +out: + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + if (dir) + closedir(dir); + return r; +} +#endif + +/** + * mnt_table_parse_dir: + * @tb: mount table + * @dirname: directory + * + * The directory: + * - files are sorted by strverscmp(3) + * - files that start with "." are ignored (e.g. ".10foo.fstab") + * - files without the ".fstab" extension are ignored + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + return __mnt_table_parse_dir(tb, dirname); +} + +struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt, int empty_for_enoent) +{ + struct libmnt_table *tb; + + if (!filename) + return NULL; + if (!mnt_is_path(filename)) + return empty_for_enoent ? mnt_new_table() : NULL; + + tb = mnt_new_table(); + if (tb) { + DBG(TAB, ul_debugobj(tb, "new tab for file: %s", filename)); + tb->fmt = fmt; + if (mnt_table_parse_file(tb, filename) != 0) { + mnt_unref_table(tb); + tb = NULL; + } + } + return tb; +} + +/** + * mnt_new_table_from_file: + * @filename: /etc/{m,fs}tab or /proc/self/mountinfo path + * + * Same as mnt_new_table() + mnt_table_parse_file(). Use this function for private + * files only. This function does not allow using the error callback, so you + * cannot provide any feedback to end-users about broken records in files (e.g. + * fstab). + * + * Returns: newly allocated tab on success and NULL in case of error. + */ +struct libmnt_table *mnt_new_table_from_file(const char *filename) +{ + if (!filename) + return NULL; + + return __mnt_new_table_from_file(filename, MNT_FMT_GUESS, 0); +} + +/** + * mnt_new_table_from_dir + * @dirname: directory with *.fstab files + * + * Returns: newly allocated tab on success and NULL in case of error. + */ +struct libmnt_table *mnt_new_table_from_dir(const char *dirname) +{ + struct libmnt_table *tb; + + if (!dirname) + return NULL; + tb = mnt_new_table(); + if (tb && mnt_table_parse_dir(tb, dirname) != 0) { + mnt_unref_table(tb); + tb = NULL; + } + return tb; +} + +/** + * mnt_table_set_parser_errcb: + * @tb: pointer to table + * @cb: pointer to callback function + * + * The error callback function is called by table parser (mnt_table_parse_file()) + * in case of a syntax error. The callback function could be used for error + * evaluation, libmount will continue/stop parsing according to callback return + * codes: + * + * <0 : fatal error (abort parsing) + * 0 : success (parsing continues) + * >0 : recoverable error (the line is ignored, parsing continues). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_set_parser_errcb(struct libmnt_table *tb, + int (*cb)(struct libmnt_table *tb, const char *filename, int line)) +{ + if (!tb) + return -EINVAL; + tb->errcb = cb; + return 0; +} + +/* + * Filter out entries during tab file parsing. If @cb returns 1, then the entry + * is ignored. + */ +int mnt_table_set_parser_fltrcb(struct libmnt_table *tb, + int (*cb)(struct libmnt_fs *, void *), + void *data) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "%s table parser filter", cb ? "set" : "unset")); + tb->fltrcb = cb; + tb->fltrcb_data = data; + return 0; +} + +/* + * mnt_table_enable_noautofs: + * @tb: table + * @ignore: ignore or don't ignore + * + * Enable/disable ignore autofs mount table entries on reading. + */ +int mnt_table_enable_noautofs(struct libmnt_table *tb, int ignore) +{ + if (!tb) + return -EINVAL; + tb->noautofs = ignore ? 1 : 0; + return 0; +} + +/* + * mnt_table_is_noautofs: + * @tb: table + * + * Return the the enabled status of ignore autofs mount table entries. + */ +int mnt_table_is_noautofs(struct libmnt_table *tb) +{ + return tb ? tb->noautofs : 0; +} + +/** + * mnt_table_parse_swaps: + * @tb: table + * @filename: overwrites default (/proc/swaps or $LIBMOUNT_SWAPS) or NULL + * + * This function parses /proc/swaps and appends new lines to the @tab. + * + * See also mnt_table_set_parser_errcb(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_swaps(struct libmnt_table *tb, const char *filename) +{ + if (!tb) + return -EINVAL; + if (!filename) { + filename = mnt_get_swaps_path(); + if (!filename) + return -EINVAL; + } + + tb->fmt = MNT_FMT_SWAPS; + + return mnt_table_parse_file(tb, filename); +} + +/** + * mnt_table_parse_fstab: + * @tb: table + * @filename: overwrites default (/etc/fstab or $LIBMOUNT_FSTAB) or NULL + * + * This function parses /etc/fstab and appends new lines to the @tab. If the + * @filename is a directory, then mnt_table_parse_dir() is called. + * + * See also mnt_table_set_parser_errcb(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_fstab(struct libmnt_table *tb, const char *filename) +{ + struct stat st; + int rc = 0; + + if (!tb) + return -EINVAL; + if (!filename) + filename = mnt_get_fstab_path(); + if (!filename) + return -EINVAL; + if (mnt_safe_stat(filename, &st) != 0) + return -errno; + + tb->fmt = MNT_FMT_FSTAB; + + if (S_ISREG(st.st_mode)) + rc = mnt_table_parse_file(tb, filename); + else if (S_ISDIR(st.st_mode)) + rc = mnt_table_parse_dir(tb, filename); + else + rc = -EINVAL; + + return rc; +} + +/* + * This function uses @uf to find a corresponding record in @tb, then the record + * from @tb is updated (user specific mount options are added). + * + * Note that @uf must contain only user specific mount options instead of + * VFS options (note that FS options are ignored). + * + * Returns modified filesystem (from @tb) or NULL. + */ +static struct libmnt_fs *mnt_table_merge_user_fs(struct libmnt_table *tb, struct libmnt_fs *uf) +{ + struct libmnt_fs *fs; + struct libmnt_iter itr; + const char *optstr, *src, *target, *root, *attrs; + int id; + + if (!tb || !uf) + return NULL; + + DBG(TAB, ul_debugobj(tb, "merging user fs")); + + src = mnt_fs_get_srcpath(uf); + target = mnt_fs_get_target(uf); + optstr = mnt_fs_get_user_options(uf); + attrs = mnt_fs_get_attributes(uf); + root = mnt_fs_get_root(uf); + id = mnt_fs_get_id(uf); + + if (!src || !target || !root || (!attrs && !optstr)) + return NULL; + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + const char *r = mnt_fs_get_root(fs); + + if (fs->flags & MNT_FS_MERGED) + continue; + + if (id > 0 && mnt_fs_get_id(fs)) { + DBG(TAB, ul_debugobj(tb, " using ID")); + if (mnt_fs_get_id(fs) == id) + break; + } else if (r && strcmp(r, root) == 0 + && mnt_fs_streq_target(fs, target) + && mnt_fs_streq_srcpath(fs, src)) + break; + } + + if (fs) { + DBG(TAB, ul_debugobj(tb, " found")); + mnt_fs_append_options(fs, optstr); + mnt_fs_append_attributes(fs, attrs); + mnt_fs_set_bindsrc(fs, mnt_fs_get_bindsrc(uf)); + fs->flags |= MNT_FS_MERGED; + + DBG(TAB, mnt_fs_print_debug(fs, stderr)); + } + return fs; +} + +/* default filename is /proc/self/mountinfo + */ +int __mnt_table_parse_mountinfo(struct libmnt_table *tb, const char *filename, + struct libmnt_table *u_tb) +{ + int rc = 0, priv_utab = 0; + int explicit_file = filename ? 1 : 0; + + assert(tb); + + if (filename) + DBG(TAB, ul_debugobj(tb, "%s requested as mount table", filename)); + + if (!filename || strcmp(filename, _PATH_PROC_MOUNTINFO) == 0) { + filename = _PATH_PROC_MOUNTINFO; + tb->fmt = MNT_FMT_MOUNTINFO; + DBG(TAB, ul_debugobj(tb, "mountinfo parse: #1 read mountinfo")); + } else + tb->fmt = MNT_FMT_GUESS; + + rc = mnt_table_parse_file(tb, filename); + if (rc) { + if (explicit_file) + return rc; + + /* hmm, old kernel? ...try /proc/mounts */ + tb->fmt = MNT_FMT_MTAB; + return mnt_table_parse_file(tb, _PATH_PROC_MOUNTS); + } + + if (!is_mountinfo(tb)) + return 0; + DBG(TAB, ul_debugobj(tb, "mountinfo parse: #2 read utab")); + + if (mnt_table_get_nents(tb) == 0) + return 0; /* empty, ignore utab */ + /* + * try to read the user specific information from /run/mount/utabs + */ + if (!u_tb) { + const char *utab = mnt_get_utab_path(); + + if (!utab || is_file_empty(utab)) + return 0; + + u_tb = mnt_new_table(); + if (!u_tb) + return -ENOMEM; + + u_tb->fmt = MNT_FMT_UTAB; + mnt_table_set_parser_fltrcb(u_tb, tb->fltrcb, tb->fltrcb_data); + + rc = mnt_table_parse_file(u_tb, utab); + priv_utab = 1; + } + + DBG(TAB, ul_debugobj(tb, "mountinfo parse: #3 merge utab")); + + if (rc == 0) { + struct libmnt_fs *u_fs; + struct libmnt_iter itr; + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + /* merge user options into mountinfo from the kernel */ + while(mnt_table_next_fs(u_tb, &itr, &u_fs) == 0) + mnt_table_merge_user_fs(tb, u_fs); + } + + + if (priv_utab) + mnt_unref_table(u_tb); + return 0; +} +/** + * mnt_table_parse_mtab: + * @tb: table + * @filename: overwrites default or NULL + * + * The default filename is /proc/self/mountinfo. If the mount table is a + * mountinfo file then /run/mount/utabs is parsed too and both files are merged + * to the one libmnt_table. + * + * The file /etc/mtab is no more used. The function uses "mtab" in the name for + * backward compatibility only. + * + * It's strongly recommended to use NULL as a @filename to keep code portable. + * + * See also mnt_table_set_parser_errcb(). + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_mtab(struct libmnt_table *tb, const char *filename) +{ + return __mnt_table_parse_mountinfo(tb, filename, NULL); +} diff --git a/libmount/src/tab_update.c b/libmount/src/tab_update.c new file mode 100644 index 0000000..f5e5d30 --- /dev/null +++ b/libmount/src/tab_update.c @@ -0,0 +1,1066 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2011-2018 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. + */ + +/** + * SECTION: update + * @title: Tables update + * @short_description: userspace mount information management + * + * The struct libmnt_update provides an abstraction to manage mount options in + * userspace independently of system configuration. The userspace mount options + * (e.g. user=) are stored in the /run/mount/utab file. + * + * It's recommended to use high-level struct libmnt_context API. + */ +#include <sys/file.h> +#include <fcntl.h> +#include <signal.h> + +#include "mountP.h" +#include "mangle.h" +#include "pathnames.h" +#include "strutils.h" + +struct libmnt_update { + char *target; + struct libmnt_fs *fs; + char *filename; + unsigned long mountflags; + int ready; + + struct libmnt_table *mountinfo; +}; + +static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags); +static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs, unsigned long mountflags); + +/** + * mnt_new_update: + * + * Returns: newly allocated update handler + */ +struct libmnt_update *mnt_new_update(void) +{ + struct libmnt_update *upd; + + upd = calloc(1, sizeof(*upd)); + if (!upd) + return NULL; + + DBG(UPDATE, ul_debugobj(upd, "allocate")); + return upd; +} + +/** + * mnt_free_update: + * @upd: update + * + * Deallocates struct libmnt_update handler. + */ +void mnt_free_update(struct libmnt_update *upd) +{ + if (!upd) + return; + + DBG(UPDATE, ul_debugobj(upd, "free")); + + mnt_unref_fs(upd->fs); + mnt_unref_table(upd->mountinfo); + free(upd->target); + free(upd->filename); + free(upd); +} + +/* + * Returns 0 on success, <0 in case of error. + */ +int mnt_update_set_filename(struct libmnt_update *upd, const char *filename) +{ + const char *path = NULL; + int rw = 0; + + if (!upd) + return -EINVAL; + + /* filename explicitly defined */ + if (filename) { + char *p = strdup(filename); + if (!p) + return -ENOMEM; + + free(upd->filename); + upd->filename = p; + } + + if (upd->filename) + return 0; + + /* detect tab filename -- /run/mount/utab + */ + path = NULL; + mnt_has_regular_utab(&path, &rw); + if (!rw) + return -EACCES; + upd->filename = strdup(path); + if (!upd->filename) + return -ENOMEM; + + return 0; +} + +/** + * mnt_update_get_filename: + * @upd: update + * + * This function returns the file name of the up-dated file. + * + * Returns: pointer to filename that will be updated or NULL in case of error. + */ +const char *mnt_update_get_filename(struct libmnt_update *upd) +{ + return upd ? upd->filename : NULL; +} + +/** + * mnt_update_is_ready: + * @upd: update handler + * + * Returns: 1 if entry described by @upd is successfully prepared and will be + * written to the utab file. + */ +int mnt_update_is_ready(struct libmnt_update *upd) +{ + return upd ? upd->ready : FALSE; +} + +/** + * mnt_update_set_fs: + * @upd: update handler + * @mountflags: MS_* flags + * @target: umount target, must be NULL for mount + * @fs: mount filesystem description, must be NULL for umount + * + * Returns: <0 in case on error, 0 on success, 1 if update is unnecessary. + */ +int mnt_update_set_fs(struct libmnt_update *upd, unsigned long mountflags, + const char *target, struct libmnt_fs *fs) +{ + int rc; + + if (!upd) + return -EINVAL; + if ((mountflags & MS_MOVE) && (!fs || !mnt_fs_get_srcpath(fs))) + return -EINVAL; + if (target && fs) + return -EINVAL; + + DBG(UPDATE, ul_debugobj(upd, + "resetting FS [target=%s, flags=0x%08lx]", + target, mountflags)); + if (fs) { + DBG(UPDATE, ul_debugobj(upd, "FS template:")); + DBG(UPDATE, mnt_fs_print_debug(fs, stderr)); + } + + mnt_unref_fs(upd->fs); + free(upd->target); + upd->ready = FALSE; + upd->fs = NULL; + upd->target = NULL; + upd->mountflags = 0; + + if (mountflags & MS_PROPAGATION) + return 1; + + upd->mountflags = mountflags; + + rc = mnt_update_set_filename(upd, NULL); + if (rc) { + DBG(UPDATE, ul_debugobj(upd, "no writable file available [rc=%d]", rc)); + return rc; /* error or no file available (rc = 1) */ + } + if (target) { + upd->target = strdup(target); + if (!upd->target) + return -ENOMEM; + + } else if (fs) { + if (!(mountflags & MS_MOVE)) { + rc = utab_new_entry(upd, fs, mountflags); + if (rc) + return rc; + } else { + upd->fs = mnt_copy_mtab_fs(fs); + if (!upd->fs) + return -ENOMEM; + } + } + + DBG(UPDATE, ul_debugobj(upd, "ready")); + upd->ready = TRUE; + return 0; +} + +/** + * mnt_update_get_fs: + * @upd: update + * + * Returns: update filesystem entry or NULL + */ +struct libmnt_fs *mnt_update_get_fs(struct libmnt_update *upd) +{ + return upd ? upd->fs : NULL; +} + +/** + * mnt_update_get_mflags: + * @upd: update + * + * Returns: mount flags as was set by mnt_update_set_fs() + */ +unsigned long mnt_update_get_mflags(struct libmnt_update *upd) +{ + return upd ? upd->mountflags : 0; +} + +/** + * mnt_update_force_rdonly: + * @upd: update + * @rdonly: is read-only? + * + * Returns: 0 on success and negative number in case of error. + */ +int mnt_update_force_rdonly(struct libmnt_update *upd, int rdonly) +{ + int rc = 0; + + if (!upd || !upd->fs) + return -EINVAL; + + if (rdonly && (upd->mountflags & MS_RDONLY)) + return 0; + if (!rdonly && !(upd->mountflags & MS_RDONLY)) + return 0; + + if (rdonly) + upd->mountflags &= ~MS_RDONLY; + else + upd->mountflags |= MS_RDONLY; + + return rc; +} + + +/* + * Allocates an utab entry (upd->fs) for mount/remount. This function should be + * called *before* mount(2) syscall. The @fs is used as a read-only template. + * + * Returns: 0 on success, negative number on error, 1 if utab's update is + * unnecessary. + */ +static int utab_new_entry(struct libmnt_update *upd, struct libmnt_fs *fs, + unsigned long mountflags) +{ + int rc = 0; + const char *o, *a; + char *u = NULL; + + assert(fs); + assert(upd); + assert(upd->fs == NULL); + assert(!(mountflags & MS_MOVE)); + + DBG(UPDATE, ul_debug("prepare utab entry")); + + o = mnt_fs_get_user_options(fs); + a = mnt_fs_get_attributes(fs); + upd->fs = NULL; + + if (o) { + /* remove non-mtab options */ + rc = mnt_optstr_get_options(o, &u, + mnt_get_builtin_optmap(MNT_USERSPACE_MAP), + MNT_NOMTAB); + if (rc) + goto err; + } + + if (!u && !a) { + DBG(UPDATE, ul_debug("utab entry unnecessary (no options)")); + return 1; + } + + /* allocate the entry */ + upd->fs = mnt_copy_fs(NULL, fs); + if (!upd->fs) { + rc = -ENOMEM; + goto err; + } + + rc = mnt_fs_set_options(upd->fs, u); + if (rc) + goto err; + rc = mnt_fs_set_attributes(upd->fs, a); + if (rc) + goto err; + + if (!(mountflags & MS_REMOUNT)) { + rc = set_fs_root(upd, fs, mountflags); + if (rc) + goto err; + } + + free(u); + DBG(UPDATE, ul_debug("utab entry OK")); + return 0; +err: + free(u); + mnt_unref_fs(upd->fs); + upd->fs = NULL; + return rc; +} + +/* + * Sets fs-root and fs-type to @upd->fs according to the @fs template and + * @mountfalgs. For MS_BIND mountflag it reads information about the source + * filesystem from /proc/self/mountinfo. + */ +static int set_fs_root(struct libmnt_update *upd, struct libmnt_fs *fs, + unsigned long mountflags) +{ + struct libmnt_fs *src_fs; + char *fsroot = NULL; + const char *src, *fstype; + int rc = 0; + + DBG(UPDATE, ul_debug("setting FS root")); + + assert(upd); + assert(upd->fs); + assert(fs); + + fstype = mnt_fs_get_fstype(fs); + + if (mountflags & MS_BIND) { + if (!upd->mountinfo) + upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); + src = mnt_fs_get_srcpath(fs); + if (src) { + rc = mnt_fs_set_bindsrc(upd->fs, src); + if (rc) + goto err; + } + + } else if (fstype && (strcmp(fstype, "btrfs") == 0 || strcmp(fstype, "auto") == 0)) { + if (!upd->mountinfo) + upd->mountinfo = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); + } + + src_fs = mnt_table_get_fs_root(upd->mountinfo, fs, + mountflags, &fsroot); + if (src_fs) { + src = mnt_fs_get_srcpath(src_fs); + rc = mnt_fs_set_source(upd->fs, src); + if (rc) + goto err; + + mnt_fs_set_fstype(upd->fs, mnt_fs_get_fstype(src_fs)); + } + + upd->fs->root = fsroot; + return 0; +err: + free(fsroot); + return rc; +} + +/* mtab and fstab update -- returns zero on success + */ +static int fprintf_mtab_fs(FILE *f, struct libmnt_fs *fs) +{ + const char *o, *src, *fstype, *comm; + char *m1, *m2, *m3, *m4; + int rc; + + assert(fs); + assert(f); + + comm = mnt_fs_get_comment(fs); + src = mnt_fs_get_source(fs); + fstype = mnt_fs_get_fstype(fs); + o = mnt_fs_get_options(fs); + + m1 = src ? mangle(src) : "none"; + m2 = mangle(mnt_fs_get_target(fs)); + m3 = fstype ? mangle(fstype) : "none"; + m4 = o ? mangle(o) : "rw"; + + if (m1 && m2 && m3 && m4) { + if (comm) + fputs(comm, f); + rc = fprintf(f, "%s %s %s %s %d %d\n", + m1, m2, m3, m4, + mnt_fs_get_freq(fs), + mnt_fs_get_passno(fs)); + if (rc > 0) + rc = 0; + } else + rc = -ENOMEM; + + if (src) + free(m1); + free(m2); + if (fstype) + free(m3); + if (o) + free(m4); + + return rc; +} + +static int fprintf_utab_fs(FILE *f, struct libmnt_fs *fs) +{ + char *p; + int rc = 0; + + if (!fs || !f) + return -EINVAL; + + if (mnt_fs_get_id(fs) > 0) { + rc = fprintf(f, "ID=%d ", mnt_fs_get_id(fs)); + } + if (rc >= 0) { + p = mangle(mnt_fs_get_source(fs)); + if (p) { + rc = fprintf(f, "SRC=%s ", p); + free(p); + } + } + if (rc >= 0) { + p = mangle(mnt_fs_get_target(fs)); + if (p) { + rc = fprintf(f, "TARGET=%s ", p); + free(p); + } + } + if (rc >= 0) { + p = mangle(mnt_fs_get_root(fs)); + if (p) { + rc = fprintf(f, "ROOT=%s ", p); + free(p); + } + } + if (rc >= 0) { + p = mangle(mnt_fs_get_bindsrc(fs)); + if (p) { + rc = fprintf(f, "BINDSRC=%s ", p); + free(p); + } + } + if (rc >= 0) { + p = mangle(mnt_fs_get_attributes(fs)); + if (p) { + rc = fprintf(f, "ATTRS=%s ", p); + free(p); + } + } + if (rc >= 0) { + p = mangle(mnt_fs_get_user_options(fs)); + if (p) { + rc = fprintf(f, "OPTS=%s", p); + free(p); + } + } + if (rc >= 0) + rc = fprintf(f, "\n"); + + if (rc > 0) + rc = 0; /* success */ + return rc; +} + +static int update_table(struct libmnt_update *upd, struct libmnt_table *tb) +{ + FILE *f; + int rc, fd; + char *uq = NULL; + + if (!tb || !upd->filename) + return -EINVAL; + + DBG(UPDATE, ul_debugobj(upd, "%s: updating", upd->filename)); + + fd = mnt_open_uniq_filename(upd->filename, &uq); + if (fd < 0) + return fd; /* error */ + + f = fdopen(fd, "w" UL_CLOEXECSTR); + if (f) { + struct stat st; + struct libmnt_iter itr; + struct libmnt_fs *fs; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + if (tb->comms && mnt_table_get_intro_comment(tb)) + fputs(mnt_table_get_intro_comment(tb), f); + + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + rc = fprintf_utab_fs(f, fs); + if (rc) { + DBG(UPDATE, ul_debugobj(upd, + "%s: write entry failed: %m", uq)); + goto leave; + } + } + if (tb->comms && mnt_table_get_trailing_comment(tb)) + fputs(mnt_table_get_trailing_comment(tb), f); + + if (fflush(f) != 0) { + rc = -errno; + DBG(UPDATE, ul_debugobj(upd, "%s: fflush failed: %m", uq)); + goto leave; + } + + rc = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) ? -errno : 0; + + if (!rc && stat(upd->filename, &st) == 0) + /* Copy uid/gid from the present file before renaming. */ + rc = fchown(fd, st.st_uid, st.st_gid) ? -errno : 0; + + fclose(f); + f = NULL; + + if (!rc) + rc = rename(uq, upd->filename) ? -errno : 0; + } else { + rc = -errno; + close(fd); + } + +leave: + if (f) + fclose(f); + + unlink(uq); /* be paranoid */ + free(uq); + DBG(UPDATE, ul_debugobj(upd, "%s: done [rc=%d]", upd->filename, rc)); + return rc; +} + +/** + * mnt_table_write_file + * @tb: parsed file (e.g. fstab) + * @file: target + * + * This function writes @tb to @file. + * + * Returns: 0 on success, negative number on error. + */ +int mnt_table_write_file(struct libmnt_table *tb, FILE *file) +{ + int rc = 0; + struct libmnt_iter itr; + struct libmnt_fs *fs; + + if (tb->comms && mnt_table_get_intro_comment(tb)) + fputs(mnt_table_get_intro_comment(tb), file); + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + while(mnt_table_next_fs(tb, &itr, &fs) == 0) { + rc = fprintf_mtab_fs(file, fs); + if (rc) + return rc; + } + if (tb->comms && mnt_table_get_trailing_comment(tb)) + fputs(mnt_table_get_trailing_comment(tb), file); + + if (fflush(file) != 0) + rc = -errno; + + DBG(TAB, ul_debugobj(tb, "write file done [rc=%d]", rc)); + return rc; +} + +/** + * mnt_table_replace_file + * @tb: parsed file (e.g. fstab) + * @filename: target + * + * This function replaces @file by the new content from @tb. + * + * Returns: 0 on success, negative number on error. + */ +int mnt_table_replace_file(struct libmnt_table *tb, const char *filename) +{ + int fd, rc = 0; + FILE *f; + char *uq = NULL; + + DBG(TAB, ul_debugobj(tb, "%s: replacing", filename)); + + fd = mnt_open_uniq_filename(filename, &uq); + if (fd < 0) + return fd; /* error */ + + f = fdopen(fd, "w" UL_CLOEXECSTR); + if (f) { + struct stat st; + + mnt_table_write_file(tb, f); + + if (fflush(f) != 0) { + rc = -errno; + DBG(UPDATE, ul_debug("%s: fflush failed: %m", uq)); + goto leave; + } + + rc = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) ? -errno : 0; + + if (!rc && stat(filename, &st) == 0) + /* Copy uid/gid from the present file before renaming. */ + rc = fchown(fd, st.st_uid, st.st_gid) ? -errno : 0; + + fclose(f); + f = NULL; + + if (!rc) + rc = rename(uq, filename) ? -errno : 0; + } else { + rc = -errno; + close(fd); + } + +leave: + if (f) + fclose(f); + unlink(uq); + free(uq); + + DBG(TAB, ul_debugobj(tb, "replace done [rc=%d]", rc)); + return rc; +} + +static int add_file_entry(struct libmnt_table *tb, struct libmnt_update *upd) +{ + struct libmnt_fs *fs; + + assert(upd); + + fs = mnt_copy_fs(NULL, upd->fs); + if (!fs) + return -ENOMEM; + + mnt_table_add_fs(tb, fs); + mnt_unref_fs(fs); + + return update_table(upd, tb); +} + +static int update_add_entry(struct libmnt_update *upd, struct libmnt_lock *lc) +{ + struct libmnt_table *tb; + int rc = 0; + + assert(upd); + assert(upd->fs); + + DBG(UPDATE, ul_debugobj(upd, "%s: add entry", upd->filename)); + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return -MNT_ERR_LOCK; + + tb = __mnt_new_table_from_file(upd->filename, MNT_FMT_UTAB, 1); + if (tb) + rc = add_file_entry(tb, upd); + if (lc) + mnt_unlock_file(lc); + + mnt_unref_table(tb); + return rc; +} + +static int update_remove_entry(struct libmnt_update *upd, struct libmnt_lock *lc) +{ + struct libmnt_table *tb; + int rc = 0; + + assert(upd); + assert(upd->target); + + DBG(UPDATE, ul_debugobj(upd, "%s: remove entry", upd->filename)); + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return -MNT_ERR_LOCK; + + tb = __mnt_new_table_from_file(upd->filename, MNT_FMT_UTAB, 1); + if (tb) { + struct libmnt_fs *rem = mnt_table_find_target(tb, upd->target, MNT_ITER_BACKWARD); + if (rem) { + mnt_table_remove_fs(tb, rem); + rc = update_table(upd, tb); + } + } + if (lc) + mnt_unlock_file(lc); + + mnt_unref_table(tb); + return rc; +} + +static int update_modify_target(struct libmnt_update *upd, struct libmnt_lock *lc) +{ + struct libmnt_table *tb = NULL; + int rc = 0; + + assert(upd); + DBG(UPDATE, ul_debugobj(upd, "%s: modify target", upd->filename)); + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return -MNT_ERR_LOCK; + + tb = __mnt_new_table_from_file(upd->filename, MNT_FMT_UTAB, 1); + if (tb) { + const char *upd_source = mnt_fs_get_srcpath(upd->fs); + const char *upd_target = mnt_fs_get_target(upd->fs); + struct libmnt_iter itr; + struct libmnt_fs *fs; + char *cn_target = mnt_resolve_path(upd_target, NULL); + + if (!cn_target) { + rc = -ENOMEM; + goto done; + } + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + char *p; + const char *e; + + e = startswith(mnt_fs_get_target(fs), upd_source); + if (!e || (*e && *e != '/')) + continue; + if (*e == '/') + e++; /* remove extra '/' */ + + /* no subdirectory, replace entire path */ + if (!*e) + rc = mnt_fs_set_target(fs, cn_target); + + /* update start of the path, keep subdirectory */ + else if (asprintf(&p, "%s/%s", cn_target, e) > 0) { + rc = mnt_fs_set_target(fs, p); + free(p); + } else + rc = -ENOMEM; + + if (rc < 0) + break; + } + + if (!rc) + rc = update_table(upd, tb); + free(cn_target); + } + +done: + if (lc) + mnt_unlock_file(lc); + + mnt_unref_table(tb); + return rc; +} + +static int update_modify_options(struct libmnt_update *upd, struct libmnt_lock *lc) +{ + struct libmnt_table *tb = NULL; + int rc = 0; + struct libmnt_fs *fs; + + assert(upd); + assert(upd->fs); + + DBG(UPDATE, ul_debugobj(upd, "%s: modify options", upd->filename)); + + fs = upd->fs; + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return -MNT_ERR_LOCK; + + tb = __mnt_new_table_from_file(upd->filename, MNT_FMT_UTAB, 1); + if (tb) { + struct libmnt_fs *cur = mnt_table_find_target(tb, + mnt_fs_get_target(fs), + MNT_ITER_BACKWARD); + if (cur) { + rc = mnt_fs_set_attributes(cur, mnt_fs_get_attributes(fs)); + if (!rc) + rc = mnt_fs_set_options(cur, mnt_fs_get_options(fs)); + if (!rc) + rc = update_table(upd, tb); + } else + rc = add_file_entry(tb, upd); /* not found, add new */ + } + + if (lc) + mnt_unlock_file(lc); + + mnt_unref_table(tb); + return rc; +} + +/** + * mnt_update_table: + * @upd: update + * @lc: lock or NULL + * + * High-level API to update /etc/mtab (or private /run/mount/utab file). + * + * The @lc lock is optional and will be created if necessary. Note that + * an automatically created lock blocks all signals. + * + * See also mnt_lock_block_signals() and mnt_context_get_lock(). + * + * Returns: 0 on success, negative number on error. + */ +int mnt_update_table(struct libmnt_update *upd, struct libmnt_lock *lc) +{ + struct libmnt_lock *lc0 = lc; + int rc = -EINVAL; + + if (!upd || !upd->filename) + return -EINVAL; + if (!upd->ready) + return 0; + + DBG(UPDATE, ul_debugobj(upd, "%s: update tab", upd->filename)); + if (upd->fs) { + DBG(UPDATE, mnt_fs_print_debug(upd->fs, stderr)); + } + if (!lc) { + lc = mnt_new_lock(upd->filename, 0); + if (lc) + mnt_lock_block_signals(lc, TRUE); + } + + if (!upd->fs && upd->target) + rc = update_remove_entry(upd, lc); /* umount */ + else if (upd->mountflags & MS_MOVE) + rc = update_modify_target(upd, lc); /* move */ + else if (upd->mountflags & MS_REMOUNT) + rc = update_modify_options(upd, lc); /* remount */ + else if (upd->fs) + rc = update_add_entry(upd, lc); /* mount */ + + upd->ready = FALSE; + DBG(UPDATE, ul_debugobj(upd, "%s: update tab: done [rc=%d]", + upd->filename, rc)); + if (lc != lc0) + mnt_free_lock(lc); + return rc; +} + +int mnt_update_already_done(struct libmnt_update *upd, struct libmnt_lock *lc) +{ + struct libmnt_table *tb = NULL; + struct libmnt_lock *lc0 = lc; + int rc = 0; + + if (!upd || !upd->filename || (!upd->fs && !upd->target)) + return -EINVAL; + + DBG(UPDATE, ul_debugobj(upd, "%s: checking for previous update", upd->filename)); + + if (!lc) { + lc = mnt_new_lock(upd->filename, 0); + if (lc) + mnt_lock_block_signals(lc, TRUE); + } + if (lc) { + rc = mnt_lock_file(lc); + if (rc) { + rc = -MNT_ERR_LOCK; + goto done; + } + } + + tb = __mnt_new_table_from_file(upd->filename, MNT_FMT_UTAB, 1); + if (lc) + mnt_unlock_file(lc); + if (!tb) + goto done; + + if (upd->fs) { + /* mount */ + const char *tgt = mnt_fs_get_target(upd->fs); + const char *src = mnt_fs_get_bindsrc(upd->fs) ? + mnt_fs_get_bindsrc(upd->fs) : + mnt_fs_get_source(upd->fs); + + if (mnt_table_find_pair(tb, src, tgt, MNT_ITER_BACKWARD)) { + DBG(UPDATE, ul_debugobj(upd, "%s: found %s %s", + upd->filename, src, tgt)); + rc = 1; + } + } else if (upd->target) { + /* umount */ + if (!mnt_table_find_target(tb, upd->target, MNT_ITER_BACKWARD)) { + DBG(UPDATE, ul_debugobj(upd, "%s: not-found (umounted) %s", + upd->filename, upd->target)); + rc = 1; + } + } + + mnt_unref_table(tb); +done: + if (lc && lc != lc0) + mnt_free_lock(lc); + DBG(UPDATE, ul_debugobj(upd, "%s: previous update check done [rc=%d]", + upd->filename, rc)); + return rc; +} + + +#ifdef TEST_PROGRAM + +static int update(const char *target, struct libmnt_fs *fs, unsigned long mountflags) +{ + int rc; + struct libmnt_update *upd; + + DBG(UPDATE, ul_debug("update test")); + + upd = mnt_new_update(); + if (!upd) + return -ENOMEM; + + rc = mnt_update_set_fs(upd, mountflags, target, fs); + if (rc == 1) { + /* update is unnecessary */ + rc = 0; + goto done; + } + if (rc) { + fprintf(stderr, "failed to set FS\n"); + goto done; + } + + /* [... mount(2) call should be here...] */ + + rc = mnt_update_table(upd, NULL); +done: + mnt_free_update(upd); + return rc; +} + +static int test_add(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_fs *fs = mnt_new_fs(); + int rc; + + if (argc < 5 || !fs) + return -1; + mnt_fs_set_source(fs, argv[1]); + mnt_fs_set_target(fs, argv[2]); + mnt_fs_set_fstype(fs, argv[3]); + mnt_fs_set_options(fs, argv[4]); + + rc = update(NULL, fs, 0); + mnt_unref_fs(fs); + return rc; +} + +static int test_remove(struct libmnt_test *ts, int argc, char *argv[]) +{ + if (argc < 2) + return -1; + return update(argv[1], NULL, 0); +} + +static int test_move(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_fs *fs = mnt_new_fs(); + int rc; + + if (argc < 3) + return -1; + mnt_fs_set_source(fs, argv[1]); + mnt_fs_set_target(fs, argv[2]); + + rc = update(NULL, fs, MS_MOVE); + + mnt_unref_fs(fs); + return rc; +} + +static int test_remount(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_fs *fs = mnt_new_fs(); + int rc; + + if (argc < 3) + return -1; + mnt_fs_set_target(fs, argv[1]); + mnt_fs_set_options(fs, argv[2]); + + rc = update(NULL, fs, MS_REMOUNT); + mnt_unref_fs(fs); + return rc; +} + +static int test_replace(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_fs *fs = mnt_new_fs(); + struct libmnt_table *tb = mnt_new_table(); + int rc; + + if (argc < 3) + return -1; + + mnt_table_enable_comments(tb, TRUE); + mnt_table_parse_fstab(tb, NULL); + + mnt_fs_set_source(fs, argv[1]); + mnt_fs_set_target(fs, argv[2]); + mnt_fs_append_comment(fs, "# this is new filesystem\n"); + + mnt_table_add_fs(tb, fs); + mnt_unref_fs(fs); + + rc = mnt_table_replace_file(tb, mnt_get_fstab_path()); + mnt_unref_table(tb); + return rc; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--add", test_add, "<src> <target> <type> <options> add a line to mtab" }, + { "--remove", test_remove, "<target> MS_REMOUNT mtab change" }, + { "--move", test_move, "<old_target> <target> MS_MOVE mtab change" }, + { "--remount",test_remount, "<target> <options> MS_REMOUNT mtab change" }, + { "--replace",test_replace, "<src> <target> Add a line to LIBMOUNT_FSTAB and replace the original file" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/test.c b/libmount/src/test.c new file mode 100644 index 0000000..0e3388c --- /dev/null +++ b/libmount/src/test.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2010-2018 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. + * + * + * Routines for TEST_PROGRAMs + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#ifndef TEST_PROGRAM +#define TEST_PROGRAM +#endif + +#include "mountP.h" + +int mnt_run_test(struct libmnt_test *tests, int argc, char *argv[]) +{ + int rc = -1; + struct libmnt_test *ts; + + assert(tests); + assert(argc); + assert(argv); + + if (argc < 2 || + strcmp(argv[1], "--help") == 0 || + strcmp(argv[1], "-h") == 0) + goto usage; + + mnt_init_debug(0); + + for (ts = tests; ts->name; ts++) { + if (strcmp(ts->name, argv[1]) == 0) { + rc = ts->body(ts, argc - 1, argv + 1); + if (rc) + printf("FAILED [rc=%d]", rc); + break; + } + } + + if (rc < 0 && ts->name == NULL) + goto usage; + + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +usage: + printf("\nUsage:\n\t%s <test> [testoptions]\nTests:\n", + program_invocation_short_name); + for (ts = tests; ts->name; ts++) { + printf("\t%-15s", ts->name); + if (ts->usage) + printf(" %s\n", ts->usage); + } + printf("\n"); + return EXIT_FAILURE; +} diff --git a/libmount/src/utils.c b/libmount/src/utils.c new file mode 100644 index 0000000..3817b39 --- /dev/null +++ b/libmount/src/utils.c @@ -0,0 +1,1542 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 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. + */ + +/** + * SECTION: utils + * @title: Utils + * @short_description: misc utils. + */ +#include <ctype.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <blkid.h> + +#include "strutils.h" +#include "pathnames.h" +#include "mountP.h" +#include "mangle.h" +#include "canonicalize.h" +#include "env.h" +#include "match.h" +#include "fileutils.h" +#include "statfs_magic.h" +#include "sysfs.h" +#include "namespace.h" + +/* + * Return 1 if the file is not accessible or empty + */ +int is_file_empty(const char *name) +{ + struct stat st; + assert(name); + + return (stat(name, &st) != 0 || st.st_size == 0); +} + +int mnt_valid_tagname(const char *tagname) +{ + if (tagname && *tagname && ( + strcmp("ID", tagname) == 0 || + strcmp("UUID", tagname) == 0 || + strcmp("LABEL", tagname) == 0 || + strcmp("PARTUUID", tagname) == 0 || + strcmp("PARTLABEL", tagname) == 0)) + return 1; + + return 0; +} + +/** + * mnt_tag_is_valid: + * @tag: NAME=value string + * + * Returns: 1 if the @tag is parsable and tag NAME= is supported by libmount, or 0. + */ +int mnt_tag_is_valid(const char *tag) +{ + char *t = NULL; + int rc = tag && blkid_parse_tag_string(tag, &t, NULL) == 0 + && mnt_valid_tagname(t); + + free(t); + return rc; +} + +int mnt_parse_offset(const char *str, size_t len, uintmax_t *res) +{ + char *p; + int rc = 0; + + if (!str || !*str) + return -EINVAL; + + p = strndup(str, len); + if (!p) + return -errno; + + if (strtosize(p, res)) + rc = -EINVAL; + free(p); + return rc; +} + +/* used as a callback by bsearch in mnt_fstype_is_pseudofs() */ +static int fstype_cmp(const void *v1, const void *v2) +{ + const char *s1 = *(char * const *)v1; + const char *s2 = *(char * const *)v2; + + return strcmp(s1, s2); +} + +/* This very simplified stat() alternative uses cached VFS data and does not + * directly ask the filesystem for details. It requires a kernel that supports + * statx(). It's usable only for file type, rdev and ino! + */ +static int safe_stat(const char *target, struct stat *st, int nofollow) +{ + assert(target); + assert(st); + + memset(st, 0, sizeof(struct stat)); + +#if defined(HAVE_STATX) && defined(HAVE_STRUCT_STATX) && defined(AT_STATX_DONT_SYNC) + { + int rc; + struct statx stx = { 0 }; + + rc = statx(AT_FDCWD, target, + /* flags */ + AT_STATX_DONT_SYNC + | AT_NO_AUTOMOUNT + | (nofollow ? AT_SYMLINK_NOFOLLOW : 0), + /* mask */ + STATX_TYPE + | STATX_MODE + | STATX_INO, + &stx); + if (rc == 0) { + st->st_ino = stx.stx_ino; + st->st_dev = makedev(stx.stx_dev_major, stx.stx_dev_minor); + st->st_rdev = makedev(stx.stx_rdev_major, stx.stx_rdev_minor); + st->st_mode = stx.stx_mode; + } + + if (rc == 0 || + (errno != EOPNOTSUPP && errno != ENOSYS && errno != EINVAL)) + return rc; + } +#endif + +#ifdef AT_NO_AUTOMOUNT + return fstatat(AT_FDCWD, target, st, + AT_NO_AUTOMOUNT | (nofollow ? AT_SYMLINK_NOFOLLOW : 0)); +#endif + return nofollow ? lstat(target, st) : stat(target, st); +} + +int mnt_safe_stat(const char *target, struct stat *st) +{ + return safe_stat(target, st, 0); +} + +int mnt_safe_lstat(const char *target, struct stat *st) +{ + return safe_stat(target, st, 1); +} + +/* Don't use access() or stat() here, we need a way how to check the path + * without trigger an automount or hangs on NFS, etc. */ +int mnt_is_path(const char *target) +{ + struct stat st; + + return safe_stat(target, &st, 0) == 0; +} + +/* + * Note that the @target has to be an absolute path (so at least "/"). The + * @filename returns an allocated buffer with the last path component, for example: + * + * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test" + */ +int mnt_chdir_to_parent(const char *target, char **filename) +{ + char *buf, *parent, *last = NULL; + char cwd[PATH_MAX]; + int rc = -EINVAL; + + if (!target || *target != '/') + return -EINVAL; + + DBG(UTILS, ul_debug("moving to %s parent", target)); + + buf = strdup(target); + if (!buf) + return -ENOMEM; + + if (*(buf + 1) != '\0') { + last = stripoff_last_component(buf); + if (!last) + goto err; + } + + parent = buf && *buf ? buf : "/"; + + if (chdir(parent) == -1) { + DBG(UTILS, ul_debug("failed to chdir to %s: %m", parent)); + rc = -errno; + goto err; + } + if (!getcwd(cwd, sizeof(cwd))) { + DBG(UTILS, ul_debug("failed to obtain current directory: %m")); + rc = -errno; + goto err; + } + if (strcmp(cwd, parent) != 0) { + DBG(UTILS, ul_debug( + "unexpected chdir (expected=%s, cwd=%s)", parent, cwd)); + goto err; + } + + DBG(CXT, ul_debug( + "current directory moved to %s [last_component='%s']", + parent, last)); + + if (filename) { + *filename = buf; + + if (!last || !*last) + memcpy(*filename, ".", 2); + else + memmove(*filename, last, strlen(last) + 1); + } else + free(buf); + return 0; +err: + free(buf); + return rc; +} + +/* + * Check if @path is on a read-only filesystem independently of file permissions. + */ +int mnt_is_readonly(const char *path) +{ + if (access(path, W_OK) == 0) + return 0; + if (errno == EROFS) + return 1; + if (errno != EACCES) + return 0; + +#ifdef HAVE_UTIMENSAT + /* + * access(2) returns EACCES on read-only FS: + * + * - for set-uid application if one component of the path is not + * accessible for the current rUID. (Note that euidaccess(2) does not + * check for EROFS at all). + * + * - for a read-write filesystem with a read-only VFS node (aka -o remount,ro,bind) + */ + { + struct timespec times[2]; + + DBG(UTILS, ul_debug(" doing utimensat() based write test")); + + times[0].tv_nsec = UTIME_NOW; /* atime */ + times[1].tv_nsec = UTIME_OMIT; /* mtime */ + + if (utimensat(AT_FDCWD, path, times, 0) == -1) + return errno == EROFS; + } +#endif + return 0; +} + +/** + * mnt_mangle: + * @str: string + * + * Encode @str to be compatible with fstab/mtab + * + * Returns: newly allocated string or NULL in case of error. + */ +char *mnt_mangle(const char *str) +{ + return mangle(str); +} + +/** + * mnt_unmangle: + * @str: string + * + * Decode @str from fstab/mtab + * + * Returns: newly allocated string or NULL in case of error. + */ +char *mnt_unmangle(const char *str) +{ + return unmangle(str, NULL); +} + +/** + * mnt_fstype_is_pseudofs: + * @type: filesystem name + * + * Returns: 1 for filesystems like proc, sysfs, ... or 0. + */ +int mnt_fstype_is_pseudofs(const char *type) +{ + /* This array must remain sorted when adding new fstypes */ + static const char *pseudofs[] = { + "anon_inodefs", + "apparmorfs", + "autofs", + "bdev", + "binder", + "binfmt_misc", + "bpf", + "cgroup", + "cgroup2", + "configfs", + "cpuset", + "debugfs", + "devfs", + "devpts", + "devtmpfs", + "dlmfs", + "dmabuf", + "drm", + "efivarfs", + "fuse", /* Fallback name of fuse used by many poorly written drivers. */ + "fuse.archivemount", /* Not a true pseudofs (has source), but source is not reported. */ + "fuse.avfsd", /* Not a true pseudofs (has source), but source is not reported. */ + "fuse.dumpfs", /* In fact, it is a netfs, but source is not reported. */ + "fuse.encfs", /* Not a true pseudofs (has source), but source is not reported. */ + "fuse.gvfs-fuse-daemon", /* Old name, not used by gvfs any more. */ + "fuse.gvfsd-fuse", + "fuse.lxcfs", + "fuse.rofiles-fuse", + "fuse.vmware-vmblock", + "fuse.xwmfs", + "fusectl", + "hugetlbfs", + "ipathfs", + "mqueue", + "nfsd", + "none", + "nsfs", + "overlay", + "pipefs", + "proc", + "pstore", + "ramfs", + "resctrl", + "rootfs", + "rpc_pipefs", + "securityfs", + "selinuxfs", + "smackfs", + "sockfs", + "spufs", + "sysfs", + "tmpfs", + "tracefs", + "vboxsf", + "virtiofs" + }; + + assert(type); + + return !(bsearch(&type, pseudofs, ARRAY_SIZE(pseudofs), + sizeof(char*), fstype_cmp) == NULL); +} + +/** + * mnt_fstype_is_netfs: + * @type: filesystem name + * + * Returns: 1 for filesystems like cifs, nfs, ... or 0. + */ +int mnt_fstype_is_netfs(const char *type) +{ + if (strcmp(type, "cifs") == 0 || + strcmp(type, "smb3") == 0 || + strcmp(type, "smbfs") == 0 || + strncmp(type,"nfs", 3) == 0 || + strcmp(type, "afs") == 0 || + strcmp(type, "ncpfs") == 0 || + strcmp(type, "glusterfs") == 0 || + strcmp(type, "fuse.curlftpfs") == 0 || + strcmp(type, "fuse.sshfs") == 0 || + strncmp(type,"9p", 2) == 0) + return 1; + return 0; +} + +const char *mnt_statfs_get_fstype(struct statfs *vfs) +{ + assert(vfs); + + switch (vfs->f_type) { + case STATFS_ADFS_MAGIC: return "adfs"; + case STATFS_AFFS_MAGIC: return "affs"; + case STATFS_AFS_MAGIC: return "afs"; + case STATFS_AUTOFS_MAGIC: return "autofs"; + case STATFS_BDEVFS_MAGIC: return "bdev"; + case STATFS_BEFS_MAGIC: return "befs"; + case STATFS_BFS_MAGIC: return "befs"; + case STATFS_BINFMTFS_MAGIC: return "binfmt_misc"; + case STATFS_BTRFS_MAGIC: return "btrfs"; + case STATFS_CEPH_MAGIC: return "ceph"; + case STATFS_CGROUP_MAGIC: return "cgroup"; + case STATFS_CIFS_MAGIC: return "cifs"; + case STATFS_CODA_MAGIC: return "coda"; + case STATFS_CONFIGFS_MAGIC: return "configfs"; + case STATFS_CRAMFS_MAGIC: return "cramfs"; + case STATFS_DEBUGFS_MAGIC: return "debugfs"; + case STATFS_DEVPTS_MAGIC: return "devpts"; + case STATFS_ECRYPTFS_MAGIC: return "ecryptfs"; + case STATFS_EFIVARFS_MAGIC: return "efivarfs"; + case STATFS_EFS_MAGIC: return "efs"; + case STATFS_EXOFS_MAGIC: return "exofs"; + case STATFS_EXT4_MAGIC: return "ext4"; /* all extN use the same magic */ + case STATFS_F2FS_MAGIC: return "f2fs"; + case STATFS_FUSE_MAGIC: return "fuse"; + case STATFS_FUTEXFS_MAGIC: return "futexfs"; + case STATFS_GFS2_MAGIC: return "gfs2"; + case STATFS_HFSPLUS_MAGIC: return "hfsplus"; + case STATFS_HOSTFS_MAGIC: return "hostfs"; + case STATFS_HPFS_MAGIC: return "hpfs"; + case STATFS_HPPFS_MAGIC: return "hppfs"; + case STATFS_HUGETLBFS_MAGIC: return "hugetlbfs"; + case STATFS_ISOFS_MAGIC: return "iso9660"; + case STATFS_JFFS2_MAGIC: return "jffs2"; + case STATFS_JFS_MAGIC: return "jfs"; + case STATFS_LOGFS_MAGIC: return "logfs"; + case STATFS_MINIX2_MAGIC: + case STATFS_MINIX2_MAGIC2: + case STATFS_MINIX3_MAGIC: + case STATFS_MINIX_MAGIC: + case STATFS_MINIX_MAGIC2: return "minix"; + case STATFS_MQUEUE_MAGIC: return "mqueue"; + case STATFS_MSDOS_MAGIC: return "vfat"; + case STATFS_NCP_MAGIC: return "ncp"; + case STATFS_NFS_MAGIC: return "nfs"; + case STATFS_NILFS_MAGIC: return "nilfs2"; + case STATFS_NTFS_MAGIC: return "ntfs"; + case STATFS_OCFS2_MAGIC: return "ocfs2"; + case STATFS_OMFS_MAGIC: return "omfs"; + case STATFS_OPENPROMFS_MAGIC: return "openpromfs"; + case STATFS_PIPEFS_MAGIC: return "pipefs"; + case STATFS_PROC_MAGIC: return "proc"; + case STATFS_PSTOREFS_MAGIC: return "pstore"; + case STATFS_QNX4_MAGIC: return "qnx4"; + case STATFS_QNX6_MAGIC: return "qnx6"; + case STATFS_RAMFS_MAGIC: return "ramfs"; + case STATFS_REISERFS_MAGIC: return "reiser4"; + case STATFS_ROMFS_MAGIC: return "romfs"; + case STATFS_SECURITYFS_MAGIC: return "securityfs"; + case STATFS_SELINUXFS_MAGIC: return "selinuxfs"; + case STATFS_SMACKFS_MAGIC: return "smackfs"; + case STATFS_SMB_MAGIC: return "smb"; + case STATFS_SOCKFS_MAGIC: return "sockfs"; + case STATFS_SQUASHFS_MAGIC: return "squashfs"; + case STATFS_SYSFS_MAGIC: return "sysfs"; + case STATFS_TMPFS_MAGIC: return "tmpfs"; + case STATFS_UBIFS_MAGIC: return "ubifs"; + case STATFS_UDF_MAGIC: return "udf"; + case STATFS_UFS2_MAGIC: + case STATFS_UFS_MAGIC: return "ufs"; + case STATFS_V9FS_MAGIC: return "9p"; + case STATFS_VXFS_MAGIC: return "vxfs"; + case STATFS_XENFS_MAGIC: return "xenfs"; + case STATFS_XFS_MAGIC: return "xfs"; + default: + break; + } + + return NULL; +} + +/** + * mnt_match_fstype: + * @type: filesystem type + * @pattern: filesystem name or comma delimited list of names + * + * The @pattern list of filesystems can be prefixed with a global + * "no" prefix to invert matching of the whole list. The "no" could + * also be used for individual items in the @pattern list. So, + * "nofoo,bar" has the same meaning as "nofoo,nobar". + * + * "bar" : "nofoo,bar" -> False (global "no" prefix) + * + * "bar" : "foo,bar" -> True + * + * "bar" : "foo,nobar" -> False + * + * Returns: 1 if type is matching, else 0. This function also returns + * 0 if @pattern is NULL and @type is non-NULL. + */ +int mnt_match_fstype(const char *type, const char *pattern) +{ + return match_fstype(type, pattern); +} + +void mnt_free_filesystems(char **filesystems) +{ + char **p; + + if (!filesystems) + return; + for (p = filesystems; *p; p++) + free(*p); + free(filesystems); +} + +static int add_filesystem(char ***filesystems, char *name) +{ + int n = 0; + + assert(filesystems); + assert(name); + + if (*filesystems) { + char **p; + for (n = 0, p = *filesystems; *p; p++, n++) { + if (strcmp(*p, name) == 0) + return 0; + } + } + + #define MYCHUNK 16 + + if (n == 0 || !((n + 1) % MYCHUNK)) { + size_t items = ((n + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK; + char **x = realloc(*filesystems, items * sizeof(char *)); + + if (!x) + goto err; + *filesystems = x; + } + name = strdup(name); + (*filesystems)[n] = name; + (*filesystems)[n + 1] = NULL; + if (!name) + goto err; + return 0; +err: + mnt_free_filesystems(*filesystems); + return -ENOMEM; +} + +static int get_filesystems(const char *filename, char ***filesystems, const char *pattern) +{ + int rc = 0; + FILE *f; + char line[129]; + + f = fopen(filename, "r" UL_CLOEXECSTR); + if (!f) + return 1; + + DBG(UTILS, ul_debug("reading filesystems list from: %s", filename)); + + while (fgets(line, sizeof(line), f)) { + char name[sizeof(line)]; + + if (*line == '#' || strncmp(line, "nodev", 5) == 0) + continue; + if (sscanf(line, " %128[^\n ]\n", name) != 1) + continue; + if (strcmp(name, "*") == 0) { + rc = 1; + break; /* end of the /etc/filesystems */ + } + if (pattern && !mnt_match_fstype(name, pattern)) + continue; + rc = add_filesystem(filesystems, name); + if (rc) + break; + } + + fclose(f); + return rc; +} + +/* + * Always check the @filesystems pointer! + * + * man mount: + * + * ...mount will try to read the file /etc/filesystems, or, if that does not + * exist, /proc/filesystems. All of the filesystem types listed there will + * be tried, except for those that are labeled "nodev" (e.g., devpts, + * proc and nfs). If /etc/filesystems ends in a line with a single * only, + * mount will read /proc/filesystems afterwards. + */ +int mnt_get_filesystems(char ***filesystems, const char *pattern) +{ + int rc; + + if (!filesystems) + return -EINVAL; + + *filesystems = NULL; + + rc = get_filesystems(_PATH_FILESYSTEMS, filesystems, pattern); + if (rc != 1) + return rc; + + rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern); + if (rc == 1 && *filesystems) + rc = 0; /* /proc/filesystems not found */ + + return rc; +} + +/* + * Returns an allocated string with username or NULL. + */ +char *mnt_get_username(const uid_t uid) +{ + struct passwd pwd; + struct passwd *res; + char *buf, *username = NULL; + + buf = malloc(UL_GETPW_BUFSIZ); + if (!buf) + return NULL; + + if (!getpwuid_r(uid, &pwd, buf, UL_GETPW_BUFSIZ, &res) && res) + username = strdup(pwd.pw_name); + + free(buf); + return username; +} + +int mnt_get_uid(const char *username, uid_t *uid) +{ + int rc = -1; + struct passwd pwd; + struct passwd *pw; + char *buf; + + if (!username || !uid) + return -EINVAL; + + buf = malloc(UL_GETPW_BUFSIZ); + if (!buf) + return -ENOMEM; + + if (!getpwnam_r(username, &pwd, buf, UL_GETPW_BUFSIZ, &pw) && pw) { + *uid = pw->pw_uid; + rc = 0; + } else { + DBG(UTILS, ul_debug( + "cannot convert '%s' username to UID", username)); + if (errno == 0) + errno = EINVAL; + rc = -errno;; + } + + free(buf); + return rc; +} + +int mnt_get_gid(const char *groupname, gid_t *gid) +{ + int rc = -1; + struct group grp; + struct group *gr; + char *buf; + + if (!groupname || !gid) + return -EINVAL; + + buf = malloc(UL_GETPW_BUFSIZ); + if (!buf) + return -ENOMEM; + + if (!getgrnam_r(groupname, &grp, buf, UL_GETPW_BUFSIZ, &gr) && gr) { + *gid = gr->gr_gid; + rc = 0; + } else { + DBG(UTILS, ul_debug( + "cannot convert '%s' groupname to GID", groupname)); + if (errno == 0) + errno = EINVAL; + rc = -errno;; + } + + free(buf); + return rc; +} + +static int parse_uid_numeric(const char *value, uid_t *uid) +{ + uint64_t num; + int rc; + + assert(value); + assert(uid); + + rc = ul_strtou64(value, &num, 10); + if (rc != 0) + goto fail; + + if (num > ULONG_MAX || (uid_t) num != num) { + rc = -(errno = ERANGE); + goto fail; + } + *uid = (uid_t) num; + + return 0; +fail: + DBG(UTILS, ul_debug("failed to convert '%s' to number [rc=%d, errno=%d]", value, rc, errno)); + return rc; +} + +/* Parse user_len-sized user; returns <0 on error, or 0 on success */ +int mnt_parse_uid(const char *user, size_t user_len, uid_t *uid) +{ + char *user_tofree = NULL; + int rc; + + assert(user); + assert(user_len); + assert(uid); + + if (user[user_len] != '\0') { + user = user_tofree = strndup(user, user_len); + if (!user) + return -ENOMEM; + } + + rc = mnt_get_uid(user, uid); + if (rc != 0 && isdigit(*user)) + rc = parse_uid_numeric(user, uid); + + free(user_tofree); + return rc; +} + +static int parse_gid_numeric(const char *value, gid_t *gid) +{ + uint64_t num; + int rc; + + assert(value); + assert(gid); + + rc = ul_strtou64(value, &num, 10); + if (rc != 0) + goto fail; + + if (num > ULONG_MAX || (gid_t) num != num) { + rc = -(errno = ERANGE); + goto fail; + } + *gid = (gid_t) num; + + return 0; +fail: + DBG(UTILS, ul_debug("failed to convert '%s' to number [rc=%d, errno=%d]", value, rc, errno)); + return rc; +} + +/* POSIX-parse group_len-sized group; -1 and errno set, or 0 on success */ +int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid) +{ + char *group_tofree = NULL; + int rc; + + assert(group); + assert(group_len); + assert(gid); + + if (group[group_len] != '\0') { + group = group_tofree = strndup(group, group_len); + if (!group) + return -ENOMEM; + } + + rc = mnt_get_gid(group, gid); + if (rc != 0 && isdigit(*group)) + rc = parse_gid_numeric(group, gid); + + free(group_tofree); + return rc; +} + +int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *uid) +{ + char buf[sizeof(stringify_value(UINT32_MAX))]; + uint32_t num; + int rc; + + assert(mode); + assert(mode_len); + assert(uid); + + if (mode_len > sizeof(buf) - 1) { + rc = -(errno = ERANGE); + goto fail; + } + mem2strcpy(buf, mode, mode_len, sizeof(buf)); + + rc = ul_strtou32(buf, &num, 8); + if (rc != 0) + goto fail; + if (num > 07777) { + rc = -(errno = ERANGE); + goto fail; + } + *uid = (mode_t) num; + + return 0; +fail: + DBG(UTILS, ul_debug("failed to convert '%.*s' to mode [rc=%d, errno=%d]", + (int) mode_len, mode, rc, errno)); + return rc; +} + +int mnt_in_group(gid_t gid) +{ + int rc = 0, n, i; + gid_t *grps = NULL; + + if (getgid() == gid) + return 1; + + n = getgroups(0, NULL); + if (n <= 0) + goto done; + + grps = malloc(n * sizeof(*grps)); + if (!grps) + goto done; + + if (getgroups(n, grps) == n) { + for (i = 0; i < n; i++) { + if (grps[i] == gid) { + rc = 1; + break; + } + } + } +done: + free(grps); + return rc; +} + +static int try_write(const char *filename, const char *directory) +{ + int rc = 0; + + if (!filename) + return -EINVAL; + + DBG(UTILS, ul_debug("try write %s dir: %s", filename, directory)); + +#ifdef HAVE_EACCESS + /* Try eaccess() first, because open() is overkill, may be monitored by + * audit and we don't want to fill logs by our checks... + */ + if (eaccess(filename, R_OK|W_OK) == 0) { + DBG(UTILS, ul_debug(" access OK")); + return 0; + } + + if (errno != ENOENT) { + DBG(UTILS, ul_debug(" access FAILED")); + return -errno; + } + + if (directory) { + /* file does not exist; try if directory is writable */ + if (eaccess(directory, R_OK|W_OK) != 0) + rc = -errno; + + DBG(UTILS, ul_debug(" access %s [%s]", rc ? "FAILED" : "OK", directory)); + return rc; + } +#endif + + DBG(UTILS, ul_debug(" doing open-write test")); + + int fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC, + S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); + if (fd < 0) + rc = -errno; + else + close(fd); + + return rc; +} + +/** + * mnt_has_regular_mtab: + * @mtab: returns NULL + * @writable: returns 0 + * + * Returns: 0 + * + * Deprecated: libmount does not use /etc/mtab at all since v2.39. + */ +int mnt_has_regular_mtab(const char **mtab, int *writable) +{ + if (writable) + *writable = 0; + if (mtab) + *mtab = NULL; + return 0; +} + +/* + * Don't export this to libmount API -- utab is private library stuff. + * + * If the file does not exist and @writable argument is not NULL, then it will + * try to create the directory (e.g. /run/mount) and the file. + * + * Returns: 1 if utab is a regular file, and 0 in case of + * error (check errno for more details). + */ +int mnt_has_regular_utab(const char **utab, int *writable) +{ + struct stat st; + int rc; + const char *filename = utab && *utab ? *utab : mnt_get_utab_path(); + + if (writable) + *writable = 0; + if (utab && !*utab) + *utab = filename; + + DBG(UTILS, ul_debug("utab: %s", filename)); + + rc = lstat(filename, &st); + + if (rc == 0) { + /* file exists */ + if (S_ISREG(st.st_mode)) { + if (writable) + *writable = !try_write(filename, NULL); + return 1; + } + goto done; /* it's not a regular file */ + } + + if (writable) { + char *dirname = strdup(filename); + + if (!dirname) + goto done; + + stripoff_last_component(dirname); /* remove filename */ + + rc = mkdir(dirname, S_IWUSR| + S_IRUSR|S_IRGRP|S_IROTH| + S_IXUSR|S_IXGRP|S_IXOTH); + if (rc && errno != EEXIST) { + free(dirname); + goto done; /* probably EACCES */ + } + + *writable = !try_write(filename, dirname); + free(dirname); + if (*writable) + return 1; + } +done: + DBG(UTILS, ul_debug("%s: irregular/non-writable file", filename)); + return 0; +} + +/** + * mnt_get_swaps_path: + * + * Returns: path to /proc/swaps or $LIBMOUNT_SWAPS. + */ +const char *mnt_get_swaps_path(void) +{ + const char *p = safe_getenv("LIBMOUNT_SWAPS"); + return p ? : _PATH_PROC_SWAPS; +} + +/** + * mnt_get_fstab_path: + * + * Returns: path to /etc/fstab or $LIBMOUNT_FSTAB. + */ +const char *mnt_get_fstab_path(void) +{ + const char *p = safe_getenv("LIBMOUNT_FSTAB"); + return p ? : _PATH_MNTTAB; +} + +/** + * mnt_get_mtab_path: + * + * This function returns the *default* location of the mtab file. + * + * + * Returns: path to /etc/mtab or $LIBMOUNT_MTAB. + * + * Deprecated: libmount uses /proc/self/mountinfo only. + * + */ +const char *mnt_get_mtab_path(void) +{ + const char *p = safe_getenv("LIBMOUNT_MTAB"); + return p ? : _PATH_MOUNTED; +} + +/* + * Don't export this to libmount API -- utab is private library stuff. + * + * Returns: path to /run/mount/utab or $LIBMOUNT_UTAB. + */ +const char *mnt_get_utab_path(void) +{ + const char *p = safe_getenv("LIBMOUNT_UTAB"); + return p ? : MNT_PATH_UTAB; +} + + +/* returns file descriptor or -errno, @name returns a unique filename + */ +int mnt_open_uniq_filename(const char *filename, char **name) +{ + int rc, fd; + char *n; + mode_t oldmode; + + if (!filename) + return -EINVAL; + if (name) + *name = NULL; + + rc = asprintf(&n, "%s.XXXXXX", filename); + if (rc <= 0) + return -errno; + + /* This is for very old glibc and for compatibility with Posix, which says + * nothing about mkstemp() mode. All sane glibc use secure mode (0600). + */ + oldmode = umask(S_IRGRP|S_IWGRP|S_IXGRP| + S_IROTH|S_IWOTH|S_IXOTH); + + fd = mkstemp_cloexec(n); + if (fd < 0) + fd = -errno; + umask(oldmode); + + if (fd >= 0 && name) + *name = n; + else + free(n); + + return fd; +} + +/** + * mnt_get_mountpoint: + * @path: pathname + * + * This function finds the mountpoint that a given path resides in. @path + * should be canonicalized. The returned pointer should be freed by the caller. + * + * WARNING: the function compares st_dev of the @path elements. This traditional + * way may be insufficient on filesystems like Linux "overlay". See also + * mnt_table_find_target(). + * + * Returns: allocated string with the target of the mounted device or NULL on error + */ +char *mnt_get_mountpoint(const char *path) +{ + char *mnt; + struct stat st; + dev_t dir, base; + + if (!path) + return NULL; + + mnt = strdup(path); + if (!mnt) + return NULL; + if (*mnt == '/' && *(mnt + 1) == '\0') + goto done; + + if (mnt_safe_stat(mnt, &st)) + goto err; + base = st.st_dev; + + do { + char *p = stripoff_last_component(mnt); + + if (!p) + break; + if (mnt_safe_stat(*mnt ? mnt : "/", &st)) + goto err; + dir = st.st_dev; + if (dir != base) { + if (p > mnt) + *(p - 1) = '/'; + goto done; + } + base = dir; + } while (mnt && *(mnt + 1) != '\0'); + + memcpy(mnt, "/", 2); +done: + DBG(UTILS, ul_debug("%s mountpoint is %s", path, mnt)); + return mnt; +err: + free(mnt); + return NULL; +} + +/* + * Search for @name kernel command parameter. + * + * Returns newly allocated string with a parameter argument if the @name is + * specified as "name=" or returns pointer to @name or returns NULL if not + * found. If it is specified more than once, we grab the last copy. + * + * For example cmdline: "aaa bbb=BBB ccc" + * + * @name is "aaa" --returns--> "aaa" (pointer to @name) + * @name is "bbb=" --returns--> "BBB" (allocated) + * @name is "foo" --returns--> NULL + * + * Note: It is not really feasible to parse the command line exactly the same + * as the kernel does since we don't know which options are valid. We can use + * the -- marker though and not walk past that. + */ +char *mnt_get_kernel_cmdline_option(const char *name) +{ + FILE *f; + size_t len; + int val = 0; + char *p, *res = NULL, *mem = NULL; + char buf[BUFSIZ]; /* see kernel include/asm-generic/setup.h: COMMAND_LINE_SIZE */ + const char *path = _PATH_PROC_CMDLINE; + + if (!name || !name[0]) + return NULL; + +#ifdef TEST_PROGRAM + path = getenv("LIBMOUNT_KERNEL_CMDLINE"); + if (!path) + path = _PATH_PROC_CMDLINE; +#endif + f = fopen(path, "r" UL_CLOEXECSTR); + if (!f) + return NULL; + + p = fgets(buf, sizeof(buf), f); + fclose(f); + + if (!p || !*p || *p == '\n') + return NULL; + + p = strstr(p, " -- "); + if (p) { + /* no more kernel args after this */ + *p = '\0'; + } else { + len = strlen(buf); + buf[len - 1] = '\0'; /* remove last '\n' */ + } + + len = strlen(name); + if (name[len - 1] == '=') + val = 1; + + for (p = buf; p && *p; p++) { + if (!(p = strstr(p, name))) + break; /* not found the option */ + if (p != buf && !isblank(*(p - 1))) + continue; /* no space before the option */ + if (!val && *(p + len) != '\0' && !isblank(*(p + len))) + continue; /* no space after the option */ + if (val) { + char *v = p + len; + int end; + + while (*p && !isblank(*p)) /* jump to the end of the argument */ + p++; + end = (*p == '\0'); + *p = '\0'; + free(mem); + res = mem = strdup(v); + if (end) + break; + } else + res = (char *) name; /* option without '=' */ + /* don't break -- keep scanning for more options */ + } + + return res; +} + +/** + * mnt_guess_system_root: + * @devno: device number or zero + * @cache: paths cache or NULL + * @path: returns allocated path + * + * Converts @devno to the real device name if devno major number is greater + * than zero, otherwise use root= kernel cmdline option to get device name. + * + * The function uses /sys to convert devno to device name. + * + * Returns: 0 = success, 1 = not found, <0 = error + * + * Since: 2.34 + */ +int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path) +{ + char buf[PATH_MAX]; + char *dev = NULL, *spec = NULL; + unsigned int x, y; + int allocated = 0; + + assert(path); + + DBG(UTILS, ul_debug("guessing system root [devno %u:%u]", major(devno), minor(devno))); + + /* The pseudo-fs, net-fs or btrfs devno is useless, otherwise it + * usually matches with the source device, let's try to use it. + */ + if (major(devno) > 0) { + dev = sysfs_devno_to_devpath(devno, buf, sizeof(buf)); + if (dev) { + DBG(UTILS, ul_debug(" devno converted to %s", dev)); + goto done; + } + } + + /* Let's try to use root= kernel command line option + */ + spec = mnt_get_kernel_cmdline_option("root="); + if (!spec) + goto done; + + /* maj:min notation */ + if (sscanf(spec, "%u:%u", &x, &y) == 2) { + dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf)); + if (dev) { + DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev)); + goto done; + } + + /* hexhex notation */ + } else if (isxdigit_string(spec)) { + char *end = NULL; + uint32_t n; + + errno = 0; + n = strtoul(spec, &end, 16); + + if (errno || spec == end || (end && *end)) + DBG(UTILS, ul_debug(" failed to parse root='%s'", spec)); + else { + /* kernel new_decode_dev() */ + x = (n & 0xfff00) >> 8; + y = (n & 0xff) | ((n >> 12) & 0xfff00); + dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf)); + if (dev) { + DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev)); + goto done; + } + } + + /* devname or PARTUUID= etc. */ + } else { + DBG(UTILS, ul_debug(" converting root='%s'", spec)); + + dev = mnt_resolve_spec(spec, cache); + if (dev && !cache) + allocated = 1; + } +done: + free(spec); + if (dev) { + *path = allocated ? dev : strdup(dev); + if (!*path) + return -ENOMEM; + return 0; + } + + return 1; +} + +#ifdef TEST_PROGRAM +static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *type = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", mnt_match_fstype(type, pattern) ? "MATCH" : "NOT-MATCH"); + return 0; +} + +static int test_match_options(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", mnt_match_options(optstr, pattern) ? "MATCH" : "NOT-MATCH"); + return 0; +} + +static int test_startswith(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", startswith(optstr, pattern) ? "YES" : "NOT"); + return 0; +} + +static int test_endswith(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", endswith(optstr, pattern) ? "YES" : "NOT"); + return 0; +} + +static int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *path = canonicalize_path(argv[1]), + *mnt = path ? mnt_get_mountpoint(path) : NULL; + + printf("%s: %s\n", argv[1], mnt ? : "unknown"); + free(mnt); + free(path); + return 0; +} + +static int test_filesystems(struct libmnt_test *ts, int argc, char *argv[]) +{ + char **filesystems = NULL; + int rc; + + rc = mnt_get_filesystems(&filesystems, argc ? argv[1] : NULL); + if (!rc) { + char **p; + for (p = filesystems; *p; p++) + printf("%s\n", *p); + mnt_free_filesystems(filesystems); + } + return rc; +} + +static int test_chdir(struct libmnt_test *ts, int argc, char *argv[]) +{ + int rc; + char *path = canonicalize_path(argv[1]), + *last = NULL; + + if (!path) + return -errno; + + rc = mnt_chdir_to_parent(path, &last); + if (!rc) { + printf("path='%s', abs='%s', last='%s'\n", + argv[1], path, last); + } + free(path); + free(last); + return rc; +} + +static int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *name = argv[1]; + char *res; + + res = mnt_get_kernel_cmdline_option(name); + if (!res) + printf("'%s' not found\n", name); + else if (res == name) + printf("'%s' found\n", name); + else { + printf("'%s' found, argument: '%s'\n", name, res); + free(res); + } + + return 0; +} + + +static int test_guess_root(struct libmnt_test *ts, int argc, char *argv[]) +{ + int rc; + char *real; + dev_t devno = 0; + + if (argc) { + unsigned int x, y; + + if (sscanf(argv[1], "%u:%u", &x, &y) != 2) + return -EINVAL; + devno = makedev(x, y); + } + + rc = mnt_guess_system_root(devno, NULL, &real); + if (rc < 0) + return rc; + if (rc == 1) + fputs("not found\n", stdout); + else { + printf("%s\n", real); + free(real); + } + return 0; +} + +static int test_mkdir(struct libmnt_test *ts, int argc, char *argv[]) +{ + int rc; + + rc = ul_mkdir_p(argv[1], S_IRWXU | + S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH); + if (rc) + printf("mkdir %s failed\n", argv[1]); + return rc; +} + +static int test_statfs_type(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct statfs vfs; + int rc; + + rc = statfs(argv[1], &vfs); + if (rc) + printf("%s: statfs failed: %m\n", argv[1]); + else + printf("%-30s: statfs type: %-12s [0x%lx]\n", argv[1], + mnt_statfs_get_fstype(&vfs), + (long) vfs.f_type); + return rc; +} + +static int tests_parse_uid(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *str = argv[1]; + uid_t uid = (uid_t) -1; + int rc; + + rc = mnt_parse_uid(str, strlen(str), &uid); + if (rc != 0) + printf("failed: rc=%d: %m\n", rc); + else + printf("'%s' --> %lu\n", str, (unsigned long) uid); + + return rc; +} + +static int tests_parse_gid(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *str = argv[1]; + gid_t gid = (gid_t) -1; + int rc; + + rc = mnt_parse_gid(str, strlen(str), &gid); + if (rc != 0) + printf("failed: rc=%d: %m\n", rc); + else + printf("'%s' --> %lu\n", str, (unsigned long) gid); + + return rc; +} + +static int tests_parse_mode(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *str = argv[1]; + mode_t mod = (mode_t) -1; + int rc; + + rc = mnt_parse_mode(str, strlen(str), &mod); + if (rc != 0) + printf("failed: rc=%d: %m\n", rc); + else { + char modstr[11]; + + xstrmode(mod, modstr); + printf("'%s' --> %04o [%s]\n", str, (unsigned int) mod, modstr); + } + return rc; +} + +static int tests_stat(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *path = argv[1]; + struct stat st; + int rc; + + if (strcmp(argv[0], "--lstat") == 0) + rc = mnt_safe_lstat(path, &st); + else + rc = mnt_safe_stat(path, &st); + if (rc) + printf("%s: failed: rc=%d: %m\n", path, rc); + else { + printf("%s: \n", path); + printf(" S_ISDIR: %s\n", S_ISDIR(st.st_mode) ? "y" : "n"); + printf(" S_ISREG: %s\n", S_ISREG(st.st_mode) ? "y" : "n"); + printf(" S_IFLNK: %s\n", S_ISLNK(st.st_mode) ? "y" : "n"); + + printf(" devno: %lu (%d:%d)\n", (unsigned long) st.st_dev, + major(st.st_dev), minor(st.st_dev)); + printf(" ino: %lu\n", (unsigned long) st.st_ino); + + } + return rc; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--match-fstype", test_match_fstype, "<type> <pattern> FS types matching" }, + { "--match-options", test_match_options, "<options> <pattern> options matching" }, + { "--filesystems", test_filesystems, "[<pattern>] list /{etc,proc}/filesystems" }, + { "--starts-with", test_startswith, "<string> <prefix>" }, + { "--ends-with", test_endswith, "<string> <prefix>" }, + { "--mountpoint", test_mountpoint, "<path>" }, + { "--cd-parent", test_chdir, "<path>" }, + { "--kernel-cmdline",test_kernel_cmdline, "<option> | <option>=" }, + { "--guess-root", test_guess_root, "[<maj:min>]" }, + { "--mkdir", test_mkdir, "<path>" }, + { "--statfs-type", test_statfs_type, "<path>" }, + { "--parse-uid", tests_parse_uid, "<username|uid>" }, + { "--parse-gid", tests_parse_gid, "<groupname|gid>" }, + { "--parse-mode", tests_parse_mode, "<number>" }, + { "--stat", tests_stat, "<path>" }, + { "--lstat", tests_stat, "<path>" }, + { NULL } + }; + + return mnt_run_test(tss, argc, argv); +} + +#endif /* TEST_PROGRAM */ diff --git a/libmount/src/version.c b/libmount/src/version.c new file mode 100644 index 0000000..894c20c --- /dev/null +++ b/libmount/src/version.c @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 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. + */ + +/** + * SECTION: version-utils + * @title: Version functions + * @short_description: functions to get the library version. + */ + +#include <ctype.h> + +#include "mountP.h" +#include "mount-api-utils.h" + +static const char *lib_version = LIBMOUNT_VERSION; +static const char *lib_features[] = { +#ifdef HAVE_LIBSELINUX + "selinux", +#endif +#ifdef HAVE_SMACK + "smack", +#endif +#ifdef HAVE_BTRFS_SUPPORT + "btrfs", +#endif +#ifdef HAVE_CRYPTSETUP + "verity", +#endif +#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES + "namespaces", +#endif +#if defined(HAVE_MOUNTFD_API) && defined(HAVE_LINUX_MOUNT_H) + "idmapping", +#endif +#ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT + "fd-based-mount", +#endif +#if defined(HAVE_STATX) && defined(HAVE_STRUCT_STATX) && defined(AT_STATX_DONT_SYNC) + "statx", +#endif +#if !defined(NDEBUG) + "assert", /* libc assert.h stuff */ +#endif + "debug", /* always enabled */ + NULL +}; + +/** + * mnt_parse_version_string: + * @ver_string: version string (e.g "2.18.0") + * + * Returns: release version code. + */ +int mnt_parse_version_string(const char *ver_string) +{ + const char *cp; + int version = 0; + + assert(ver_string); + + for (cp = ver_string; *cp; cp++) { + if (*cp == '.') + continue; + if (!isdigit(*cp)) + break; + version = (version * 10) + (*cp - '0'); + } + return version; +} + +/** + * mnt_get_library_version: + * @ver_string: return pointer to the static library version string if not NULL + * + * Returns: release version number. + */ +int mnt_get_library_version(const char **ver_string) +{ + if (ver_string) + *ver_string = lib_version; + + return mnt_parse_version_string(lib_version); +} + +/** + * mnt_get_library_features: + * @features: returns a pointer to the static array of strings, the array is + * terminated by NULL. + * + * Returns: number of items in the features array not including the last NULL, + * or less than zero in case of error + * + * Example: + * <informalexample> + * <programlisting> + * const char **features; + * + * mnt_get_library_features(&features); + * while (features && *features) + * printf("%s\n", *features++); + * </programlisting> + * </informalexample> + * + */ +int mnt_get_library_features(const char ***features) +{ + if (!features) + return -EINVAL; + + *features = lib_features; + return ARRAY_SIZE(lib_features) - 1; +} + +#ifdef TEST_PROGRAM +static int test_version(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *ver; + const char **features; + + if (argc == 2) + printf("Your version: %d\n", + mnt_parse_version_string(argv[1])); + + mnt_get_library_version(&ver); + + printf("Library version: %s\n", ver); + printf("Library API version: " LIBMOUNT_VERSION "\n"); + printf("Library features:"); + + mnt_get_library_features(&features); + while (features && *features) + printf(" %s", *features++); + + printf("\n"); + + if (mnt_get_library_version(NULL) == + mnt_parse_version_string(LIBMOUNT_VERSION)) + return 0; + + return -1; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test ts[] = { + { "--print", test_version, "print versions" }, + { NULL } + }; + + return mnt_run_test(ts, argc, argv); +} +#endif |