summaryrefslogtreecommitdiffstats
path: root/libmount/src
diff options
context:
space:
mode:
Diffstat (limited to 'libmount/src')
-rw-r--r--libmount/src/Makemodule.am217
-rw-r--r--libmount/src/btrfs.c175
-rw-r--r--libmount/src/cache.c861
-rw-r--r--libmount/src/context.c3455
-rw-r--r--libmount/src/context_mount.c1866
-rw-r--r--libmount/src/context_umount.c1333
-rw-r--r--libmount/src/fs.c1799
-rw-r--r--libmount/src/fuzz.c35
-rw-r--r--libmount/src/hook_idmap.c521
-rw-r--r--libmount/src/hook_loopdev.c530
-rw-r--r--libmount/src/hook_mkdir.c139
-rw-r--r--libmount/src/hook_mount.c790
-rw-r--r--libmount/src/hook_mount_legacy.c310
-rw-r--r--libmount/src/hook_owner.c175
-rw-r--r--libmount/src/hook_selinux.c203
-rw-r--r--libmount/src/hook_subdir.c389
-rw-r--r--libmount/src/hook_veritydev.c646
-rw-r--r--libmount/src/hooks.c404
-rw-r--r--libmount/src/init.c108
-rw-r--r--libmount/src/iter.c78
-rw-r--r--libmount/src/libmount.h.in1053
-rw-r--r--libmount/src/libmount.sym377
-rw-r--r--libmount/src/lock.c399
-rw-r--r--libmount/src/monitor.c982
-rw-r--r--libmount/src/mountP.h685
-rw-r--r--libmount/src/optlist.c1415
-rw-r--r--libmount/src/optmap.c272
-rw-r--r--libmount/src/optstr.c1170
-rw-r--r--libmount/src/tab.c2278
-rw-r--r--libmount/src/tab_diff.c378
-rw-r--r--libmount/src/tab_parse.c1322
-rw-r--r--libmount/src/tab_update.c1066
-rw-r--r--libmount/src/test.c65
-rw-r--r--libmount/src/utils.c1542
-rw-r--r--libmount/src/version.c161
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