summaryrefslogtreecommitdiffstats
path: root/libmount/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
commit30ff6afe596eddafacf22b1a5b2d1a3d6254ea15 (patch)
tree9b788335f92174baf7ee18f03ca8330b8c19ce2b /libmount/src
parentInitial commit. (diff)
downloadutil-linux-upstream.tar.xz
util-linux-upstream.zip
Adding upstream version 2.36.1.upstream/2.36.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--libmount/src/Makemodule.am176
-rw-r--r--libmount/src/btrfs.c175
-rw-r--r--libmount/src/cache.c840
-rw-r--r--libmount/src/context.c3442
-rw-r--r--libmount/src/context_loopdev.c446
-rw-r--r--libmount/src/context_mount.c1935
-rw-r--r--libmount/src/context_umount.c1333
-rw-r--r--libmount/src/context_veritydev.c520
-rw-r--r--libmount/src/fs.c1624
-rw-r--r--libmount/src/init.c106
-rw-r--r--libmount/src/iter.c78
-rw-r--r--libmount/src/libmount.h.in1016
-rw-r--r--libmount/src/libmount.sym358
-rw-r--r--libmount/src/lock.c732
-rw-r--r--libmount/src/monitor.c980
-rw-r--r--libmount/src/mountP.h485
-rw-r--r--libmount/src/optmap.c268
-rw-r--r--libmount/src/optstr.c1442
-rw-r--r--libmount/src/tab.c2206
-rw-r--r--libmount/src/tab_diff.c377
-rw-r--r--libmount/src/tab_parse.c1343
-rw-r--r--libmount/src/tab_update.c1065
-rw-r--r--libmount/src/test.c65
-rw-r--r--libmount/src/utils.c1524
-rw-r--r--libmount/src/version.c154
25 files changed, 22690 insertions, 0 deletions
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
new file mode 100644
index 0000000..eaa69c1
--- /dev/null
+++ b/libmount/src/Makemodule.am
@@ -0,0 +1,176 @@
+
+# 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/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_loopdev.c \
+ libmount/src/context_veritydev.c \
+ libmount/src/context_mount.c \
+ libmount/src/context_umount.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_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
+check_PROGRAMS += test_mount_monitor
+endif
+
+libmount_tests_cflags = -DTEST_PROGRAM $(libmount_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
+libmount_tests_ldflags = libblkid.la -static
+libmount_tests_ldadd = libmount.la $(LDADD) $(REALTIME_LIBS)
+
+if HAVE_SELINUX
+libmount_tests_ldadd += $(SELINUX_LIBS)
+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_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_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)
+
+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..2fafdd0
--- /dev/null
+++ b/libmount/src/cache.c
@@ -0,0 +1,840 @@
+/* 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;
+
+ /* 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 *mtab;
+};
+
+/**
+ * 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->mtab);
+
+ mnt_free_cache(cache);
+ }
+ }
+}
+
+/**
+ * mnt_cache_set_targets:
+ * @cache: cache pointer
+ * @mtab: table with already canonicalized mountpoints
+ *
+ * Add to @cache reference to @mtab. 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 *mtab)
+{
+ if (!cache)
+ return -EINVAL;
+
+ mnt_ref_table(mtab);
+ mnt_unref_table(cache->mtab);
+ cache->mtab = mtab;
+ 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);
+
+ 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, mtab) was called: if @path is found in the
+ * cached @mtab 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;
+
+ /*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/
+
+ if (!cache || !cache->mtab)
+ 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->mtab, &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..6196c71
--- /dev/null
+++ b/libmount/src/context.c
@@ -0,0 +1,3442 @@
+/* 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 "fileutils.h"
+#include "strutils.h"
+#include "namespace.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;
+
+ INIT_LIST_HEAD(&cxt->addmounts);
+
+ ruid = getuid();
+ euid = geteuid();
+
+ mnt_context_reset_status(cxt);
+
+ cxt->loopdev_fd = -1;
+
+ cxt->ns_orig.fd = -1;
+ cxt->ns_tgt.fd = -1;
+ cxt->ns_cur = &cxt->ns_orig;
+
+ /* if we're really root and aren't running setuid */
+ cxt->restricted = (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
+
+ 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_fs(cxt->fs_template);
+
+ mnt_context_clear_loopdev(cxt);
+ 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->mtab);
+ mnt_unref_table(cxt->utab);
+
+ free(cxt->helper);
+ free(cxt->orig_user);
+
+ cxt->fs = NULL;
+ cxt->mtab = NULL;
+ cxt->utab = NULL;
+ cxt->helper = NULL;
+ cxt->orig_user = NULL;
+ cxt->mountflags = 0;
+ cxt->user_mountflags = 0;
+ cxt->mountdata = NULL;
+ cxt->flags = MNT_FL_DEFAULT;
+
+ /* free additional mounts list */
+ while (!list_empty(&cxt->addmounts)) {
+ struct libmnt_addmount *ad = list_entry(cxt->addmounts.next,
+ struct libmnt_addmount,
+ mounts);
+ mnt_free_addmount(ad);
+ }
+
+ mnt_context_reset_status(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 FS 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)
+{
+ struct libmnt_fs *fs = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "save FS as template"));
+
+ if (cxt->fs) {
+ fs = mnt_copy_fs(NULL, cxt->fs);
+ if (!fs)
+ return -ENOMEM;
+ }
+
+ mnt_unref_fs(cxt->fs_template);
+ cxt->fs_template = fs;
+
+ 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)
+{
+ struct libmnt_fs *fs = NULL;
+ int rc = 0;
+
+ if (!cxt)
+ return -EINVAL;
+
+ if (cxt->fs_template) {
+ DBG(CXT, ul_debugobj(cxt, "copy FS from template"));
+ fs = mnt_copy_fs(NULL, cxt->fs_template);
+ if (!fs)
+ return -ENOMEM;
+ rc = mnt_context_set_fs(cxt, fs);
+ mnt_unref_fs(fs);
+ } else {
+ DBG(CXT, ul_debugobj(cxt, "no FS template, reset only"));
+ mnt_unref_fs(cxt->fs);
+ cxt->fs = NULL;
+ }
+
+ return rc;
+}
+
+int mnt_context_has_template(struct libmnt_context *cxt)
+{
+ return cxt && cxt->fs_template ? 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->mtab = o->mtab;
+ mnt_ref_table(n->mtab);
+
+ n->mtab = 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;
+ if (strdup_between_structs(n, o, orig_user))
+ goto failed;
+
+ n->mountflags = o->mountflags;
+ n->mountdata = o->mountdata;
+
+ mnt_context_reset_status(n);
+
+ n->table_fltrcb = o->table_fltrcb;
+ n->table_fltrcb_data = o->table_fltrcb_data;
+
+ 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);
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ if (!cxt->mtab_path) {
+ cxt->mtab_path = mnt_get_mtab_path();
+ DBG(CXT, ul_debugobj(cxt, "mtab path initialized to: %s", cxt->mtab_path));
+ }
+#endif
+ 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"));
+
+ cxt->mtab_writable = 0;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ mnt_has_regular_mtab(&cxt->mtab_path, &cxt->mtab_writable);
+ if (!cxt->mtab_writable)
+#endif
+ /* use /run/mount/utab if /etc/mtab is useless */
+ 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_mtab_writable(struct libmnt_context *cxt)
+{
+ assert(cxt);
+
+ context_init_paths(cxt, 1);
+ return cxt->mtab_writable == 1;
+}
+
+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->mtab_writable ? cxt->mtab_path : 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/mtab.
+ *
+ * @MNT_OMODE_IGNORE: ignore mtab/fstab options
+ *
+ * @MNT_OMODE_APPEND: append mtab/fstab options to existing options
+ *
+ * @MNT_OMODE_PREPEND: prepend mtab/fstab options to existing options
+ *
+ * @MNT_OMODE_REPLACE: replace existing options with options from mtab/fstab
+ *
+ * @MNT_OMODE_FORCE: always read mtab/fstab (although source and target are defined)
+ *
+ * @MNT_OMODE_FSTAB: read from fstab
+ *
+ * @MNT_OMODE_MTAB: read from mtab if fstab not enabled or failed
+ *
+ * @MNT_OMODE_NOTAB: do not read fstab/mtab at 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/mtab
+ * 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_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 mtab update (see mount(8) man page, option -n).
+ *
+ * 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;
+
+ DBG(CXT, ul_debugobj(cxt, "setting new FS"));
+ mnt_ref_fs(fs); /* new */
+ mnt_unref_fs(cxt->fs); /* old */
+ 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)
+ cxt->fs = mnt_new_fs();
+ 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
+ *
+ * Returns: pointer to userdata or NULL.
+ */
+void *mnt_context_get_mtab_userdata(struct libmnt_context *cxt)
+{
+ return cxt->mtab ? mnt_table_get_userdata(cxt->mtab) : 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));
+}
+
+/**
+ * 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)
+{
+ return mnt_fs_set_options(mnt_context_get_fs(cxt), optstr);
+}
+
+/**
+ * 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)
+{
+ return mnt_fs_append_options(mnt_context_get_fs(cxt), optstr);
+}
+
+/**
+ * mnt_context_get_options:
+ * @cxt: mount context
+ *
+ * This function returns mount options set by mnt_context_set_options() or
+ * mnt_context_append_options().
+ *
+ * Note that *after* mnt_context_prepare_mount(), the mount options string
+ * may also include options set by mnt_context_set_mflags() or other options
+ * generated by this library.
+ *
+ * Returns: pointer or NULL
+ */
+const char *mnt_context_get_options(struct libmnt_context *cxt)
+{
+ return mnt_fs_get_options(mnt_context_get_fs(cxt));
+}
+
+/**
+ * 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;
+}
+
+/**
+ * mnt_context_get_mtab:
+ * @cxt: mount context
+ * @tb: returns mtab
+ *
+ * See also mnt_table_parse_mtab() for more details about mtab/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)
+{
+ int rc = 0;
+ struct libmnt_ns *ns_old = NULL;
+
+ if (!cxt)
+ return -EINVAL;
+ if (!cxt->mtab) {
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ context_init_paths(cxt, 0);
+
+ cxt->mtab = mnt_new_table();
+ if (!cxt->mtab) {
+ rc = -ENOMEM;
+ goto end;
+ }
+
+ if (cxt->table_errcb)
+ mnt_table_set_parser_errcb(cxt->mtab, cxt->table_errcb);
+ if (cxt->table_fltrcb)
+ mnt_table_set_parser_fltrcb(cxt->mtab,
+ cxt->table_fltrcb,
+ cxt->table_fltrcb_data);
+
+ mnt_table_set_cache(cxt->mtab, mnt_context_get_cache(cxt));
+
+ /*
+ * Note that mtab_path is NULL if mtab is useless or unsupported
+ */
+ if (cxt->utab)
+ /* utab already parsed, don't parse it again */
+ rc = __mnt_table_parse_mtab(cxt->mtab,
+ cxt->mtab_path, cxt->utab);
+ else
+ rc = mnt_table_parse_mtab(cxt->mtab, cxt->mtab_path);
+ if (rc)
+ goto end;
+ }
+
+ if (tb)
+ *tb = cxt->mtab;
+
+ DBG(CXT, ul_debugobj(cxt, "mtab requested [nents=%d]",
+ mnt_table_get_nents(cxt->mtab)));
+
+end:
+ if (ns_old && !mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/*
+ * Called by mtab parser to filter out entries, non-zero means that
+ * an entry has to be filtered out.
+ */
+static int mtab_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_mtab(), but does not read all mountinfo/mtab
+ * file, but only entries relevant for @tgt.
+ */
+int mnt_context_get_mtab_for_target(struct libmnt_context *cxt,
+ struct libmnt_table **mtab,
+ 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, mtab_filter, (void *) tgt);
+
+ else if (mnt_stat_mountpoint(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, mtab_filter, cn_tgt);
+ }
+
+ rc = mnt_context_get_mtab(cxt, mtab);
+ 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 mtab and 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->mtab)
+ mnt_table_set_parser_fltrcb(cxt->mtab,
+ 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 mtab 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. mtab, 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->mtab)
+ mnt_table_set_parser_errcb(cxt->mtab, 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 mtab 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->mtab)
+ mnt_table_set_cache(cxt->mtab, 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 mtab 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 mtab 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 mtab/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 mount context can be used to define mount options by mount flags. It
+ * means you can for example use
+ *
+ * mnt_context_set_mflags(cxt, MS_NOEXEC | MS_NOSUID);
+ *
+ * rather than
+ *
+ * mnt_context_set_options(cxt, "noexec,nosuid");
+ *
+ * both of these calls have the same effect.
+ *
+ * Be careful if you want to use MS_REC flag -- in this case the bit is applied
+ * to all bind/slave/etc. options. If you want to mix more propadation flags
+ * and/or bind operations than it's better to specify mount options by
+ * strings.
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_context_set_mflags(struct libmnt_context *cxt, unsigned long flags)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ cxt->mountflags = flags;
+
+ if ((cxt->flags & MNT_FL_MOUNTOPTS_FIXED) && cxt->fs)
+ /*
+ * the final mount options are already generated, refresh...
+ */
+ return mnt_optstr_apply_flags(
+ &cxt->fs->vfs_optstr,
+ cxt->mountflags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+
+ return 0;
+}
+
+/**
+ * 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)
+{
+ int rc = 0;
+ struct list_head *p;
+
+ if (!cxt || !flags)
+ return -EINVAL;
+
+ *flags = 0;
+ if (!(cxt->flags & MNT_FL_MOUNTFLAGS_MERGED) && cxt->fs) {
+ const char *o = mnt_fs_get_options(cxt->fs);
+ if (o)
+ rc = mnt_optstr_get_flags(o, flags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ }
+
+ list_for_each(p, &cxt->addmounts) {
+ struct libmnt_addmount *ad =
+ list_entry(p, struct libmnt_addmount, mounts);
+
+ *flags |= ad->mountflags;
+ }
+
+ if (!rc)
+ *flags |= cxt->mountflags;
+ return rc;
+}
+
+/**
+ * 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)
+{
+ if (!cxt)
+ return -EINVAL;
+ cxt->user_mountflags = flags;
+ return 0;
+}
+
+/**
+ * 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)
+{
+ int rc = 0;
+
+ if (!cxt || !flags)
+ return -EINVAL;
+
+ *flags = 0;
+ if (!(cxt->flags & MNT_FL_MOUNTFLAGS_MERGED) && cxt->fs) {
+ const char *o = mnt_fs_get_user_options(cxt->fs);
+ if (o)
+ rc = mnt_optstr_get_flags(o, flags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP));
+ }
+ if (!rc)
+ *flags |= cxt->user_mountflags;
+ return rc;
+}
+
+/**
+ * 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;
+ 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 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;
+
+ 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;
+
+ if ((cxt->mountflags & (MS_BIND | MS_MOVE | MS_REMOUNT))
+ || mnt_fs_is_pseudofs(cxt->fs)) {
+ DBG(CXT, ul_debugobj(cxt, "REMOUNT/BIND/MOVE/pseudo FS source: %s", path));
+ goto end;
+ }
+
+
+ /*
+ * Initialize verity or loop device
+ * ENOTSUP means verity options were requested, but the library is built without
+ * libcryptsetup so integrity cannot be enforced, and this should be an error
+ * rather than a silent fallback to a simple loopdev mount
+ */
+ rc = mnt_context_is_veritydev(cxt);
+ if (rc == -ENOTSUP) {
+ goto end;
+ } else if (rc) {
+ rc = mnt_context_setup_veritydev(cxt);
+ if (rc)
+ goto end;
+ } else if (mnt_context_is_loopdev(cxt)) {
+ rc = mnt_context_setup_loopdev(cxt);
+ 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;
+}
+
+static int is_mkdir_required(const char *tgt, struct libmnt_fs *fs, mode_t *mode, int *rc)
+{
+ char *mstr = NULL;
+ size_t mstr_sz = 0;
+ struct stat st;
+
+ assert(tgt);
+ assert(fs);
+ assert(mode);
+ assert(rc);
+
+ *mode = 0;
+ *rc = 0;
+
+ if (mnt_optstr_get_option(fs->user_optstr, "X-mount.mkdir", &mstr, &mstr_sz) != 0 &&
+ mnt_optstr_get_option(fs->user_optstr, "x-mount.mkdir", &mstr, &mstr_sz) != 0) /* obsolete */
+ return 0;
+
+ if (mnt_stat_mountpoint(tgt, &st) == 0)
+ return 0;
+
+ DBG(CXT, ul_debug("mkdir %s (%s) wanted", tgt, mstr));
+
+ if (mstr && mstr_sz) {
+ char *end = NULL;
+
+ errno = 0;
+ *mode = strtol(mstr, &end, 8);
+
+ if (errno || !end || mstr + mstr_sz != end) {
+ DBG(CXT, 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;
+
+ return 1;
+}
+
+int mnt_context_prepare_target(struct libmnt_context *cxt)
+{
+ const char *tgt, *prefix;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+ mode_t mode = 0;
+
+ 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;
+
+ /* X-mount.mkdir target */
+ if (cxt->action == MNT_ACT_MOUNT
+ && (cxt->user_mountflags & MNT_MS_XCOMMENT ||
+ cxt->user_mountflags & MNT_MS_XFSTABCOMM)
+ && is_mkdir_required(tgt, cxt->fs, &mode, &rc)) {
+
+ /* supported only for root or non-suid mount(8) */
+ if (!mnt_context_is_restricted(cxt)) {
+ rc = mkdir_p(tgt, mode);
+ if (rc)
+ DBG(CXT, ul_debug("mkdir %s failed: %m", tgt));
+ } else
+ rc = -EPERM;
+ }
+
+ /* 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 (!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;
+}
+
+/* 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;
+
+ 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)
+{
+ char *type;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ DBG(CXT, ul_debugobj(cxt, "preparing fstype"));
+
+ if ((cxt->mountflags & (MS_BIND | MS_MOVE))
+ || 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 (cxt->mountflags & MS_REMOUNT)
+ 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));
+
+ 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 due to stat() etc.
+ */
+ path = strtok_r(search_path, ":", &p);
+ while (path) {
+ char helper[PATH_MAX];
+ struct stat st;
+ int xrc;
+
+ xrc = snprintf(helper, sizeof(helper), "%s/%s.%s",
+ path, name, type);
+ path = strtok_r(NULL, ":", &p);
+
+ if (xrc < 0 || (size_t) xrc >= sizeof(helper))
+ continue;
+
+ xrc = stat(helper, &st);
+ if (xrc == -1 && errno == ENOENT && strchr(type, '.')) {
+ /* If type ends with ".subtype" try without it */
+ char *hs = strrchr(helper, '.');
+ if (hs)
+ *hs = '\0';
+ xrc = stat(helper, &st);
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "%-25s ... %s", helper,
+ xrc ? "not found" : "found"));
+ if (xrc)
+ 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;
+}
+
+int mnt_context_merge_mflags(struct libmnt_context *cxt)
+{
+ unsigned long fl = 0;
+ int rc;
+
+ assert(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "merging mount flags"));
+
+ rc = mnt_context_get_mflags(cxt, &fl);
+ if (rc)
+ return rc;
+ cxt->mountflags = fl;
+
+ fl = 0;
+ rc = mnt_context_get_user_mflags(cxt, &fl);
+ if (rc)
+ return rc;
+ cxt->user_mountflags = fl;
+
+ DBG(CXT, ul_debugobj(cxt, "final flags: VFS=%08lx user=%08lx",
+ cxt->mountflags, cxt->user_mountflags));
+
+ cxt->flags |= MNT_FL_MOUNTFLAGS_MERGED;
+ return 0;
+}
+
+/*
+ * Prepare /etc/mtab or /run/mount/utab
+ */
+int mnt_context_prepare_update(struct libmnt_context *cxt)
+{
+ int rc;
+ const char *target;
+
+ 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;
+ }
+ if (!mnt_context_get_writable_tabpath(cxt)) {
+ 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) {
+ const char *name = mnt_context_get_writable_tabpath(cxt);
+
+ 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_mtab_writable(cxt));
+ }
+
+ if (cxt->action == MNT_ACT_UMOUNT)
+ rc = mnt_update_set_fs(cxt->update, cxt->mountflags,
+ mnt_context_get_target(cxt), NULL);
+ else
+ rc = mnt_update_set_fs(cxt->update, cxt->mountflags,
+ NULL, cxt->fs);
+
+ return rc < 0 ? rc : 0;
+}
+
+int mnt_context_update_tabs(struct libmnt_context *cxt)
+{
+ unsigned long fl;
+ 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;
+ }
+
+ fl = mnt_update_get_mflags(cxt->update);
+ if ((cxt->mountflags & MS_RDONLY) != (fl & MS_RDONLY))
+ /*
+ * fix MS_RDONLY in options
+ */
+ mnt_update_force_rdonly(cxt->update,
+ cxt->mountflags & MS_RDONLY);
+
+ 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 -- use mnt_context_apply_fstab() if not sure
+ */
+int mnt_context_apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs)
+{
+ 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;
+ }
+ }
+
+ 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;
+
+ if (cxt->optsmode & MNT_OMODE_IGNORE)
+ ;
+ else if (cxt->optsmode & MNT_OMODE_REPLACE)
+ rc = mnt_fs_set_options(cxt->fs, mnt_fs_get_options(fs));
+
+ else if (cxt->optsmode & MNT_OMODE_APPEND)
+ rc = mnt_fs_append_options(cxt->fs, mnt_fs_get_options(fs));
+
+ else if (cxt->optsmode & MNT_OMODE_PREPEND)
+ rc = mnt_fs_prepend_options(cxt->fs, mnt_fs_get_options(fs));
+
+ 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)
+{
+ 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 mnt_context_apply_fs(cxt, fs);
+}
+
+/**
+ * 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);
+ }
+
+ /* try mtab */
+ if (rc < 0 && (cxt->optsmode & MNT_OMODE_MTAB)
+ && (isremount || cxt->action == MNT_ACT_UMOUNT)) {
+ DBG(CXT, ul_debugobj(cxt, "trying to apply mtab (src=%s, target=%s)", src, tgt));
+ if (tgt)
+ rc = mnt_context_get_mtab_for_target(cxt, &tab, tgt);
+ else
+ rc = mnt_context_get_mtab(cxt, &tab);
+ if (!rc)
+ rc = apply_table(cxt, tab, MNT_ITER_BACKWARD);
+ }
+
+ 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 mtab entry on remount"));
+ return 0;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "failed to find entry in fstab/mtab [rc=%d]: %m", rc));
+
+ /* force to "not found in fstab/mtab" error, the details why
+ * not found are not so important and may be misinterpreted by
+ * applications... */
+ rc = -MNT_ERR_NOFSTAB;
+
+
+ } else if (isremount && !iscmdbind) {
+
+ /* remove "bind" from fstab (or no-op if not present) */
+ mnt_optstr_remove_option(&cxt->fs->optstr, "bind");
+ }
+ return rc;
+}
+
+/**
+ * mnt_context_tab_applied:
+ * @cxt: mount context
+ *
+ * Returns: 1 if fstab (or mtab) 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)
+{
+ if (cxt->action != MNT_ACT_MOUNT)
+ return 0;
+
+ /* has to be called after context_mount.c: fix_opts() */
+ assert((cxt->flags & MNT_FL_MOUNTOPTS_FIXED));
+
+ /* all propagation mounts are in cxt->addmount */
+ return !list_empty(&cxt->addmounts)
+ && (cxt->mountflags == 0 || cxt->mountflags == MS_SILENT)
+ && cxt->fs
+ && (!cxt->fs->fstype || strcmp(cxt->fs->fstype, "none") == 0)
+ && (!cxt->fs->source || strcmp(cxt->fs->source, "none") == 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;
+}
+
+
+int mnt_context_get_generic_excode(int rc, char *buf, size_t bufsz, 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.
+ *
+ * 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 *mtab, *orig;
+ int rc;
+ 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->mtab;
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ if (rc == -ENOENT && mnt_fs_streq_target(fs, "/proc") &&
+ (!cxt->mtab_path || startswith(cxt->mtab_path, "/proc/"))) {
+ if (!orig) {
+ mnt_unref_table(cxt->mtab);
+ cxt->mtab = NULL;
+ }
+ *mounted = 0;
+ return 0; /* /proc not mounted */
+ }
+
+ if (rc)
+ return rc;
+
+ *mounted = __mnt_table_is_fs_mounted(mtab, fs,
+ mnt_context_get_target_prefix(cxt));
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return 0;
+}
+
+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_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>" },
+ { "--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_loopdev.c b/libmount/src/context_loopdev.c
new file mode 100644
index 0000000..c5fc80d
--- /dev/null
+++ b/libmount/src/context_loopdev.c
@@ -0,0 +1,446 @@
+/* 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.
+ */
+
+/*
+ * DOCS: - "lo@" prefix for fstype is unsupported
+ */
+
+#include <blkid.h>
+#include <stdbool.h>
+
+#include "mountP.h"
+#include "loopdev.h"
+#include "linux_version.h"
+
+
+int mnt_context_is_loopdev(struct libmnt_context *cxt)
+{
+ const char *type, *src;
+
+ assert(cxt);
+
+ /* The mount flags have to be merged, otherwise we have to use
+ * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs)
+ return 0;
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return 0; /* backing file not set */
+
+ if (cxt->user_mountflags & (MNT_MS_LOOP |
+ MNT_MS_OFFSET |
+ MNT_MS_SIZELIMIT)) {
+
+ DBG(LOOP, ul_debugobj(cxt, "loopdev specific options detected"));
+ return 1;
+ }
+
+ if ((cxt->mountflags & (MS_BIND | MS_MOVE))
+ || mnt_context_propagation_only(cxt))
+ return 0;
+
+ /* 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_regular(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"));
+ cxt->user_mountflags |= MNT_MS_LOOP;
+ mnt_optstr_append_option(&cxt->fs->user_optstr, "loop", NULL);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+/* 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;
+ struct libmnt_iter itr;
+ struct libmnt_fs *fs;
+ struct libmnt_cache *cache;
+ const char *bf;
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (mnt_context_get_mtab(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));
+
+ 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 mtab, 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 && (cxt->user_mountflags & 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;
+}
+
+int mnt_context_setup_loopdev(struct libmnt_context *cxt)
+{
+ const char *backing_file, *optstr, *loopdev = NULL;
+ char *val = NULL, *loopval = NULL;
+ size_t len;
+ struct loopdev_cxt lc;
+ int rc = 0, lo_flags = 0;
+ uint64_t offset = 0, sizelimit = 0;
+ bool reuse = FALSE;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ 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));
+
+ if (cxt->mountflags & MS_RDONLY) {
+ DBG(LOOP, ul_debugobj(cxt, "enabling READ-ONLY flag"));
+ lo_flags |= LO_FLAGS_READ_ONLY;
+ }
+
+ optstr = mnt_fs_get_user_options(cxt->fs);
+
+ /*
+ * loop=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_LOOP) &&
+ mnt_optstr_get_option(optstr, "loop", &val, &len) == 0 && val) {
+ loopval = strndup(val, len);
+ rc = loopval ? 0 : -ENOMEM;
+ }
+
+ /*
+ * offset=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_OFFSET) &&
+ mnt_optstr_get_option(optstr, "offset", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &offset);
+ if (rc) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to parse offset="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * sizelimit=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_SIZELIMIT) &&
+ mnt_optstr_get_option(optstr, "sizelimit", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &sizelimit);
+ if (rc) {
+ DBG(LOOP, ul_debugobj(cxt, "failed to parse sizelimit="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * encryption=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ENCRYPTION) &&
+ mnt_optstr_get_option(optstr, "encryption", &val, &len) == 0) {
+ DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported"));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+
+ if (rc == 0 && 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;
+
+ DBG(LOOP, ul_debugobj(cxt, "re-using existing loop device %s",
+ loopcxt_get_device(&lc)));
+
+ /* 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 (loopval) {
+ 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 (loopval) {
+ rc = loopcxt_set_device(&lc, loopval);
+ 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 mtab
+ * because kernel provides the name in /sys.
+ */
+ if (get_linux_version() >= KERNEL_VERSION(2, 6, 37) ||
+ !mnt_context_mtab_writable(cxt)) {
+ 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) {
+ /* success */
+ cxt->flags |= MNT_FL_LOOPDEV_READY;
+
+ if (reuse || ( (cxt->user_mountflags & MNT_MS_LOOP) &&
+ loopcxt_is_autoclear(&lc))) {
+ /*
+ * autoclear flag accepted by the kernel, don't store
+ * the "loop=" option to mtab.
+ */
+ DBG(LOOP, ul_debugobj(cxt, "removing unnecessary loop= from mtab"));
+ cxt->user_mountflags &= ~MNT_MS_LOOP;
+ mnt_optstr_remove_option(&cxt->fs->user_optstr, "loop");
+ }
+
+ if (!(cxt->mountflags & MS_RDONLY) &&
+ loopcxt_is_readonly(&lc))
+ /*
+ * mount planned read-write, but loopdev is read-only,
+ * let's fix mount options...
+ */
+ mnt_context_set_mflags(cxt, cxt->mountflags | MS_RDONLY);
+
+ /* we have to keep the device open until mount(1),
+ * otherwise it will be auto-cleared by kernel
+ */
+ cxt->loopdev_fd = loopcxt_get_fd(&lc);
+ if (cxt->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:
+ free(loopval);
+ return rc;
+}
+
+/*
+ * Deletes loop device
+ */
+int mnt_context_delete_loopdev(struct libmnt_context *cxt)
+{
+ const char *src;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return -EINVAL;
+
+ if (cxt->loopdev_fd > -1)
+ close(cxt->loopdev_fd);
+
+ rc = loopdev_delete(src);
+ cxt->flags &= ~MNT_FL_LOOPDEV_READY;
+ cxt->loopdev_fd = -1;
+
+ DBG(LOOP, ul_debugobj(cxt, "deleted [rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * Clears loopdev stuff in context, should be called after
+ * failed or successful mount(2).
+ */
+int mnt_context_clear_loopdev(struct libmnt_context *cxt)
+{
+ assert(cxt);
+
+ if (mnt_context_get_status(cxt) == 0 &&
+ (cxt->flags & MNT_FL_LOOPDEV_READY)) {
+ /*
+ * mount(2) failed, delete loopdev
+ */
+ mnt_context_delete_loopdev(cxt);
+
+ } else if (cxt->loopdev_fd > -1) {
+ /*
+ * mount(2) success, close the device
+ */
+ DBG(LOOP, ul_debugobj(cxt, "closing FD"));
+ close(cxt->loopdev_fd);
+ }
+ cxt->loopdev_fd = -1;
+ return 0;
+}
+
diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c
new file mode 100644
index 0000000..8c394c1
--- /dev/null
+++ b/libmount/src/context_mount.c
@@ -0,0 +1,1935 @@
+/* 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.
+ */
+
+#ifdef HAVE_LIBSELINUX
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+#endif
+
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#include "linux_version.h"
+#include "mountP.h"
+#include "strutils.h"
+
+/*
+ * Kernel supports only one MS_PROPAGATION flag change by one mount(2) syscall,
+ * to bypass this restriction we call mount(2) per flag. It's really not a perfect
+ * solution, but it's the same like to execute multiple mount(8) commands.
+ *
+ * We use cxt->addmounts (additional mounts) list to keep order of the requested
+ * flags changes.
+ */
+struct libmnt_addmount *mnt_new_addmount(void)
+{
+ struct libmnt_addmount *ad = calloc(1, sizeof(*ad));
+ if (!ad)
+ return NULL;
+
+ INIT_LIST_HEAD(&ad->mounts);
+ return ad;
+}
+
+void mnt_free_addmount(struct libmnt_addmount *ad)
+{
+ if (!ad)
+ return;
+ list_del(&ad->mounts);
+ free(ad);
+}
+
+static int mnt_context_append_additional_mount(struct libmnt_context *cxt,
+ struct libmnt_addmount *ad)
+{
+ assert(cxt);
+ assert(ad);
+
+ if (!list_empty(&ad->mounts))
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt,
+ "mount: add additional flag: 0x%08lx",
+ ad->mountflags));
+
+ list_add_tail(&ad->mounts, &cxt->addmounts);
+ return 0;
+}
+
+/*
+ * add additional mount(2) syscall requests when necessary to set propagation flags
+ * after regular mount(2).
+ */
+static int init_propagation(struct libmnt_context *cxt)
+{
+ char *name;
+ char *opts = (char *) mnt_fs_get_vfs_options(cxt->fs);
+ size_t namesz;
+ struct libmnt_optmap const *maps[1];
+ int rec_count = 0;
+
+ if (!opts)
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: initialize additional propagation mounts"));
+
+ maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+
+ while (!mnt_optstr_next_option(&opts, &name, &namesz, NULL, NULL)) {
+ const struct libmnt_optmap *ent;
+ struct libmnt_addmount *ad;
+ int rc;
+
+ if (!mnt_optmap_get_entry(maps, 1, name, namesz, &ent) || !ent)
+ continue;
+
+ DBG(CXT, ul_debugobj(cxt, " checking %s", ent->name));
+
+ /* Note that MS_REC may be used for more flags, so we have to keep
+ * track about number of recursive options to keep the MS_REC in the
+ * mountflags if necessary.
+ */
+ if (ent->id & MS_REC)
+ rec_count++;
+
+ if (!(ent->id & MS_PROPAGATION))
+ continue;
+
+ ad = mnt_new_addmount();
+ if (!ad)
+ return -ENOMEM;
+
+ ad->mountflags = ent->id;
+ DBG(CXT, ul_debugobj(cxt, " adding extra mount(2) call for %s", ent->name));
+ rc = mnt_context_append_additional_mount(cxt, ad);
+ if (rc)
+ return rc;
+
+ DBG(CXT, ul_debugobj(cxt, " removing %s from primary mount(2) call", ent->name));
+ cxt->mountflags &= ~ent->id;
+
+ if (ent->id & MS_REC)
+ rec_count--;
+ }
+
+ if (rec_count)
+ cxt->mountflags |= MS_REC;
+
+ return 0;
+}
+
+/*
+ * 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 init_bind_remount(struct libmnt_context *cxt)
+{
+ struct libmnt_addmount *ad;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->mountflags & MS_BIND);
+ assert(!(cxt->mountflags & MS_REMOUNT));
+
+ DBG(CXT, ul_debugobj(cxt, "mount: initialize additional ro,bind mount"));
+
+ ad = mnt_new_addmount();
+ if (!ad)
+ return -ENOMEM;
+
+ ad->mountflags = cxt->mountflags;
+ ad->mountflags |= (MS_REMOUNT | MS_BIND);
+
+ rc = mnt_context_append_additional_mount(cxt, ad);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+#if defined(HAVE_LIBSELINUX) || defined(HAVE_SMACK)
+struct libmnt_optname {
+ const char *name;
+ size_t namesz;
+};
+
+#define DEF_OPTNAME(n) { .name = n, .namesz = sizeof(n) - 1 }
+#define DEF_OPTNAME_LAST { .name = NULL }
+
+static int is_option(const char *name, size_t namesz,
+ const struct libmnt_optname *names)
+{
+ const struct libmnt_optname *p;
+
+ for (p = names; p && p->name; p++) {
+ if (p->namesz == namesz
+ && strncmp(name, p->name, namesz) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* HAVE_LIBSELINUX || HAVE_SMACK */
+
+/*
+ * this has to be called after mnt_context_evaluate_permissions()
+ */
+static int fix_optstr(struct libmnt_context *cxt)
+{
+ int rc = 0;
+ struct libmnt_ns *ns_old;
+ char *next;
+ char *name, *val;
+ size_t namesz, valsz;
+ struct libmnt_fs *fs;
+#ifdef HAVE_LIBSELINUX
+ int se_fix = 0, se_rem = 0;
+ static const struct libmnt_optname selinux_options[] = {
+ DEF_OPTNAME("context"),
+ DEF_OPTNAME("fscontext"),
+ DEF_OPTNAME("defcontext"),
+ DEF_OPTNAME("rootcontext"),
+ DEF_OPTNAME("seclabel"),
+ DEF_OPTNAME_LAST
+ };
+#endif
+#ifdef HAVE_SMACK
+ int sm_rem = 0;
+ static const struct libmnt_optname smack_options[] = {
+ DEF_OPTNAME("smackfsdef"),
+ DEF_OPTNAME("smackfsfloor"),
+ DEF_OPTNAME("smackfshat"),
+ DEF_OPTNAME("smackfsroot"),
+ DEF_OPTNAME("smackfstransmute"),
+ DEF_OPTNAME_LAST
+ };
+#endif
+ assert(cxt);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs || (cxt->flags & MNT_FL_MOUNTOPTS_FIXED))
+ return 0;
+
+ fs = cxt->fs;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: fixing options, current "
+ "vfs: '%s' fs: '%s' user: '%s', optstr: '%s'",
+ fs->vfs_optstr, fs->fs_optstr, fs->user_optstr, fs->optstr));
+
+ /*
+ * The "user" options is our business (so we can modify the option),
+ * the exception is command line for /sbin/mount.<type> helpers. Let's
+ * save the original user=<name> to call the helpers with an unchanged
+ * "user" setting.
+ */
+ if (cxt->user_mountflags & MNT_MS_USER) {
+ if (!mnt_optstr_get_option(fs->user_optstr,
+ "user", &val, &valsz) && val) {
+ cxt->orig_user = strndup(val, valsz);
+ if (!cxt->orig_user) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ }
+ cxt->flags |= MNT_FL_SAVED_USER;
+ }
+
+ /*
+ * Sync mount options with mount flags
+ */
+ DBG(CXT, ul_debugobj(cxt, "mount: fixing vfs optstr"));
+ rc = mnt_optstr_apply_flags(&fs->vfs_optstr, cxt->mountflags,
+ mnt_get_builtin_optmap(MNT_LINUX_MAP));
+ if (rc)
+ goto done;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: fixing user optstr"));
+ rc = mnt_optstr_apply_flags(&fs->user_optstr, cxt->user_mountflags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP));
+ if (rc)
+ goto done;
+
+ if (fs->vfs_optstr && *fs->vfs_optstr == '\0') {
+ free(fs->vfs_optstr);
+ fs->vfs_optstr = NULL;
+ }
+ if (fs->user_optstr && *fs->user_optstr == '\0') {
+ free(fs->user_optstr);
+ fs->user_optstr = NULL;
+ }
+ if (cxt->mountflags & MS_PROPAGATION) {
+ rc = init_propagation(cxt);
+ if (rc)
+ return rc;
+ }
+ if ((cxt->mountflags & MS_BIND)
+ && (cxt->mountflags & MNT_BIND_SETTABLE)
+ && !(cxt->mountflags & MS_REMOUNT)) {
+ rc = init_bind_remount(cxt);
+ if (rc)
+ return rc;
+ }
+
+ next = fs->fs_optstr;
+
+#ifdef HAVE_LIBSELINUX
+ if (!is_selinux_enabled())
+ /* Always remove SELinux garbage if SELinux disabled */
+ se_rem = 1;
+ else if (cxt->mountflags & MS_REMOUNT)
+ /*
+ * 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;
+
+ if (!se_rem) {
+ /* de-duplicate SELinux options */
+ const struct libmnt_optname *p;
+ for (p = selinux_options; p && p->name; p++)
+ mnt_optstr_deduplicate_option(&fs->fs_optstr, p->name);
+ }
+#endif
+#ifdef HAVE_SMACK
+ if (access("/sys/fs/smackfs", F_OK) != 0)
+ sm_rem = 1;
+#endif
+ while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) {
+
+ if (namesz == 3 && !strncmp(name, "uid", 3))
+ rc = mnt_optstr_fix_uid(&fs->fs_optstr, val, valsz, &next);
+ else if (namesz == 3 && !strncmp(name, "gid", 3))
+ rc = mnt_optstr_fix_gid(&fs->fs_optstr, val, valsz, &next);
+#ifdef HAVE_LIBSELINUX
+ else if ((se_rem || se_fix)
+ && is_option(name, namesz, selinux_options)) {
+
+ if (se_rem) {
+ /* remove context= option */
+ next = name;
+ rc = mnt_optstr_remove_option_at(&fs->fs_optstr,
+ name,
+ val ? val + valsz :
+ name + namesz);
+ } else if (se_fix && val && valsz)
+ /* translate selinux contexts */
+ rc = mnt_optstr_fix_secontext(&fs->fs_optstr,
+ val, valsz, &next);
+ }
+#endif
+#ifdef HAVE_SMACK
+ else if (sm_rem && is_option(name, namesz, smack_options)) {
+
+ next = name;
+ rc = mnt_optstr_remove_option_at(&fs->fs_optstr,
+ name,
+ val ? val + valsz : name + namesz);
+ }
+#endif
+ if (rc)
+ goto done;
+ }
+
+
+ if (!rc && mnt_context_is_restricted(cxt) && (cxt->user_mountflags & MNT_MS_USER)) {
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = mnt_optstr_fix_user(&fs->user_optstr);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ }
+
+ /* refresh merged optstr */
+ free(fs->optstr);
+ fs->optstr = NULL;
+ fs->optstr = mnt_fs_strdup_options(fs);
+done:
+ cxt->flags |= MNT_FL_MOUNTOPTS_FIXED;
+
+ DBG(CXT, ul_debugobj(cxt, "fixed options [rc=%d]: "
+ "vfs: '%s' fs: '%s' user: '%s', optstr: '%s'", rc,
+ fs->vfs_optstr, fs->fs_optstr, fs->user_optstr, fs->optstr));
+
+ if (rc)
+ rc = -MNT_ERR_MOUNTOPT;
+ return rc;
+}
+
+/*
+ * Converts the already evaluated and fixed options to the form that is compatible
+ * with /sbin/mount.type helpers.
+ */
+static int generate_helper_optstr(struct libmnt_context *cxt, char **optstr)
+{
+ struct libmnt_optmap const *maps[2];
+ char *next, *name, *val;
+ size_t namesz, valsz;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(optstr);
+
+ DBG(CXT, ul_debugobj(cxt, "mount: generate helper mount options"));
+
+ *optstr = mnt_fs_strdup_options(cxt->fs);
+ if (!*optstr)
+ return -ENOMEM;
+
+ if ((cxt->user_mountflags & MNT_MS_USER) ||
+ (cxt->user_mountflags & MNT_MS_USERS)) {
+ /*
+ * This is unnecessary for real user-mounts as mount.<type>
+ * helpers always have to follow fstab rather than mount
+ * options on the command line.
+ *
+ * However, if you call mount.<type> as root, then the helper follows
+ * the command line. If there is (for example) "user,exec" in fstab,
+ * then we have to manually append the "exec" back to the options
+ * string, because there is nothing like MS_EXEC (we only have
+ * MS_NOEXEC in mount flags and we don't care about the original
+ * mount string in libmount for VFS options).
+ */
+ if (!(cxt->mountflags & MS_NOEXEC))
+ mnt_optstr_append_option(optstr, "exec", NULL);
+ if (!(cxt->mountflags & MS_NOSUID))
+ mnt_optstr_append_option(optstr, "suid", NULL);
+ if (!(cxt->mountflags & MS_NODEV))
+ mnt_optstr_append_option(optstr, "dev", NULL);
+ if (!(cxt->mountflags & MS_NOSYMFOLLOW))
+ mnt_optstr_append_option(optstr, "symfollow", NULL);
+ }
+
+
+ if (cxt->flags & MNT_FL_SAVED_USER)
+ rc = mnt_optstr_set_option(optstr, "user", cxt->orig_user);
+ if (rc)
+ goto err;
+
+ /* remove userspace options with MNT_NOHLPS flag */
+ maps[0] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
+ maps[1] = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+ next = *optstr;
+
+ while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) {
+ const struct libmnt_optmap *ent;
+
+ mnt_optmap_get_entry(maps, 2, name, namesz, &ent);
+ if (ent && ent->id && (ent->mask & MNT_NOHLPS)) {
+ next = name;
+ rc = mnt_optstr_remove_option_at(optstr, name,
+ val ? val + valsz : name + namesz);
+ if (rc)
+ goto err;
+ }
+ }
+
+ return rc;
+err:
+ free(*optstr);
+ *optstr = NULL;
+ 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)
+{
+ unsigned long u_flags = 0;
+
+ assert(cxt);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs)
+ return 0;
+
+ DBG(CXT, ul_debugobj(cxt, "mount: evaluating permissions"));
+
+ mnt_context_get_user_mflags(cxt, &u_flags);
+
+ if (!mnt_context_is_restricted(cxt)) {
+ /*
+ * superuser mount
+ */
+ cxt->user_mountflags &= ~MNT_MS_OWNER;
+ cxt->user_mountflags &= ~MNT_MS_GROUP;
+ } else {
+ /*
+ * user mount
+ */
+ if (!mnt_context_tab_applied(cxt))
+ {
+ DBG(CXT, ul_debugobj(cxt, "perms: fstab not applied, ignore user mount"));
+ return -EPERM;
+ }
+
+ /*
+ * MS_OWNERSECURE and MS_SECURE mount options are already
+ * applied by mnt_optstr_get_flags() in mnt_context_merge_mflags()
+ * if "user" (but no user=<name> !) options is set.
+ *
+ * Let's ignore all user=<name> (if <name> is set) requests.
+ */
+ if (cxt->user_mountflags & MNT_MS_USER) {
+ size_t valsz = 0;
+
+ if (!mnt_optstr_get_option(cxt->fs->user_optstr,
+ "user", NULL, &valsz) && valsz) {
+
+ DBG(CXT, ul_debugobj(cxt, "perms: user=<name> detected, ignore"));
+ cxt->user_mountflags &= ~MNT_MS_USER;
+ }
+ }
+
+ /*
+ * 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 (u_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);
+
+ 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 &&
+ (((u_flags & MNT_MS_OWNER) && getuid() == sb.st_uid) ||
+ ((u_flags & MNT_MS_GROUP) && mnt_in_group(sb.st_gid))))
+
+ cxt->user_mountflags |= MNT_MS_USER;
+
+ if (!cache)
+ free(xsrc);
+ }
+
+ if (!(cxt->user_mountflags & (MNT_MS_USER | MNT_MS_USERS))) {
+ DBG(CXT, ul_debugobj(cxt, "permissions evaluation ends with -EPERMS"));
+ return -EPERM;
+ }
+ }
+
+ 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)
+{
+ char *o = NULL, *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));
+
+ DBG(CXT, ul_debugobj(cxt, "mount: executing helper %s", cxt->helper));
+
+ rc = generate_helper_optstr(cxt, &o);
+ if (rc)
+ return -EINVAL;
+
+ if (ns_tgt->fd != -1
+ && asprintf(&namespace, "/proc/%i/fd/%i",
+ getpid(), ns_tgt->fd) == -1) {
+ free(o);
+ return -ENOMEM;
+ }
+
+ DBG_FLUSH;
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ {
+ const char *args[14], *type;
+ int i = 0;
+
+ if (setgid(getgid()) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (setuid(getuid()) < 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(o);
+ return rc;
+}
+
+static int do_mount_additional(struct libmnt_context *cxt,
+ const char *target,
+ unsigned long flags,
+ int *syserr)
+{
+ struct list_head *p;
+
+ assert(cxt);
+ assert(target);
+
+ if (syserr)
+ *syserr = 0;
+
+ list_for_each(p, &cxt->addmounts) {
+ int rc;
+ struct libmnt_addmount *ad =
+ list_entry(p, struct libmnt_addmount, mounts);
+
+ DBG(CXT, ul_debugobj(cxt, "mount(2) changing flag: 0x%08lx %s",
+ ad->mountflags,
+ ad->mountflags & MS_REC ? " (recursive)" : ""));
+
+ rc = mount("none", target, NULL,
+ ad->mountflags | (flags & MS_SILENT), NULL);
+ if (rc) {
+ if (syserr)
+ *syserr = -errno;
+ DBG(CXT, ul_debugobj(cxt,
+ "mount(2) failed [errno=%d %m]",
+ errno));
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * 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;
+ const char *src, *target, *type;
+ unsigned long flags;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (try_type && !cxt->helper) {
+ rc = mnt_context_prepare_helper(cxt, "mount", try_type);
+ if (rc)
+ return rc;
+ }
+
+ flags = cxt->mountflags;
+ src = mnt_fs_get_srcpath(cxt->fs);
+ target = mnt_fs_get_target(cxt->fs);
+
+ if (cxt->helper) {
+ rc = exec_helper(cxt);
+
+ if (mnt_context_helper_executed(cxt)
+ && mnt_context_get_helper_status(cxt) == 0
+ && !list_empty(&cxt->addmounts)
+ && do_mount_additional(cxt, target, flags, NULL))
+
+ return -MNT_ERR_APPLYFLAGS;
+ return rc;
+ }
+
+ if (!target)
+ return -EINVAL;
+ if (!src) {
+ /* unnecessary, should be already resolved in
+ * mnt_context_prepare_srcpath(), but to be sure... */
+ DBG(CXT, ul_debugobj(cxt, "WARNING: source is NULL -- using \"none\"!"));
+ src = "none";
+ }
+ type = try_type ? : mnt_fs_get_fstype(cxt->fs);
+
+ if (try_type)
+ flags |= MS_SILENT;
+
+ DBG(CXT, ul_debugobj(cxt, "%smount(2) "
+ "[source=%s, target=%s, type=%s, "
+ " mountflags=0x%08lx, mountdata=%s]",
+ mnt_context_is_fake(cxt) ? "(FAKE) " : "",
+ src, target, type,
+ flags, cxt->mountdata ? "yes" : "<none>"));
+
+ if (mnt_context_is_fake(cxt)) {
+ /*
+ * fake
+ */
+ cxt->syscall_status = 0;
+
+ } else if (mnt_context_propagation_only(cxt)) {
+ /*
+ * propagation flags *only*
+ */
+ if (do_mount_additional(cxt, target, flags, &cxt->syscall_status))
+ return -MNT_ERR_APPLYFLAGS;
+ } else {
+ /*
+ * regular mount
+ */
+ if (mount(src, target, type, flags, cxt->mountdata)) {
+ cxt->syscall_status = -errno;
+ DBG(CXT, ul_debugobj(cxt, "mount(2) failed [errno=%d %m]",
+ -cxt->syscall_status));
+ return -cxt->syscall_status;
+ }
+ DBG(CXT, ul_debugobj(cxt, " success"));
+ cxt->syscall_status = 0;
+
+ /*
+ * additional mounts for extra propagation flags
+ */
+ if (!list_empty(&cxt->addmounts)
+ && do_mount_additional(cxt, target, flags, NULL)) {
+
+ /* TODO: call umount? */
+ return -MNT_ERR_APPLYFLAGS;
+ }
+ }
+
+ if (try_type && cxt->update) {
+ struct libmnt_fs *fs = mnt_update_get_fs(cxt->update);
+ if (fs)
+ rc = mnt_fs_set_fstype(fs, try_type);
+ }
+
+ return rc;
+}
+
+/* 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 (!mnt_context_get_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++) {
+ rc = do_mount(cxt, *fp);
+ if (mnt_context_get_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;
+}
+
+/**
+ * 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)
+ 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 = mnt_context_prepare_target(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_helper(cxt, "mount", NULL);
+ 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;
+ 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_MOUNTFLAGS_MERGED));
+ assert((cxt->flags & MNT_FL_PREPARED));
+ assert((cxt->action == MNT_ACT_MOUNT));
+
+ DBG(CXT, ul_debugobj(cxt, "mount: do mount"));
+
+ if (!(cxt->flags & MNT_FL_MOUNTDATA))
+ cxt->mountdata = (char *) mnt_fs_get_fs_options(cxt->fs);
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ 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);
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ if (mnt_context_get_status(cxt)
+ && !mnt_context_is_fake(cxt)
+ && !cxt->helper
+ && mnt_context_mtab_writable(cxt)) {
+
+ int is_rdonly = -1;
+
+ DBG(CXT, ul_debugobj(cxt, "checking for RDONLY mismatch"));
+
+ /*
+ * Mounted by mount(2), do some post-mount checks
+ *
+ * Kernel can be used to use MS_RDONLY for bind mounts, but the
+ * read-only request could be silently ignored. Check it to
+ * avoid 'ro' in mtab and 'rw' in /proc/mounts.
+ */
+ if ((cxt->mountflags & MS_BIND)
+ && (cxt->mountflags & MS_RDONLY)) {
+
+ if (is_rdonly < 0)
+ is_rdonly = mnt_is_readonly(mnt_context_get_target(cxt));
+ if (!is_rdonly)
+ mnt_context_set_mflags(cxt, cxt->mountflags & ~MS_RDONLY);
+ }
+
+
+ /* Kernel can silently add MS_RDONLY flag when mounting file
+ * system that does not have write support. Check this to avoid
+ * 'ro' in /proc/mounts and 'rw' in mtab.
+ */
+ if (!(cxt->mountflags & (MS_RDONLY | MS_MOVE))
+ && !mnt_context_propagation_only(cxt)) {
+
+ if (is_rdonly < 0)
+ is_rdonly = mnt_is_readonly(mnt_context_get_target(cxt));
+ if (is_rdonly)
+ mnt_context_set_mflags(cxt, cxt->mountflags | MS_RDONLY);
+ }
+ }
+#endif
+
+ /* Cleanup will be immediate on failure, and deferred to umount on success */
+ if (mnt_context_is_veritydev(cxt))
+ mnt_context_deferred_delete_veritydev(cxt);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ 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_mtab(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);
+ assert(cxt->syscall_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 (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ 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, *mtab;
+ const char *o, *tgt;
+ int rc, mounted = 0;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ 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)
+ 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 mtab */
+ mtab = cxt->mtab;
+ cxt->mtab = NULL;
+ mnt_reset_context(cxt);
+ cxt->mtab = mtab;
+
+ 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 *mtab;
+ const char *tgt;
+ int rc;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ if (rc)
+ return rc;
+
+ rc = mnt_table_next_fs(mtab, 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 mtab */
+ cxt->mtab = NULL;
+ mnt_reset_context(cxt);
+ cxt->mtab = mtab;
+
+ 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_mtab(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 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)
+ 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;
+ 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 mtab processing).
+ */
+ 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 < 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) errors
+ */
+ syserr = mnt_context_get_syscall_errno(cxt);
+
+
+ switch(syserr) {
+ case EPERM:
+ if (!buf)
+ break;
+ if (geteuid() == 0) {
+ if (mnt_stat_mountpoint(tgt, &st) || !S_ISDIR(st.st_mode))
+ snprintf(buf, bufsz, _("mount point is not a directory"));
+ else
+ snprintf(buf, bufsz, _("permission denied"));
+ } 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_lstat_mountpoint(tgt, &st)) {
+ if (buf)
+ snprintf(buf, bufsz, _("mount point does not exist"));
+ } else if (tgt && mnt_stat_mountpoint(tgt, &st)) {
+ if (buf)
+ snprintf(buf, bufsz, _("mount point is a symbolic link to nowhere"));
+ } else if (src && stat(src, &st)) {
+ 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_stat_mountpoint(tgt, &st) || ! S_ISDIR(st.st_mode)) {
+ if (buf)
+ snprintf(buf, bufsz, _("mount point is not a directory"));
+ } else if (src && stat(src, &st) && errno == ENOTDIR) {
+ 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 && 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 && 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;
+}
+
diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c
new file mode 100644
index 0000000..df9a2eb
--- /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/mtab */
+static int __mtab_find_umount_fs(struct libmnt_context *cxt,
+ const char *tgt,
+ struct libmnt_fs **pfs)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+ struct libmnt_table *mtab = 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_mtab_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
+ * (non-writable mtab).
+ */
+ if (mnt_context_is_nocanonicalize(cxt) &&
+ !mnt_context_mtab_writable(cxt) && *tgt == '/')
+ rc = mnt_context_get_mtab_for_target(cxt, &mtab, tgt);
+ else
+ rc = mnt_context_get_mtab(cxt, &mtab);
+
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "umount: failed to read mtab"));
+ return rc;
+ }
+
+ if (mnt_table_get_nents(mtab) == 0) {
+ DBG(CXT, ul_debugobj(cxt, "umount: mtab 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(mtab, 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(mtab, tgt, MNT_ITER_BACKWARD);
+
+ if (fs) {
+ struct libmnt_fs *fs1 = mnt_table_find_target(mtab,
+ mnt_fs_get_target(fs),
+ MNT_ITER_BACKWARD);
+ if (!fs1) {
+ DBG(CXT, ul_debugobj(cxt, "mtab 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_stat_mountpoint(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/mtab.
+ */
+ return __mtab_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_mtab() 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;
+
+ 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_mtab_writable(cxt)
+ || mnt_context_is_force(cxt)
+ || mnt_context_is_lazy(cxt)
+ || mnt_context_is_nocanonicalize(cxt)
+ || mnt_context_is_loopdel(cxt)
+ || mnt_stat_mountpoint(tgt, &st) != 0 || !S_ISDIR(st.st_mode)
+ || has_utab_entry(cxt, tgt))
+ return 1; /* not found */
+
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ DBG(CXT, ul_debugobj(cxt, " umount: disabling mtab"));
+ mnt_context_disable_mtab(cxt, TRUE);
+
+ 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,
+ " 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;
+
+ DBG(CXT, ul_debugobj(cxt, " lookup by mountinfo"));
+
+ /* search */
+ rc = __mtab_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, " mtab applied"));
+ }
+
+ cxt->flags |= MNT_FL_TAB_APPLIED;
+ return 0;
+}
+
+/* This finction search 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)
+ return rc;
+
+ /* get complete fs from fs entry from mountinfo */
+ rc = lookup_umount_fs_by_mountinfo(cxt, tgt);
+ if (rc <= 0)
+ return rc;
+
+ DBG(CXT, ul_debugobj(cxt, " cannot find '%s'", tgt));
+ return 0; /* this is correct! */
+}
+
+/* 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);
+}
+
+static int prepare_helper_from_options(struct libmnt_context *cxt,
+ const char *name)
+{
+ char *suffix = NULL;
+ const char *opts;
+ size_t valsz;
+ int rc;
+
+ if (mnt_context_is_nohelpers(cxt))
+ return 0;
+
+ opts = mnt_fs_get_user_options(cxt->fs);
+ if (!opts)
+ return 0;
+
+ if (mnt_optstr_get_option(opts, name, &suffix, &valsz))
+ return 0;
+
+ suffix = strndup(suffix, valsz);
+ if (!suffix)
+ return -ENOMEM;
+
+ DBG(CXT, ul_debugobj(cxt, "umount: umount.%s %s requested", suffix, name));
+
+ rc = mnt_context_prepare_helper(cxt, "umount", suffix);
+ free(suffix);
+
+ return rc;
+}
+
+static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv)
+{
+ struct libmnt_ns *ns_old;
+ const char *type = mnt_fs_get_fstype(cxt->fs);
+ const char *optstr;
+ char *user_id = NULL;
+ size_t sz;
+ uid_t uid;
+ char uidstr[sizeof(stringify_value(ULONG_MAX))];
+
+ *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;
+
+ /* get user_id= from mount table */
+ optstr = mnt_fs_get_fs_options(cxt->fs);
+ if (!optstr)
+ return 0;
+
+ if (mnt_optstr_get_option(optstr, "user_id", &user_id, &sz) != 0)
+ return 0;
+
+ if (sz == 0 || user_id == NULL)
+ 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;
+ }
+
+ snprintf(uidstr, sizeof(uidstr), "%lu", (unsigned long) uid);
+ return strncmp(user_id, uidstr, sz) == 0;
+}
+
+/*
+ * Note that cxt->fs contains relevant mtab entry!
+ */
+static int evaluate_permissions(struct libmnt_context *cxt)
+{
+ struct libmnt_table *fstab;
+ unsigned long u_flags = 0;
+ 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 mtab and you are not root",
+ mnt_fs_get_target(cxt->fs)));
+ goto eperm;
+ }
+
+ if (cxt->user_mountflags & MNT_MS_UHELPER) {
+ /* on uhelper= mount option based helper */
+ rc = prepare_helper_from_options(cxt, "uhelper");
+ if (rc)
+ return rc;
+ if (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 mtab -- 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 mtab */
+ 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: mtab 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 mtab).
+ */
+ optstr = mnt_fs_get_user_options(fs); /* FSTAB mount options! */
+ if (!optstr)
+ goto eperm;
+
+ if (mnt_optstr_get_flags(optstr, &u_flags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP)))
+ goto eperm;
+
+ if (u_flags & MNT_MS_USERS) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: promiscuous setting ('users') in fstab"));
+ return 0;
+ }
+ /*
+ * Check user=<username> setting from mtab if there is a user, owner or
+ * group option in /etc/fstab
+ */
+ if (u_flags & (MNT_MS_USER | MNT_MS_OWNER | MNT_MS_GROUP)) {
+
+ char *curr_user;
+ char *mtab_user = NULL;
+ size_t sz;
+ struct libmnt_ns *ns_old;
+
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: checking user=<username> from mtab"));
+
+ 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 options from mtab */
+ optstr = mnt_fs_get_user_options(cxt->fs);
+ if (optstr && !mnt_optstr_get_option(optstr,
+ "user", &mtab_user, &sz) && sz)
+ ok = !strncmp(curr_user, mtab_user, sz);
+
+ 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 (setgid(getgid()) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (setuid(getuid()) < 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;
+ }
+
+ 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) {
+
+ mnt_context_set_mflags(cxt, (cxt->mountflags |
+ MS_REMOUNT | MS_RDONLY));
+ 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;
+ 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 && !cxt->helper) {
+
+ if (cxt->user_mountflags & MNT_MS_HELPER)
+ /* on helper= mount option based helper */
+ rc = prepare_helper_from_options(cxt, "helper");
+
+ if (!rc && !cxt->helper)
+ /* on fstype based helper */
+ rc = mnt_context_prepare_helper(cxt, "umount", NULL);
+ }
+
+ if (!rc && (cxt->user_mountflags & MNT_MS_LOOP))
+ /* loop option explicitly specified in mtab, 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 mtab stuff if remount rather than
+ * umount has been performed
+ */
+ if (mnt_context_is_loopdel(cxt)
+ && !(cxt->mountflags & MS_REMOUNT))
+ rc = mnt_context_delete_loopdev(cxt);
+
+ if (!mnt_context_is_nomtab(cxt)
+ && mnt_context_get_status(cxt)
+ && !cxt->helper
+ && mnt_context_is_rdonly_umount(cxt)
+ && (cxt->mountflags & MS_REMOUNT)) {
+
+ /* use "remount" instead of "umount" in /etc/mtab */
+ if (!rc && cxt->update && mnt_context_mtab_writable(cxt))
+ rc = mnt_update_set_fs(cxt->update,
+ cxt->mountflags, NULL, cxt->fs);
+ }
+ }
+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 mtab (as returned by
+ * mnt_context_get_mtab()).
+ *
+ * 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 *mtab;
+ const char *tgt;
+ int rc;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ cxt->mtab = NULL; /* do not reset mtab */
+ mnt_reset_context(cxt);
+
+ if (rc)
+ return rc;
+
+ cxt->mtab = mtab;
+
+ do {
+ rc = mnt_table_next_fs(mtab, 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 mtab */
+ 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 mtab 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/context_veritydev.c b/libmount/src/context_veritydev.c
new file mode 100644
index 0000000..d663456
--- /dev/null
+++ b/libmount/src/context_veritydev.c
@@ -0,0 +1,520 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2019 Microsoft Corporation
+ *
+ * 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.
+ */
+
+#include "mountP.h"
+
+#if defined(HAVE_CRYPTSETUP)
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+#include <dlfcn.h>
+#endif
+#include <libcryptsetup.h>
+#include "path.h"
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+static void *get_symbol(struct libmnt_context *cxt, void *dl, const char *name, int *rc)
+{
+ char *dl_error = NULL;
+ void *sym = dlsym(dl, name);
+
+ *rc = 0;
+ if ((dl_error = dlerror()) == NULL)
+ return sym;
+
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen symbol %s: %s", name, dl_error));
+ *rc = -ENOTSUP;
+
+ return NULL;
+}
+#endif
+
+/* 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);
+ bytes[i] = strtoul(buf, &endp, 16);
+ if (endp != &buf[2]) {
+ free(bytes);
+ return -EINVAL;
+ }
+ }
+ *result = bytes;
+ return i;
+}
+
+
+int mnt_context_setup_veritydev(struct libmnt_context *cxt)
+{
+ const char *backing_file, *optstr;
+ char *val = NULL, *key = NULL, *root_hash_binary = NULL, *mapper_device = NULL,
+ *mapper_device_full = NULL, *backing_file_basename = NULL, *root_hash = NULL,
+ *hash_device = NULL, *root_hash_file = NULL, *fec_device = NULL, *hash_sig = NULL;
+ size_t len, 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;
+ struct stat hash_sig_st;
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ /* To avoid linking libmount to libcryptsetup, and keep the default dependencies list down, use dlopen */
+ void *dl = NULL;
+ int (*sym_crypt_init_data_device)(struct crypt_device **, const char *, const char *) = NULL;
+ int (*sym_crypt_load)(struct crypt_device *, const char *, void *) = NULL;
+ int (*sym_crypt_get_volume_key_size)(struct crypt_device *) = NULL;
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ int (*sym_crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t) = NULL;
+#endif
+ int (*sym_crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t) = NULL;
+ void (*sym_crypt_free)(struct crypt_device *) = NULL;
+ int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = NULL;
+ int (*sym_crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *) = NULL;
+ int (*sym_crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t) = NULL;
+#else
+ int (*sym_crypt_init_data_device)(struct crypt_device **, const char *, const char *) = &crypt_init_data_device;
+ int (*sym_crypt_load)(struct crypt_device *, const char *, void *) = &crypt_load;
+ int (*sym_crypt_get_volume_key_size)(struct crypt_device *) = &crypt_get_volume_key_size;
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ int (*sym_crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t) = &crypt_activate_by_signed_key;
+#endif
+ int (*sym_crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t) = &crypt_activate_by_volume_key;
+ void (*sym_crypt_free)(struct crypt_device *) = &crypt_free;
+ int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = &crypt_init_by_name;
+ int (*sym_crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *) = &crypt_get_verity_info;
+ int (*sym_crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t) = &crypt_volume_key_get;
+#endif
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ /* dm-verity volumes are read-only, and mount will fail if not set */
+ mnt_context_set_mflags(cxt, (cxt->mountflags | MS_RDONLY));
+
+ backing_file = mnt_fs_get_srcpath(cxt->fs);
+ if (!backing_file)
+ return -EINVAL;
+
+ /* To avoid clashes, prefix libmnt_ to all mapper devices */
+ backing_file_basename = basename(backing_file);
+ mapper_device = calloc(strlen(backing_file_basename) + strlen("libmnt_") + 1, sizeof(char));
+ if (!mapper_device)
+ return -ENOMEM;
+ strcat(mapper_device, "libmnt_");
+ strcat(mapper_device, backing_file_basename);
+
+ DBG(VERITY, ul_debugobj(cxt, "trying to setup verity device for %s", backing_file));
+
+ optstr = mnt_fs_get_user_options(cxt->fs);
+
+ /*
+ * verity.hashdevice=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_DEVICE) &&
+ mnt_optstr_get_option(optstr, "verity.hashdevice", &val, &len) == 0 && val) {
+ hash_device = strndup(val, len);
+ rc = hash_device ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.roothash=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH) &&
+ mnt_optstr_get_option(optstr, "verity.roothash", &val, &len) == 0 && val) {
+ root_hash = strndup(val, len);
+ rc = root_hash ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.hashoffset=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_OFFSET) &&
+ mnt_optstr_get_option(optstr, "verity.hashoffset", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &offset);
+ if (rc) {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.hashoffset="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * verity.roothashfile=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH_FILE) &&
+ mnt_optstr_get_option(optstr, "verity.roothashfile", &val, &len) == 0 && val) {
+ root_hash_file = strndup(val, len);
+ rc = root_hash_file ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.fecdevice=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_FEC_DEVICE) &&
+ mnt_optstr_get_option(optstr, "verity.fecdevice", &val, &len) == 0 && val) {
+ fec_device = strndup(val, len);
+ rc = fec_device ? 0 : -ENOMEM;
+ }
+
+ /*
+ * verity.fecoffset=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_FEC_OFFSET) &&
+ mnt_optstr_get_option(optstr, "verity.fecoffset", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &fec_offset);
+ if (rc) {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.fecoffset="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * verity.fecroots=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_FEC_ROOTS) &&
+ mnt_optstr_get_option(optstr, "verity.fecroots", &val, &len) == 0) {
+ rc = mnt_parse_offset(val, len, &fec_roots);
+ if (rc) {
+ DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.fecroots="));
+ rc = -MNT_ERR_MOUNTOPT;
+ }
+ }
+
+ /*
+ * verity.roothashsig=
+ */
+ if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH_SIG) &&
+ mnt_optstr_get_option(optstr, "verity.roothashsig", &val, &len) == 0 && val) {
+ rc = ul_path_stat(NULL, &hash_sig_st, val);
+ if (rc == 0)
+ rc = !S_ISREG(hash_sig_st.st_mode) || !hash_sig_st.st_size ? -EINVAL : 0;
+ 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, val);
+ rc = rc < (int)hash_sig_size ? -1 : 0;
+ }
+ }
+
+ if (!rc && root_hash && root_hash_file) {
+ DBG(VERITY, ul_debugobj(cxt, "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(VERITY, ul_debugobj(cxt, "verity.hashdevice and one of verity.roothash or verity.roothashfile are mandatory"));
+ rc = -EINVAL;
+ }
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ if (rc == 0) {
+ int dl_flags = RTLD_LAZY | RTLD_LOCAL;
+ /* glibc extension: mnt_context_deferred_delete_veritydev is called immediately after, don't unload on dl_close */
+#ifdef RTLD_NODELETE
+ dl_flags |= RTLD_NODELETE;
+#endif
+ /* glibc extension: might help to avoid further symbols clashes */
+#ifdef RTLD_DEEPBIND
+ dl_flags |= RTLD_DEEPBIND;
+#endif
+ dl = dlopen("libcryptsetup.so.12", dl_flags);
+ if (!dl) {
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen libcryptsetup"));
+ rc = -ENOTSUP;
+ }
+ }
+
+ /* clear errors first, then load all the libcryptsetup symbols */
+ dlerror();
+
+ if (rc == 0)
+ *(void **)(&sym_crypt_init_data_device) = get_symbol(cxt, dl, "crypt_init_data_device", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_load) = get_symbol(cxt, dl, "crypt_load", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_get_volume_key_size) = get_symbol(cxt, dl, "crypt_get_volume_key_size", &rc);
+#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
+ if (rc == 0)
+ *(void **)(&sym_crypt_activate_by_signed_key) = get_symbol(cxt, dl, "crypt_activate_by_signed_key", &rc);
+#endif
+ if (rc == 0)
+ *(void **)(&sym_crypt_activate_by_volume_key) = get_symbol(cxt, dl, "crypt_activate_by_volume_key", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_free) = get_symbol(cxt, dl, "crypt_free", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_init_by_name) = get_symbol(cxt, dl, "crypt_init_by_name", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_get_verity_info) = get_symbol(cxt, dl, "crypt_get_verity_info", &rc);
+ if (rc == 0)
+ *(void **)(&sym_crypt_volume_key_get) = get_symbol(cxt, dl, "crypt_volume_key_get", &rc);
+#endif
+ if (rc)
+ goto done;
+
+ rc = (*sym_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 = (*sym_crypt_load)(crypt_dev, CRYPT_VERITY, &crypt_params);
+ if (rc < 0)
+ goto done;
+
+ hash_size = (*sym_crypt_get_volume_key_size)(crypt_dev);
+ if (crypt_hex_to_bytes(root_hash, &root_hash_binary) != hash_size) {
+ DBG(VERITY, ul_debugobj(cxt, "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 = (*sym_crypt_activate_by_signed_key)(crypt_dev, mapper_device, root_hash_binary, hash_size,
+ hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY);
+#else
+ rc = -EINVAL;
+ DBG(VERITY, ul_debugobj(cxt, "verity.roothashsig=%s passed but libcryptsetup does not provide crypt_activate_by_signed_key()", hash_sig));
+#endif
+ } else
+ rc = (*sym_crypt_activate_by_volume_key)(crypt_dev, mapper_device, root_hash_binary, hash_size,
+ CRYPT_ACTIVATE_READONLY);
+ /*
+ * 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(VERITY, ul_debugobj(cxt, "%s already in use as /dev/mapper/%s", backing_file, mapper_device));
+ (*sym_crypt_free)(crypt_dev);
+ rc = (*sym_crypt_init_by_name)(&crypt_dev, mapper_device);
+ if (!rc) {
+ rc = (*sym_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 = (*sym_crypt_volume_key_get)(crypt_dev, CRYPT_ANY_SLOT, key, &keysize, NULL, 0);
+ }
+ if (!rc) {
+ DBG(VERITY, ul_debugobj(cxt, "comparing root hash of existing device with %s", root_hash));
+ if (memcmp(key, root_hash_binary, hash_size)) {
+ DBG(VERITY, ul_debugobj(cxt, "existing device's hash does not match with %s", root_hash));
+ rc = -EINVAL;
+ goto done;
+ }
+ } else {
+ DBG(VERITY, ul_debugobj(cxt, "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(VERITY, ul_debugobj(cxt, "existing device and new mount have to either be both opened with signature or both without"));
+ goto done;
+ }
+#endif
+ DBG(VERITY, ul_debugobj(cxt, "root hash of %s matches %s, reusing device", mapper_device, root_hash));
+ }
+ }
+
+ if (!rc) {
+ cxt->flags |= MNT_FL_VERITYDEV_READY;
+ mapper_device_full = calloc(strlen(mapper_device) + strlen("/dev/mapper/") + 1, sizeof(char));
+ if (!mapper_device_full)
+ rc = -ENOMEM;
+ else {
+ strcat(mapper_device_full, "/dev/mapper/");
+ strcat(mapper_device_full, mapper_device);
+ rc = mnt_fs_set_source(cxt->fs, mapper_device_full);
+ }
+ }
+
+done:
+ if (sym_crypt_free)
+ (*sym_crypt_free)(crypt_dev);
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ if (dl)
+ dlclose(dl);
+#endif
+ free(root_hash_binary);
+ free(mapper_device_full);
+ free(mapper_device);
+ free(hash_device);
+ free(root_hash);
+ free(root_hash_file);
+ free(fec_device);
+ free(hash_sig);
+ free(key);
+ return rc;
+}
+
+int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt)
+{
+ const char *src;
+ struct crypt_device *crypt_dev = NULL;
+ /* If mounting failed delete immediately, otherwise setup auto cleanup for user umount */
+ uint32_t flags = mnt_context_get_status(cxt) ? CRYPT_DEACTIVATE_DEFERRED : 0;
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ void *dl = NULL;
+ int dl_flags = RTLD_LAZY | RTLD_LOCAL;
+ /* glibc extension: might help to avoid further symbols clashes */
+#ifdef RTLD_DEEPBIND
+ dl_flags |= RTLD_DEEPBIND;
+#endif
+ int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = NULL;
+ int (*sym_crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t) = NULL;
+ void (*sym_crypt_free)(struct crypt_device *) = NULL;
+#else
+ int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = &crypt_init_by_name;
+ int (*sym_crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t) = &crypt_deactivate_by_name;
+ void (*sym_crypt_free)(struct crypt_device *) = &crypt_free;
+#endif
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ if (!(cxt->flags & MNT_FL_VERITYDEV_READY))
+ return 0;
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return -EINVAL;
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ dl = dlopen("libcryptsetup.so.12", dl_flags);
+ if (!dl) {
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen libcryptsetup"));
+ return -ENOTSUP;
+ }
+
+ /* clear errors first */
+ dlerror();
+
+ if (!rc)
+ *(void **)(&sym_crypt_init_by_name) = get_symbol(cxt, dl, "crypt_init_by_name", &rc);
+ if (!rc)
+ *(void **)(&sym_crypt_deactivate_by_name) = get_symbol(cxt, dl, "crypt_deactivate_by_name", &rc);
+ if (!rc)
+ *(void **)(&sym_crypt_free) = get_symbol(cxt, dl, "crypt_free", &rc);
+#endif
+ if (!rc) {
+ rc = (*sym_crypt_init_by_name)(&crypt_dev, src);
+ if (!rc) {
+ rc = (*sym_crypt_deactivate_by_name)(crypt_dev, src, flags);
+ if (!rc)
+ cxt->flags &= ~MNT_FL_VERITYDEV_READY;
+ }
+
+ (*sym_crypt_free)(crypt_dev);
+ }
+
+#ifdef CRYPTSETUP_VIA_DLOPEN
+ dlclose(dl);
+#endif
+ DBG(VERITY, ul_debugobj(cxt, "deleted [rc=%d]", rc));
+ return rc;
+}
+
+#else
+
+int mnt_context_setup_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__)))
+{
+ return 0;
+}
+
+int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__)))
+{
+ return 0;
+}
+#endif
+
+int mnt_context_is_veritydev(struct libmnt_context *cxt)
+{
+ const char *src;
+
+ assert(cxt);
+
+ /* The mount flags have to be merged, otherwise we have to use
+ * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!cxt->fs)
+ return 0;
+ src = mnt_fs_get_srcpath(cxt->fs);
+ if (!src)
+ return 0; /* backing file not set */
+
+ if (cxt->user_mountflags & (MNT_MS_HASH_DEVICE |
+ MNT_MS_ROOT_HASH |
+ MNT_MS_HASH_OFFSET)) {
+#ifndef HAVE_CRYPTSETUP
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but libmount built without libcryptsetup"));
+ return -ENOTSUP;
+#else
+ DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected"));
+ return 1;
+#endif
+ }
+
+ if (!strncmp(src, "/dev/mapper/libmnt_", strlen("/dev/mapper/libmnt_"))) {
+#ifndef HAVE_CRYPTSETUP
+ DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device but libmount built without libcryptsetup"));
+ return -ENOTSUP;
+#else
+ DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device"));
+ return 1;
+#endif
+ }
+
+ return 0;
+}
diff --git a/libmount/src/fs.c b/libmount/src/fs.c
new file mode 100644
index 0000000..52f937a
--- /dev/null
+++ b/libmount/src/fs.c
@@ -0,0 +1,1624 @@
+/* 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, mtab, 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);
+
+ 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 do NOT overwrite (replace) the string in @new, the string in
+ * the @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);
+}
+
+/**
+ * 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.
+ *
+ * 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(const struct libmnt_fs *fs)
+{
+ struct libmnt_fs *n = mnt_new_fs();
+
+ assert(fs);
+ if (!n)
+ return NULL;
+
+ 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);
+}
+
+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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+
+ 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)
+{
+ 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 (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;
+
+ 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;
+
+ 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)
+{
+ return fs ? fs->fs_optstr : NULL;
+}
+
+/**
+ * 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)
+{
+ return fs ? fs->vfs_optstr : NULL;
+}
+
+/**
+ * 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)
+{
+ return fs ? fs->user_optstr : NULL;
+}
+
+/**
+ * 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->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 append_string(&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;
+ 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/init.c b/libmount/src/init.c
new file mode 100644
index 0000000..2410fcc
--- /dev/null
+++ b/libmount/src/init.c
@@ -0,0 +1,106 @@
+/* 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" },
+ { "locks", MNT_DEBUG_LOCKS, "mtab and utab locking" },
+ { "loop", MNT_DEBUG_LOOP, "loop devices routines" },
+ { "options", MNT_DEBUG_OPTIONS, "mount options parsing" },
+ { "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%04x", 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..e6710ae
--- /dev/null
+++ b/libmount/src/libmount.h.in
@@ -0,0 +1,1016 @@
+/* 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 (e.g. /etc/mtab)
+ */
+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 */
+
+/**
+ * libmnt_fs:
+ *
+ * Parsed fstab/mtab/mountinfo entry
+ */
+struct libmnt_fs;
+
+/**
+ * libmnt_table:
+ *
+ * List of struct libmnt_fs entries (parsed fstab/mtab/mountinfo)
+ */
+struct libmnt_table;
+
+/**
+ * libmnt_update
+ *
+ * /etc/mtab or 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
+ */
+#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 mtab/utab or so.
+ */
+#define MNT_ERR_LOCK 5008
+/**
+ * MNT_ERR_NAMESPACE:
+ *
+ * failed to switch namespace
+ */
+#define MNT_ERR_NAMESPACE 5009
+
+
+/*
+ * 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, ... mtab/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 *mtab);
+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 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 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_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 (or mtab), see mnt_context_set_optsmode().
+ */
+enum {
+ MNT_OMODE_IGNORE = (1 << 1), /* ignore mtab/fstab options */
+ MNT_OMODE_APPEND = (1 << 2), /* append mtab/fstab options to existing options */
+ MNT_OMODE_PREPEND = (1 << 3), /* prepend mtab/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 mtab/fstab options */
+
+ MNT_OMODE_FSTAB = (1 << 10), /* read from fstab */
+ MNT_OMODE_MTAB = (1 << 11), /* read from mtab if fstab not enabled or failed */
+ MNT_OMODE_NOTAB = (1 << 12), /* do not read fstab/mtab 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_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_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_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)
+
+/*
+ * 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..792d117
--- /dev/null
+++ b/libmount/src/libmount.sym
@@ -0,0 +1,358 @@
+/*
+ * 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;
diff --git a/libmount/src/lock.c b/libmount/src/lock.c
new file mode 100644
index 0000000..cc5340d
--- /dev/null
+++ b/libmount/src/lock.c
@@ -0,0 +1,732 @@
+/* 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 /etc/mtab or another libmount files
+ *
+ * The mtab lock is backwards compatible with the standard linux /etc/mtab
+ * locking. Note, it's necessary to use the same locking schema in all
+ * applications that access the file.
+ */
+#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~) */
+ char *linkfile; /* path to link file (e.g. /etc/mtab~.<id>) */
+ int lockfile_fd; /* lock file descriptor */
+
+ unsigned int locked :1, /* do we own the lock? */
+ sigblock :1, /* block signals when locked */
+ simplelock :1; /* use flock rather than normal mtab lock */
+
+ sigset_t oldsigmask;
+};
+
+
+/**
+ * mnt_new_lock:
+ * @datafile: the file that should be covered by the lock
+ * @id: unique linkfile identifier or 0 (default is getpid())
+ *
+ * Returns: newly allocated lock handler or NULL on case of error.
+ */
+struct libmnt_lock *mnt_new_lock(const char *datafile, pid_t id)
+{
+ struct libmnt_lock *ml = NULL;
+ char *lo = NULL, *ln = NULL;
+ size_t losz;
+
+ if (!datafile)
+ return NULL;
+
+ /* for flock we use "foo.lock, for mtab "foo~"
+ */
+ losz = strlen(datafile) + sizeof(".lock");
+ lo = malloc(losz);
+ if (!lo)
+ goto err;
+
+ /* default is mtab~ lock */
+ snprintf(lo, losz, "%s~", datafile);
+
+ if (asprintf(&ln, "%s~.%d", datafile, id ? : getpid()) == -1) {
+ ln = NULL;
+ goto err;
+ }
+ ml = calloc(1, sizeof(*ml) );
+ if (!ml)
+ goto err;
+
+ ml->lockfile_fd = -1;
+ ml->linkfile = ln;
+ ml->lockfile = lo;
+
+ DBG(LOCKS, ul_debugobj(ml, "alloc: default linkfile=%s, lockfile=%s", ln, lo));
+ return ml;
+err:
+ free(lo);
+ free(ln);
+ 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->linkfile);
+ 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;
+}
+
+/* don't export this to API
+ */
+int mnt_lock_use_simplelock(struct libmnt_lock *ml, int enable)
+{
+ size_t sz;
+
+ if (!ml)
+ return -EINVAL;
+
+ assert(ml->lockfile);
+
+ DBG(LOCKS, ul_debugobj(ml, "flock: %s", enable ? "ENABLED" : "DISABLED"));
+ ml->simplelock = enable ? 1 : 0;
+
+ sz = strlen(ml->lockfile);
+ assert(sz);
+
+ if (sz < 1)
+ return -EINVAL;
+
+ /* Change lock name:
+ *
+ * flock: "<name>.lock"
+ * mtab lock: "<name>~"
+ */
+ if (ml->simplelock && endswith(ml->lockfile, "~"))
+ memcpy(ml->lockfile + sz - 1, ".lock", 6);
+
+ else if (!ml->simplelock && endswith(ml->lockfile, ".lock"))
+ memcpy(ml->lockfile + sz - 5, "~", 2);
+
+ DBG(LOCKS, ul_debugobj(ml, "new lock filename: '%s'", ml->lockfile));
+ return 0;
+}
+
+/*
+ * Returns path to lockfile.
+ */
+static const char *mnt_lock_get_lockfile(struct libmnt_lock *ml)
+{
+ return ml ? ml->lockfile : NULL;
+}
+
+/*
+ * Note that the filename is generated by mnt_new_lock() and depends on
+ * getpid() or 'id' argument of the mnt_new_lock() function.
+ *
+ * Returns: unique (per process/thread) path to linkfile.
+ */
+static const char *mnt_lock_get_linkfile(struct libmnt_lock *ml)
+{
+ return ml ? ml->linkfile : NULL;
+}
+
+/*
+ * Simple flocking
+ */
+static void unlock_simplelock(struct libmnt_lock *ml)
+{
+ assert(ml);
+ assert(ml->simplelock);
+
+ 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);
+ assert(ml->simplelock);
+
+ 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;
+}
+
+/*
+ * traditional mtab locking
+ */
+
+static void mnt_lockalrm_handler(int sig __attribute__((__unused__)))
+{
+ /* do nothing, say nothing, be nothing */
+}
+
+/*
+ * Waits for F_SETLKW, unfortunately we have to use SIGALRM here to interrupt
+ * fcntl() to avoid neverending waiting.
+ *
+ * Returns: 0 on success, 1 on timeout, -errno on error.
+ */
+static int mnt_wait_mtab_lock(struct libmnt_lock *ml, struct flock *fl, time_t maxtime)
+{
+ struct timeval now;
+ struct sigaction sa, osa;
+ int ret = 0;
+
+ gettime_monotonic(&now);
+ DBG(LOCKS, ul_debugobj(ml, "(%d) waiting for F_SETLKW (now=%lu, maxtime=%lu, diff=%lu)",
+ getpid(),
+ (unsigned long) now.tv_sec,
+ (unsigned long) maxtime,
+ (unsigned long) (maxtime - now.tv_sec)));
+
+ if (now.tv_sec >= maxtime)
+ return 1; /* timeout */
+
+ /* setup ALARM handler -- we don't want to wait forever */
+ sa.sa_flags = 0;
+ sa.sa_handler = mnt_lockalrm_handler;
+ sigfillset (&sa.sa_mask);
+
+ sigaction(SIGALRM, &sa, &osa);
+
+
+ alarm(maxtime - now.tv_sec);
+ if (fcntl(ml->lockfile_fd, F_SETLKW, fl) == -1)
+ ret = errno == EINTR ? 1 : -errno;
+ alarm(0);
+
+ /* restore old sigaction */
+ sigaction(SIGALRM, &osa, NULL);
+
+ DBG(LOCKS, ul_debugobj(ml, "(%d) leaving mnt_wait_setlkw(), rc=%d",
+ getpid(), ret));
+ return ret;
+}
+
+/*
+ * Create the mtab lock file.
+ *
+ * The old code here used flock on a lock file /etc/mtab~ and deleted
+ * this lock file afterwards. However, as rgooch remarks, that has a
+ * race: a second mount may be waiting on the lock and proceed as
+ * soon as the lock file is deleted by the first mount, and immediately
+ * afterwards a third mount comes, creates a new /etc/mtab~, applies
+ * flock to that, and also proceeds, so that the second and third mount
+ * are now both scribbling in /etc/mtab.
+ *
+ * The new code uses a link() instead of a creat(), where we proceed
+ * only if it was us that created the lock, and hence we always have
+ * to delete the lock afterwards. Now the use of flock() is in principle
+ * superfluous, but avoids an arbitrary sleep().
+ *
+ * Where does the link point to? Obvious choices are mtab and mtab~~.
+ * HJLu points out that the latter leads to races. Right now we use
+ * mtab~.pid instead.
+ *
+ *
+ * The original mount locking code has used sleep(1) between attempts and
+ * maximal number of attempts has been 5.
+ *
+ * There was a very small number of attempts and extremely long waiting (1s)
+ * that is useless on machines with large number of mount processes.
+ *
+ * Now we wait for a few thousand microseconds between attempts and we have a global
+ * time limit (30s) rather than a limit for the number of attempts. The advantage
+ * is that this method also counts time which we spend in fcntl(F_SETLKW) and
+ * the number of attempts is not restricted.
+ * -- kzak@redhat.com [Mar-2007]
+ *
+ *
+ * This mtab locking code has been refactored and moved to libmount. The mtab
+ * locking is really not perfect (e.g. SIGALRM), but it's stable, reliable and
+ * backwardly compatible code.
+ *
+ * Don't forget that this code has to be compatible with 3rd party mounts
+ * (/sbin/mount.foo) and has to work with NFS.
+ * -- kzak@redhat.com [May-2009]
+ */
+
+/* maximum seconds between the first and the last attempt */
+#define MOUNTLOCK_MAXTIME 30
+
+/* sleep time (in microseconds, max=999999) between attempts */
+#define MOUNTLOCK_WAITTIME 5000
+
+static void unlock_mtab(struct libmnt_lock *ml)
+{
+ if (!ml)
+ return;
+
+ if (!ml->locked && ml->lockfile && ml->linkfile)
+ {
+ /* We (probably) have all the files, but we don't own the lock,
+ * Really? Check it! Maybe ml->locked wasn't set properly
+ * because the code was interrupted by a signal. Paranoia? Yes.
+ *
+ * We own the lock when linkfile == lockfile.
+ */
+ struct stat lo, li;
+
+ if (!stat(ml->lockfile, &lo) && !stat(ml->linkfile, &li) &&
+ lo.st_dev == li.st_dev && lo.st_ino == li.st_ino)
+ ml->locked = 1;
+ }
+
+ if (ml->linkfile)
+ unlink(ml->linkfile);
+ if (ml->lockfile_fd >= 0)
+ close(ml->lockfile_fd);
+ if (ml->locked && ml->lockfile) {
+ unlink(ml->lockfile);
+ DBG(LOCKS, ul_debugobj(ml, "unlink %s", ml->lockfile));
+ }
+}
+
+static int lock_mtab(struct libmnt_lock *ml)
+{
+ int i, rc = -1;
+ struct timespec waittime;
+ struct timeval maxtime;
+ const char *lockfile, *linkfile;
+
+ if (!ml)
+ return -EINVAL;
+ if (ml->locked)
+ return 0;
+
+ lockfile = mnt_lock_get_lockfile(ml);
+ if (!lockfile)
+ return -EINVAL;
+ linkfile = mnt_lock_get_linkfile(ml);
+ if (!linkfile)
+ return -EINVAL;
+
+ if (ml->sigblock) {
+ /*
+ * Block all signals when locked, mnt_unlock_file() will
+ * restore the old mask.
+ */
+ sigset_t sigs;
+
+ sigemptyset(&ml->oldsigmask);
+ sigfillset(&sigs);
+ sigdelset(&sigs, SIGTRAP);
+ sigdelset(&sigs, SIGALRM);
+ sigprocmask(SIG_BLOCK, &sigs, &ml->oldsigmask);
+ }
+
+ i = open(linkfile, O_WRONLY|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR);
+ if (i < 0) {
+ /* linkfile does not exist (as a file) and we cannot create it.
+ * Read-only or full filesystem? Too many files open in the system?
+ */
+ if (errno > 0)
+ rc = -errno;
+ goto failed;
+ }
+ close(i);
+
+ gettime_monotonic(&maxtime);
+ maxtime.tv_sec += MOUNTLOCK_MAXTIME;
+
+ waittime.tv_sec = 0;
+ waittime.tv_nsec = (1000 * MOUNTLOCK_WAITTIME);
+
+ /* Repeat until it was us who made the link */
+ while (!ml->locked) {
+ struct timeval now;
+ struct flock flock;
+ int j;
+
+ j = link(linkfile, lockfile);
+ if (j == 0)
+ ml->locked = 1;
+
+ if (j < 0 && errno != EEXIST) {
+ if (errno > 0)
+ rc = -errno;
+ goto failed;
+ }
+ ml->lockfile_fd = open(lockfile, O_WRONLY|O_CLOEXEC);
+
+ if (ml->lockfile_fd < 0) {
+ /* Strange... Maybe the file was just deleted? */
+ int errsv = errno;
+ gettime_monotonic(&now);
+ if (errsv == ENOENT && now.tv_sec < maxtime.tv_sec) {
+ ml->locked = 0;
+ continue;
+ }
+ if (errsv > 0)
+ rc = -errsv;
+ goto failed;
+ }
+
+ flock.l_type = F_WRLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+
+ if (ml->locked) {
+ /* We made the link. Now claim the lock. */
+ if (fcntl (ml->lockfile_fd, F_SETLK, &flock) == -1) {
+ DBG(LOCKS, ul_debugobj(ml,
+ "%s: can't F_SETLK lockfile, errno=%d\n",
+ lockfile, errno));
+ /* proceed, since it was us who created the lockfile anyway */
+ }
+ break;
+ }
+
+ /* Someone else made the link. Wait. */
+ int err = mnt_wait_mtab_lock(ml, &flock, maxtime.tv_sec);
+
+ if (err == 1) {
+ DBG(LOCKS, ul_debugobj(ml,
+ "%s: can't create link: time out (perhaps "
+ "there is a stale lock file?)", lockfile));
+ rc = -ETIMEDOUT;
+ goto failed;
+
+ } else if (err < 0) {
+ rc = err;
+ goto failed;
+ }
+ nanosleep(&waittime, NULL);
+ close(ml->lockfile_fd);
+ ml->lockfile_fd = -1;
+ }
+ DBG(LOCKS, ul_debugobj(ml, "%s: (%d) successfully locked",
+ lockfile, getpid()));
+ unlink(linkfile);
+ return 0;
+
+failed:
+ mnt_unlock_file(ml);
+ return rc;
+}
+
+
+/**
+ * mnt_lock_file
+ * @ml: pointer to struct libmnt_lock instance
+ *
+ * Creates a lock file (e.g. /etc/mtab~). Note that this function may
+ * use alarm().
+ *
+ * Your application always has to call mnt_unlock_file() before exit.
+ *
+ * Traditional mtab locking scheme:
+ *
+ * 1. create linkfile (e.g. /etc/mtab~.$PID)
+ * 2. link linkfile --> lockfile (e.g. /etc/mtab~.$PID --> /etc/mtab~)
+ * 3. a) link() success: setups F_SETLK lock (see fcntl(2))
+ * b) link() failed: wait (max 30s) on F_SETLKW lock, goto 2.
+ *
+ * Note that when the lock is used by mnt_update_table() interface then libmount
+ * uses flock() for private library file /run/mount/utab. The fcntl(2) is used only
+ * for backwardly compatible stuff like /etc/mtab.
+ *
+ * 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;
+
+ if (ml->simplelock)
+ return lock_simplelock(ml);
+
+ return lock_mtab(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"));
+
+ if (ml->simplelock)
+ unlock_simplelock(ml);
+ else
+ unlock_mtab(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..dada02e
--- /dev/null
+++ b/libmount/src/monitor.c
@@ -0,0 +1,980 @@
+/* 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);
+ assert(me);
+
+ *me = NULL;
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &mn->ents);
+ if (itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, *me, struct monitor_entry, ents);
+ 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..d8ba0ab
--- /dev/null
+++ b/libmount/src/mountP.h
@@ -0,0 +1,485 @@
+/* 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 "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_ALL 0xFFFF
+
+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"
+#define MNT_RUNTIME_TOPDIR_OLD "/dev"
+
+#define MNT_PATH_UTAB MNT_RUNTIME_TOPDIR "/mount/utab"
+#define MNT_PATH_UTAB_OLD MNT_RUNTIME_TOPDIR_OLD "/.mount/utab"
+
+#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 int append_string(char **a, const char *b);
+
+extern const char *mnt_statfs_get_fstype(struct statfs *vfs);
+extern int is_procfs_fd(int fd);
+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_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_stat_mountpoint(const char *target, struct stat *st);
+extern int mnt_lstat_mountpoint(const char *target, struct stat *st);
+extern FILE *mnt_get_procfs_memstream(int fd, char **membuf);
+
+/* 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_mtab(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);
+
+/*
+ * 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_ITERATE(itr, res, restype, member) \
+ do { \
+ res = list_entry((itr)->p, restype, member); \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (itr)->p->next : (itr)->p->prev; \
+ } while(0)
+
+
+/*
+ * This struct represents one entry in a mtab/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 */
+ 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 */
+
+#define mnt_fs_is_regular(_f) (!(mnt_fs_is_pseudofs(_f) \
+ || mnt_fs_is_netfs(_f) \
+ || mnt_fs_is_swaparea(_f)))
+
+/*
+ * mtab/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;
+
+
+ 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 */
+};
+
+/*
+ * Additional mounts
+ */
+struct libmnt_addmount {
+ unsigned long mountflags;
+
+ struct list_head mounts;
+};
+
+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_fs *fs_template; /* used for @fs on mnt_reset_context() */
+
+ struct libmnt_table *fstab; /* fstab (or mtab for some remounts) entries */
+ struct libmnt_table *mtab; /* mtab entries */
+ 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} */
+ int loopdev_fd; /* open loopdev */
+
+ unsigned long mountflags; /* final mount(2) flags */
+ const void *mountdata; /* final mount(2) data, string or binary data */
+
+ unsigned long user_mountflags; /* MNT_MS_* (loop=, user=, ...) */
+
+ struct list_head addmounts; /* additional mounts */
+
+ struct libmnt_cache *cache; /* paths cache */
+ struct libmnt_lock *lock; /* mtab lock */
+ struct libmnt_update *update;/* mtab/utab update */
+
+ const char *mtab_path; /* path to mtab */
+ int mtab_writable; /* is mtab writable */
+
+ 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 */
+
+ char *orig_user; /* original (non-fixed) user= option */
+
+ 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 */
+
+ 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 */
+};
+
+/* 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_MOUNTDATA (1 << 20)
+#define MNT_FL_TAB_APPLIED (1 << 21) /* mtab/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_LOOPDEV_READY (1 << 26) /* /dev/loop<N> initialized by the library */
+#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)
+
+/* lock.c */
+extern int mnt_lock_use_simplelock(struct libmnt_lock *ml, int enable);
+
+/* 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_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next);
+extern int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next);
+extern int mnt_optstr_fix_secontext(char **optstr, char *value, size_t valsz, char **next);
+extern int mnt_optstr_fix_user(char **optstr);
+
+/* fs.c */
+extern struct libmnt_fs *mnt_copy_mtab_fs(const struct libmnt_fs *fs)
+ __attribute__((nonnull));
+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)));
+
+/* context.c */
+extern struct libmnt_context *mnt_copy_context(struct libmnt_context *o);
+extern int mnt_context_mtab_writable(struct libmnt_context *cxt);
+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_mtab_for_target(struct libmnt_context *cxt,
+ struct libmnt_table **mtab, const char *tgt);
+
+extern int mnt_context_prepare_srcpath(struct libmnt_context *cxt);
+extern int mnt_context_prepare_target(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_is_loopdev(struct libmnt_context *cxt)
+ __attribute__((nonnull));
+
+extern int mnt_context_propagation_only(struct libmnt_context *cxt)
+ __attribute__((nonnull));
+
+extern struct libmnt_addmount *mnt_new_addmount(void);
+extern void mnt_free_addmount(struct libmnt_addmount *ad);
+
+extern int mnt_context_setup_loopdev(struct libmnt_context *cxt);
+extern int mnt_context_delete_loopdev(struct libmnt_context *cxt);
+extern int mnt_context_clear_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, char *fmt, ...);
+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 int mnt_context_is_veritydev(struct libmnt_context *cxt)
+ __attribute__((nonnull));
+extern int mnt_context_setup_veritydev(struct libmnt_context *cxt);
+extern int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt);
+
+/* tab_update.c */
+extern int mnt_update_set_filename(struct libmnt_update *upd,
+ const char *filename, int userspace_only);
+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
+
+#endif /* _LIBMOUNT_PRIVATE_H */
diff --git a/libmount/src/optmap.c b/libmount/src/optmap.c
new file mode 100644
index 0000000..49e8113
--- /dev/null
+++ b/libmount/src/optmap.c
@@ -0,0 +1,268 @@
+/* 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 }, /* synchronous I/O */
+ { "async", MS_SYNCHRONOUS, MNT_INVERT },/* asynchronous I/O */
+
+ { "dirsync", MS_DIRSYNC }, /* 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 }, /* Allow mandatory locks on this FS */
+ { "nomand", MS_MANDLOCK, MNT_INVERT }, /* 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 }, /* Update {a,m,c}time on the in-memory inode only */
+ { "nolazytime", MS_LAZYTIME, MNT_INVERT },
+#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
+ { NULL, 0, 0 }
+};
+
+/*
+ * userspace mount option (built-in MNT_USERSPACE_MAP)
+ */
+static const struct libmnt_optmap userspace_opts_map[] =
+{
+ { "defaults", 0, 0 }, /* 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 */
+
+ { 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..ea933c6
--- /dev/null
+++ b/libmount/src/optstr.c
@@ -0,0 +1,1442 @@
+/* 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>
+
+#ifdef HAVE_LIBSELINUX
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+#endif
+
+#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(_ol) (memset((_ol), 0, sizeof(struct libmnt_optloc)))
+
+#define mnt_optmap_entry_novalue(e) \
+ (e && (e)->name && !strchr((e)->name, '=') && !((e)->mask & MNT_PREFIX))
+
+/*
+ * Parses the first option from @optstr. The @optstr pointer is set to the beginning
+ * of the next option.
+ *
+ * Returns -EINVAL on parse error, 1 at the end of optstr and 0 on success.
+ */
+static int mnt_optstr_parse_next(char **optstr, char **name, size_t *namesz,
+ char **value, size_t *valsz)
+{
+ int open_quote = 0;
+ char *start = NULL, *stop = NULL, *p, *sep = NULL;
+ char *optstr0;
+
+ assert(optstr);
+ assert(*optstr);
+
+ optstr0 = *optstr;
+
+ if (name)
+ *name = NULL;
+ if (namesz)
+ *namesz = 0;
+ if (value)
+ *value = NULL;
+ if (valsz)
+ *valsz = 0;
+
+ /* trim leading commas as to not invalidate option
+ * strings with multiple consecutive commas */
+ while (optstr0 && *optstr0 == ',')
+ optstr0++;
+
+ for (p = optstr0; p && *p; p++) {
+ if (!start)
+ start = p; /* beginning of the option item */
+ if (*p == '"')
+ open_quote ^= 1; /* reverse the status */
+ if (open_quote)
+ continue; /* still in quoted block */
+ if (!sep && p > start && *p == '=')
+ sep = p; /* name and value separator */
+ if (*p == ',')
+ stop = p; /* terminate the option item */
+ else if (*(p + 1) == '\0')
+ stop = p + 1; /* end of optstr */
+ if (!start || !stop)
+ continue;
+ if (stop <= start)
+ goto error;
+
+ if (name)
+ *name = start;
+ if (namesz)
+ *namesz = sep ? sep - start : stop - start;
+ *optstr = *stop ? stop + 1 : stop;
+
+ if (sep) {
+ if (value)
+ *value = sep + 1;
+ if (valsz)
+ *valsz = stop - sep - 1;
+ }
+ return 0;
+ }
+
+ return 1; /* end of optstr */
+
+error:
+ DBG(OPTIONS, ul_debug("parse error: \"%s\"", optstr0));
+ return -EINVAL;
+}
+
+/*
+ * 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);
+
+ do {
+ rc = mnt_optstr_parse_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 mnt_optstr_parse_next(optstr, name, namesz, value, valuesz);
+}
+
+static int __mnt_optstr_append_option(char **optstr,
+ const char *name, size_t nsz,
+ const char *value, size_t vsz)
+{
+ char *p;
+ size_t sz, osz;
+
+ assert(name);
+ assert(*name);
+ assert(nsz);
+ assert(optstr);
+
+ osz = *optstr ? strlen(*optstr) : 0;
+
+ sz = osz + nsz + 1; /* 1: '\0' */
+ if (osz)
+ sz++; /* ',' options separator */
+ if (value)
+ sz += vsz + 1; /* 1: '=' */
+
+ p = realloc(*optstr, sz);
+ if (!p)
+ return -ENOMEM;
+ *optstr = p;
+
+ if (osz) {
+ p += osz;
+ *p++ = ',';
+ }
+
+ memcpy(p, name, nsz);
+ p += nsz;
+
+ if (value) {
+ *p++ = '=';
+ memcpy(p, value, vsz);
+ p += vsz;
+ }
+ *p = '\0';
+
+ return 0;
+}
+
+/**
+ * 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)
+{
+ size_t vsz, nsz;
+
+ if (!optstr)
+ return -EINVAL;
+ if (!name || !*name)
+ return 0;
+
+ nsz = strlen(name);
+ vsz = value ? strlen(value) : 0;
+
+ return __mnt_optstr_append_option(optstr, name, nsz, value, vsz);
+}
+
+/**
+ * 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)
+{
+ int rc = 0;
+ char *tmp;
+
+ if (!optstr)
+ return -EINVAL;
+ if (!name || !*name)
+ return 0;
+
+ tmp = *optstr;
+ *optstr = NULL;
+
+ rc = mnt_optstr_append_option(optstr, name, value);
+ if (!rc && tmp && *tmp)
+ rc = mnt_optstr_append_option(optstr, tmp, NULL);
+ if (!rc) {
+ free(tmp);
+ return 0;
+ }
+
+ free(*optstr);
+ *optstr = tmp;
+
+ DBG(OPTIONS, ul_debug("failed to prepend '%s[=%s]' to '%s'",
+ name, value, *optstr));
+ 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;
+ int rc;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ mnt_init_optloc(&ol);
+
+ 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(&ol);
+
+ 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;
+ char *nameend;
+ int rc = 1;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ mnt_init_optloc(&ol);
+
+ 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;
+ int rc;
+
+ if (!optstr || !name)
+ return -EINVAL;
+
+ mnt_init_optloc(&ol);
+
+ 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)
+{
+ char *name, *val, *str = (char *) optstr;
+ size_t namesz, valsz;
+ struct libmnt_optmap const *maps[2];
+
+ if (!optstr)
+ return -EINVAL;
+
+ maps[0] = mnt_get_builtin_optmap(MNT_LINUX_MAP);
+ maps[1] = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
+
+ if (vfs)
+ *vfs = NULL;
+ if (fs)
+ *fs = NULL;
+ if (user)
+ *user = NULL;
+
+ while (!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) {
+ int rc = 0;
+ 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;
+ rc = __mnt_optstr_append_option(vfs, name, namesz,
+ val, valsz);
+ } else if (ent && m && m == maps[1] && user) {
+ if (ignore_user && (ent->mask & ignore_user))
+ continue;
+ rc = __mnt_optstr_append_option(user, name, namesz,
+ val, valsz);
+ } else if (!m && fs)
+ rc = __mnt_optstr_append_option(fs, name, namesz,
+ val, valsz);
+ if (rc) {
+ if (vfs) {
+ free(*vfs);
+ *vfs = NULL;
+ }
+ if (fs) {
+ free(*fs);
+ *fs = NULL;
+ }
+ if (user) {
+ free(*user);
+ *user = NULL;
+ }
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * 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];
+ char *name, *val, *str = (char *) optstr;
+ size_t namesz, valsz;
+
+ if (!optstr || !subset)
+ return -EINVAL;
+
+ maps[0] = map;
+ *subset = NULL;
+
+ while(!mnt_optstr_next_option(&str, &name, &namesz, &val, &valsz)) {
+ int rc = 0;
+ 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_optstr_append_option(subset, name, namesz, val, valsz);
+ if (rc) {
+ free(*subset);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * 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.
+ */
+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;
+ 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)) {
+ fl &= ~ent->id;
+ if (ent->id & MS_REC)
+ fl |= MS_REC;
+ }
+ }
+ }
+ }
+
+ /* add missing options (but ignore fl if contains MS_REC only) */
+ if (fl && fl != MS_REC) {
+ const struct libmnt_optmap *ent;
+ char *p;
+
+ 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= */
+
+ p = strndup(ent->name, p - ent->name);
+ if (!p) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ mnt_optstr_append_option(optstr, p, NULL);
+ free(p);
+ } else
+ mnt_optstr_append_option(optstr, ent->name, 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;
+}
+
+/*
+ * @optstr: string with comma separated list of options
+ * @value: pointer to the begin of the context value
+ * @valsz: size of the value
+ * @next: returns pointer to the next option (optional argument)
+ *
+ * Translates SELinux context from human to raw format. The function does not
+ * modify @optstr and returns zero if libmount is compiled without SELinux
+ * support.
+ *
+ * Returns: 0 on success, a negative number in case of error.
+ */
+#ifndef HAVE_LIBSELINUX
+int mnt_optstr_fix_secontext(char **optstr __attribute__ ((__unused__)),
+ char *value __attribute__ ((__unused__)),
+ size_t valsz __attribute__ ((__unused__)),
+ char **next __attribute__ ((__unused__)))
+{
+ return 0;
+}
+#else
+int mnt_optstr_fix_secontext(char **optstr,
+ char *value,
+ size_t valsz,
+ char **next)
+{
+ int rc = 0;
+
+ security_context_t raw = NULL;
+ char *p, *val, *begin, *end;
+ size_t sz;
+
+ if (!optstr || !*optstr || !value || !valsz)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("fixing SELinux context"));
+
+ begin = value;
+ end = value + valsz;
+
+ /* the selinux contexts are quoted */
+ if (*value == '"') {
+ if (valsz <= 2 || *(value + valsz - 1) != '"')
+ return -EINVAL; /* improperly quoted option string */
+ value++;
+ valsz -= 2;
+ }
+
+ p = strndup(value, valsz);
+ if (!p)
+ return -ENOMEM;
+
+
+ /* translate the context */
+ rc = selinux_trans_to_raw_context((security_context_t) p, &raw);
+
+ DBG(CXT, ul_debug("SELinux context '%s' translated to '%s'",
+ p, rc == -1 ? "FAILED" : (char *) raw));
+
+ free(p);
+ if (rc == -1 || !raw)
+ return -EINVAL;
+
+
+ /* create a quoted string from the raw context */
+ sz = strlen((char *) raw);
+ if (!sz)
+ return -EINVAL;
+
+ p = val = malloc(valsz + 3);
+ if (!val)
+ return -ENOMEM;
+
+ *p++ = '"';
+ memcpy(p, raw, sz);
+ p += sz;
+ *p++ = '"';
+ *p = '\0';
+
+ freecon(raw);
+
+ /* set new context */
+ mnt_optstr_remove_option_at(optstr, begin, end);
+ rc = insert_value(optstr, begin, val, next);
+ free(val);
+
+ return rc;
+}
+#endif
+
+static int set_uint_value(char **optstr, unsigned int num,
+ char *begin, char *end, char **next)
+{
+ char buf[40];
+ snprintf(buf, sizeof(buf), "%u", num);
+
+ mnt_optstr_remove_option_at(optstr, begin, end);
+ return insert_value(optstr, begin, buf, next);
+}
+
+/*
+ * @optstr: string with a comma separated list of options
+ * @value: pointer to the beginning of the uid value
+ * @valsz: size of the value
+ * @next: returns pointer to the next option (optional argument)
+
+ * Translates "username" or "useruid" to the real UID.
+ *
+ * For example:
+ * if (!mnt_optstr_get_option(optstr, "uid", &val, &valsz))
+ * mnt_optstr_fix_uid(&optstr, val, valsz, NULL);
+ *
+ * Returns: 0 on success, a negative number in case of error.
+ */
+int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next)
+{
+ char *end;
+
+ if (!optstr || !*optstr || !value || !valsz)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("fixing uid"));
+
+ end = value + valsz;
+
+ if (valsz == 7 && !strncmp(value, "useruid", 7) &&
+ (*(value + 7) == ',' || !*(value + 7)))
+ return set_uint_value(optstr, getuid(), value, end, next);
+
+ if (!isdigit(*value)) {
+ uid_t id;
+ int rc;
+ char *p = strndup(value, valsz);
+ if (!p)
+ return -ENOMEM;
+ rc = mnt_get_uid(p, &id);
+ free(p);
+
+ if (!rc)
+ return set_uint_value(optstr, id, value, end, next);
+ }
+
+ if (next) {
+ /* no change, let's keep the original value */
+ *next = value + valsz;
+ if (**next == ',')
+ (*next)++;
+ }
+
+ return 0;
+}
+
+/*
+ * @optstr: string with a comma separated list of options
+ * @value: pointer to the beginning of the uid value
+ * @valsz: size of the value
+ * @next: returns pointer to the next option (optional argument)
+
+ * Translates "groupname" or "usergid" to the real GID.
+ *
+ * Returns: 0 on success, a negative number in case of error.
+ */
+int mnt_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next)
+{
+ char *end;
+
+ if (!optstr || !*optstr || !value || !valsz)
+ return -EINVAL;
+
+ DBG(CXT, ul_debug("fixing gid"));
+
+ end = value + valsz;
+
+ if (valsz == 7 && !strncmp(value, "usergid", 7) &&
+ (*(value + 7) == ',' || !*(value + 7)))
+ return set_uint_value(optstr, getgid(), value, end, next);
+
+ if (!isdigit(*value)) {
+ int rc;
+ gid_t id;
+ char *p = strndup(value, valsz);
+ if (!p)
+ return -ENOMEM;
+ rc = mnt_get_gid(p, &id);
+ free(p);
+
+ if (!rc)
+ return set_uint_value(optstr, id, value, end, next);
+
+ }
+
+ if (next) {
+ /* nothing */
+ *next = value + valsz;
+ if (**next == ',')
+ (*next)++;
+ }
+ return 0;
+}
+
+/*
+ * Converts "user" to "user=<username>".
+ *
+ * Returns: 0 on success, negative number in case of error.
+ */
+int mnt_optstr_fix_user(char **optstr)
+{
+ char *username;
+ struct libmnt_optloc ol;
+ int rc = 0;
+
+ DBG(CXT, ul_debug("fixing user"));
+
+ mnt_init_optloc(&ol);
+
+ rc = mnt_optstr_locate_option(*optstr, "user", &ol);
+ if (rc)
+ return rc == 1 ? 0 : rc; /* 1: user= not found */
+
+ username = mnt_get_username(getuid());
+ if (!username)
+ return -ENOMEM;
+
+ if (!ol.valsz || (ol.value && strncmp(ol.value, username, ol.valsz) != 0)) {
+ if (ol.valsz)
+ /* remove old value */
+ mnt_optstr_remove_option_at(optstr, ol.value, ol.end);
+
+ rc = insert_value(optstr, ol.value ? ol.value : ol.end,
+ username, NULL);
+ }
+
+ free(username);
+ 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.
+ *
+ * "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")
+ *
+ *
+ * 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, *patval;
+ size_t namesz = 0, patvalsz = 0;
+ int match = 1;
+
+ if (!pattern && !optstr)
+ return 1;
+ if (!pattern)
+ return 0;
+
+ buf = malloc(strlen(pattern) + 1);
+ if (!buf)
+ return 0;
+
+ /* walk on pattern string
+ */
+ while (match && !mnt_optstr_next_option(&pat, &name, &namesz,
+ &patval, &patvalsz)) {
+ char *val;
+ size_t sz;
+ int no = 0, rc;
+
+ if (*name == '+')
+ name++, namesz--;
+ else if ((no = (startswith(name, "no") != NULL)))
+ name += 2, namesz -= 2;
+
+ xstrncpy(buf, name, namesz + 1);
+
+ rc = mnt_optstr_get_option(optstr, buf, &val, &sz);
+
+ /* 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]);
+ 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]);
+ 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]);
+
+ 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]);
+
+ 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]);
+ 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]);
+ 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]);
+ 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]);
+ name = argv[2];
+
+ rc = mnt_optstr_deduplicate_option(&optstr, name);
+ if (!rc)
+ printf("result: >%s<\n", optstr);
+ free(optstr);
+ return rc;
+}
+
+static int test_fix(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *optstr;
+ int rc = 0;
+ char *name, *val, *next;
+ size_t valsz, namesz;
+
+ if (argc < 2)
+ return -EINVAL;
+
+ next = optstr = strdup(argv[1]);
+
+ printf("optstr: %s\n", optstr);
+
+ while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) {
+
+ if (!strncmp(name, "uid", 3))
+ rc = mnt_optstr_fix_uid(&optstr, val, valsz, &next);
+ else if (!strncmp(name, "gid", 3))
+ rc = mnt_optstr_fix_gid(&optstr, val, valsz, &next);
+ else if (!strncmp(name, "context", 7))
+ rc = mnt_optstr_fix_secontext(&optstr, val, valsz, &next);
+ if (rc)
+ break;
+ }
+ if (rc)
+ rc = mnt_optstr_fix_user(&optstr);
+
+ printf("fixed: %s\n", optstr);
+
+ free(optstr);
+ return rc;
+
+}
+
+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" },
+ { "--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" },
+ { "--fix", test_fix, "<optstr> fix uid=, gid=, user, and context=" },
+
+ { 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..e91254a
--- /dev/null
+++ b/libmount/src/tab.c
@@ -0,0 +1,2206 @@
+
+/* 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 append_string(&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 append_string(&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: 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;
+ int root_id = 0;
+
+ if (!tb || !root || !is_mountinfo(tb))
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup root fs"));
+
+ *root = NULL;
+
+ /* 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 || id < root_id) {
+ *root = fs;
+ root_id = id;
+ }
+ }
+
+ /* go to the root node by "parent_id -> id" relation */
+ while (*root) {
+ struct libmnt_fs *x = get_parent_fs(tb, *root);
+ if (!x || x == *root)
+ break;
+ DBG(TAB, ul_debugobj(tb, " messy mountinfo, walk to %s", mnt_fs_get_target(x)));
+ *root = x;
+ }
+
+ return *root ? 0 : -EINVAL;
+}
+
+/**
+ * mnt_table_next_child_fs:
+ * @tb: mountinfo file (/proc/self/mountinfo)
+ * @itr: iterator
+ * @parent: parental FS
+ * @chld: returns the next child filesystem
+ *
+ * Note that filesystems are returned in the order of mounting (according to
+ * IDs in /proc/self/mountinfo).
+ *
+ * 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;
+ int parent_id, lastchld_id = 0, chld_id = 0;
+
+ 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);
+
+ /* get ID of the previously returned child */
+ if (itr->head && itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, fs, struct libmnt_fs, ents);
+ lastchld_id = mnt_fs_get_id(fs);
+ }
+
+ *chld = NULL;
+
+ mnt_reset_iter(itr, MNT_ITER_FORWARD);
+ 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 ((!lastchld_id || id > lastchld_id) &&
+ (!*chld || id < chld_id)) {
+ *chld = fs;
+ chld_id = id;
+ }
+ }
+
+ if (!*chld)
+ return 1; /* end of iterator */
+
+ /* set the iterator to the @chld for the next call */
+ mnt_table_set_iter(tb, itr, *chld);
+
+ return 0;
+}
+
+/**
+ * mnt_table_next_fs:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @fs: 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 || !fs)
+ return -EINVAL;
+ *fs = NULL;
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &tb->ents);
+ if (itr->p != itr->head) {
+ MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents);
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/**
+ * mnt_table_first_fs:
+ * @tb: tab pointer
+ * @fs: 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 || !fs)
+ return -EINVAL;
+ if (list_empty(&tb->ents))
+ return 1;
+ *fs = list_first_entry(&tb->ents, struct libmnt_fs, ents);
+ return 0;
+}
+
+/**
+ * mnt_table_last_fs:
+ * @tb: tab pointer
+ * @fs: 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 || !fs)
+ return -EINVAL;
+ if (list_empty(&tb->ents))
+ return 1;
+ *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: 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)
+{
+ if (!tb || !itr || !fs || !match_func)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "lookup next fs"));
+
+ if (!itr->head)
+ MNT_ITER_INIT(itr, &tb->ents);
+
+ do {
+ if (itr->p != itr->head)
+ MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents);
+ else
+ break; /* end */
+
+ if (match_func(*fs, userdata))
+ return 0;
+ } while(1);
+
+ *fs = NULL;
+ return 1;
+}
+
+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;
+ struct stat st;
+
+ 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_stat_mountpoint(path, &st))
+ 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 && !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 = strappend(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 && 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;
+ int writable = 0;
+ const char *path = NULL;
+
+ if (mnt_has_regular_mtab(&path, &writable) == 1 && writable == 0)
+ tb = mnt_new_table_from_file(path);
+ else
+ 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..81694bc
--- /dev/null
+++ b/libmount/src/tab_diff.c
@@ -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.
+ */
+
+/**
+ * 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) {
+ MNT_ITER_ITERATE(itr, de, struct tabdiff_entry, changes);
+ 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..fa2d31b
--- /dev/null
+++ b/libmount/src/tab_parse.c
@@ -0,0 +1,1343 @@
+/* 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 <sys/stat.h>
+
+#include "fileutils.h"
+#include "mangle.h"
+#include "mountP.h"
+#include "pathnames.h"
+#include "strutils.h"
+
+struct libmnt_parser {
+ FILE *f; /* fstab, mtab, 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 */
+};
+
+static void parser_cleanup(struct libmnt_parser *pa)
+{
+ if (!pa)
+ return;
+ free(pa->buf);
+ 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;
+
+ *rc = -EINVAL;
+ *num = strtol(s, &end, 10);
+ if (end == NULL || s == end)
+ return s;
+ if (*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;
+
+ *rc = -EINVAL;
+ *num = (uint64_t) strtoumax(s, &end, 10);
+ if (end == NULL || s == end)
+ return s;
+ if (*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;
+ }
+
+ /* remove "\040(deleted)" suffix */
+ p = (char *) endswith(fs->target, PATH_DELETED_SUFFIX);
+ if (p && *p)
+ *p = '\0';
+
+ 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->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, mtab 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) {
+ /* Missing final newline? Otherwise an extremely */
+ /* long line - assume file was corrupted */
+ if (feof(pa->f)) {
+ DBG(TAB, ul_debugobj(tb,
+ "%s: no final newline", pa->filename));
+ s = strchr(pa->buf, '\0');
+ } else {
+ DBG(TAB, ul_debugobj(tb,
+ "%s:%zu: missing newline at line",
+ pa->filename, pa->line));
+ goto err;
+ }
+ }
+
+ /* comments parser */
+ 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;
+
+ }
+
+ *s = '\0';
+ if (--s >= pa->buf && *s == '\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_table *tb,
+ struct libmnt_fs *fs, pid_t *tid,
+ const char *filename)
+{
+ 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 (filename && *tid == -1)
+ *tid = path_to_tid(filename);
+
+ fs->tid = *tid;
+
+ /*
+ * Convert obscure /dev/root to something more usable
+ */
+ if (src && strcmp(src, "/dev/root") == 0) {
+ char *real = NULL;
+
+ rc = mnt_guess_system_root(fs->devno, tb->cache, &real);
+ if (rc < 0)
+ return rc;
+
+ if (rc == 0 && real) {
+ 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;
+}
+
+static int __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 (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... */
+
+ /* 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(tb, fs, &tid, filename);
+ 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_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 fd, rc;
+ FILE *memf = NULL;
+ char *membuf = NULL;
+
+ /*
+ * For /proc/#/{mountinfo,mount} we read all file to memory and use it
+ * as memory stream. For more details see mnt_read_procfs_file().
+ */
+ if ((fd = fileno(f)) >= 0
+ && (tb->fmt == MNT_FMT_GUESS ||
+ tb->fmt == MNT_FMT_MOUNTINFO ||
+ tb->fmt == MNT_FMT_MTAB)
+ && is_procfs_fd(fd)
+ && (memf = mnt_get_procfs_memstream(fd, &membuf))) {
+
+ rc = __table_parse_stream(tb, memf, filename);
+ fclose(memf);
+ free(membuf);
+ } else
+ rc = __table_parse_stream(tb, f, filename);
+
+ 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, fd = -1;
+
+ if (!filename || !tb)
+ return -EINVAL;
+
+ /*
+ * Try to use read()+poll() to realiably read all
+ * /proc/#/{mount,mountinfo} file to memory
+ */
+ if (tb->fmt != MNT_FMT_SWAPS
+ && strncmp(filename, "/proc/", 6) == 0) {
+
+ FILE *memf;
+ char *membuf = NULL;
+
+ fd = open(filename, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ rc = -errno;
+ goto done;
+ }
+ memf = mnt_get_procfs_memstream(fd, &membuf);
+ if (memf) {
+ rc = __table_parse_stream(tb, memf, filename);
+
+ fclose(memf);
+ free(membuf);
+ close(fd);
+ goto done;
+ }
+ /* else fallback to fopen/fdopen() */
+ }
+
+ if (fd >= 0)
+ f = fdopen(fd, "r" UL_CLOEXECSTR);
+ else
+ f = fopen(filename, "r" UL_CLOEXECSTR);
+
+ if (f) {
+ rc = __table_parse_stream(tb, f, filename);
+ fclose(f);
+ } else
+ rc = -errno;
+done:
+ 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) {
+ __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) {
+ __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;
+ struct stat st;
+
+ if (!filename)
+ return NULL;
+ if (stat(filename, &st))
+ 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_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 (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;
+
+ 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);
+
+ 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 (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 fs -- appending user optstr"));
+ 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, ul_debugobj(tb, "found fs:"));
+ DBG(TAB, mnt_fs_print_debug(fs, stderr));
+ }
+ return fs;
+}
+
+/* default filename is /proc/self/mountinfo
+ */
+int __mnt_table_parse_mtab(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 mtab", filename));
+
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ if (mnt_has_regular_mtab(&filename, NULL)) {
+
+ DBG(TAB, ul_debugobj(tb, "force mtab usage [filename=%s]", filename));
+
+ rc = mnt_table_parse_file(tb, filename);
+
+ /*
+ * If @filename forces us to read from /proc then also read
+ * utab file to merge userspace mount options.
+ */
+ if (rc == 0 && is_mountinfo(tb))
+ goto read_utab;
+
+ if (!rc)
+ return 0;
+ filename = NULL; /* failed */
+ } else
+ filename = NULL; /* mtab useless */
+#endif
+
+ if (!filename || strcmp(filename, _PATH_PROC_MOUNTINFO) == 0) {
+ filename = _PATH_PROC_MOUNTINFO;
+ tb->fmt = MNT_FMT_MOUNTINFO;
+ DBG(TAB, ul_debugobj(tb, "mtab 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;
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+read_utab:
+#endif
+ DBG(TAB, ul_debugobj(tb, "mtab 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, "mtab 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.
+ *
+ * If libmount is compiled with classic mtab file support, and the /etc/mtab is
+ * a regular file then this file is parsed.
+ *
+ * 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_mtab(tb, filename, NULL);
+}
diff --git a/libmount/src/tab_update.c b/libmount/src/tab_update.c
new file mode 100644
index 0000000..b685535
--- /dev/null
+++ b/libmount/src/tab_update.c
@@ -0,0 +1,1065 @@
+/* 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. This low-level API works on
+ * systems both with and without /etc/mtab. On systems without the regular /etc/mtab
+ * file, 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"
+
+struct libmnt_update {
+ char *target;
+ struct libmnt_fs *fs;
+ char *filename;
+ unsigned long mountflags;
+ int userspace_only;
+ 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,
+ int userspace_only)
+{
+ const char *path = NULL;
+ int rw = 0;
+
+ if (!upd)
+ return -EINVAL;
+
+ /* filename explicitly defined */
+ if (filename) {
+ char *p = strdup(filename);
+ if (!p)
+ return -ENOMEM;
+
+ upd->userspace_only = userspace_only;
+ free(upd->filename);
+ upd->filename = p;
+ }
+
+ if (upd->filename)
+ return 0;
+
+ /* detect tab filename -- /etc/mtab or /run/mount/utab
+ */
+#ifdef USE_LIBMOUNT_SUPPORT_MTAB
+ mnt_has_regular_mtab(&path, &rw);
+#endif
+ if (!rw) {
+ path = NULL;
+ mnt_has_regular_utab(&path, &rw);
+ if (!rw)
+ return -EACCES;
+ upd->userspace_only = TRUE;
+ }
+ upd->filename = strdup(path);
+ if (!upd->filename)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * mnt_update_get_filename:
+ * @upd: update
+ *
+ * This function returns the file name (e.g. /etc/mtab) 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 mtab/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, 0);
+ 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 (upd->userspace_only && !(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 (!upd->userspace_only) {
+ /* /etc/mtab -- we care about VFS options there */
+ const char *o = mnt_fs_get_options(upd->fs);
+ char *n = o ? strdup(o) : NULL;
+
+ if (n)
+ mnt_optstr_remove_option(&n, rdonly ? "rw" : "ro");
+ if (!mnt_optstr_prepend_option(&n, rdonly ? "ro" : "rw", NULL))
+ rc = mnt_fs_set_options(upd->fs, n);
+
+ free(n);
+ }
+
+ 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;
+
+ 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) {
+ if (upd->userspace_only)
+ rc = fprintf_utab_fs(f, fs);
+ else
+ rc = fprintf_mtab_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,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 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,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 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,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (tb) {
+ struct libmnt_fs *cur = mnt_table_find_target(tb,
+ mnt_fs_get_srcpath(upd->fs), MNT_ITER_BACKWARD);
+ if (cur) {
+ rc = mnt_fs_set_target(cur, mnt_fs_get_target(upd->fs));
+ if (!rc)
+ rc = update_table(upd, tb);
+ }
+ }
+
+ 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,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 1);
+ if (tb) {
+ struct libmnt_fs *cur = mnt_table_find_target(tb,
+ mnt_fs_get_target(fs),
+ MNT_ITER_BACKWARD);
+ if (cur) {
+ if (upd->userspace_only)
+ 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 (lc && upd->userspace_only)
+ mnt_lock_use_simplelock(lc, TRUE); /* use flock */
+
+ 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 && upd->userspace_only)
+ mnt_lock_use_simplelock(lc, TRUE); /* use flock */
+ if (lc) {
+ rc = mnt_lock_file(lc);
+ if (rc) {
+ rc = -MNT_ERR_LOCK;
+ goto done;
+ }
+ }
+
+ tb = __mnt_new_table_from_file(upd->filename,
+ upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB, 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..92829eb
--- /dev/null
+++ b/libmount/src/utils.c
@@ -0,0 +1,1524 @@
+/* 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 <poll.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"
+
+int append_string(char **a, const char *b)
+{
+ size_t al, bl;
+ char *tmp;
+
+ assert(a);
+
+ if (!b || !*b)
+ return 0;
+ if (!*a) {
+ *a = strdup(b);
+ return !*a ? -ENOMEM : 0;
+ }
+
+ al = strlen(*a);
+ bl = strlen(b);
+
+ tmp = realloc(*a, al + bl + 1);
+ if (!tmp)
+ return -ENOMEM;
+ *a = tmp;
+ memcpy((*a) + al, b, bl + 1);
+ return 0;
+}
+
+/*
+ * 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);
+}
+
+int mnt_stat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT);
+#else
+ return stat(target, st);
+#endif
+}
+
+int mnt_lstat_mountpoint(const char *target, struct stat *st)
+{
+#ifdef AT_NO_AUTOMOUNT
+ return fstatat(AT_FDCWD, target, st, AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
+#else
+ return lstat(target, st);
+#endif
+}
+
+
+/*
+ * 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"
+ };
+
+ 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, "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;
+}
+
+int is_procfs_fd(int fd)
+{
+ struct statfs sfs;
+
+ return fstatfs(fd, &sfs) == 0 && sfs.f_type == STATFS_PROC_MAGIC;
+}
+
+/**
+ * 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));
+ rc = errno ? -errno : -EINVAL;
+ }
+
+ 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));
+ rc = errno ? -errno : -EINVAL;
+ }
+
+ free(buf);
+ 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 path to mtab
+ * @writable: returns 1 if the file is writable
+ *
+ * If the file does not exist and @writable argument is not NULL, then it will
+ * try to create the file.
+ *
+ * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check
+ * errno for more details).
+ */
+int mnt_has_regular_mtab(const char **mtab, int *writable)
+{
+ struct stat st;
+ int rc;
+ const char *filename = mtab && *mtab ? *mtab : mnt_get_mtab_path();
+
+ if (writable)
+ *writable = 0;
+ if (mtab && !*mtab)
+ *mtab = filename;
+
+ DBG(UTILS, ul_debug("mtab: %s", filename));
+
+ rc = lstat(filename, &st);
+
+ if (rc == 0) {
+ /* file exists */
+ if (S_ISREG(st.st_mode)) {
+ if (writable)
+ *writable = !try_write(filename, NULL);
+ DBG(UTILS, ul_debug("%s: writable", filename));
+ return 1;
+ }
+ goto done;
+ }
+
+ /* try to create the file */
+ if (writable) {
+ *writable = !try_write(filename, NULL);
+ if (*writable) {
+ DBG(UTILS, ul_debug("%s: writable", filename));
+ return 1;
+ }
+ }
+
+done:
+ DBG(UTILS, ul_debug("%s: irregular/non-writable", filename));
+ 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. The result does
+ * not have to be writable. See also mnt_has_regular_mtab().
+ *
+ * Returns: path to /etc/mtab or $LIBMOUNT_MTAB.
+ */
+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 /dev/.mount/utab) or $LIBMOUNT_UTAB.
+ */
+const char *mnt_get_utab_path(void)
+{
+ struct stat st;
+ const char *p = safe_getenv("LIBMOUNT_UTAB");
+
+ if (p)
+ return p;
+
+ if (stat(MNT_RUNTIME_TOPDIR, &st) == 0)
+ return MNT_PATH_UTAB;
+
+ return MNT_PATH_UTAB_OLD;
+}
+
+
+/* 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 = mkostemp(n, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
+ 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_stat_mountpoint(mnt, &st))
+ goto err;
+ base = st.st_dev;
+
+ do {
+ char *p = stripoff_last_component(mnt);
+
+ if (!p)
+ break;
+ if (mnt_stat_mountpoint(*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;
+}
+
+#if defined(HAVE_FMEMOPEN) || defined(TEST_PROGRAM)
+
+/*
+ * This function tries to minimize possible races when we read
+ * /proc/#/{mountinfo,mount} files.
+ *
+ * The idea is to minimize number of read()s and check by poll() that during
+ * the read the mount table has not been modified. If yes, than re-read it
+ * (with some limitations to avoid never ending loop).
+ *
+ * Returns: <0 error, 0 success, 1 too many attempts
+ */
+static int read_procfs_file(int fd, char **buf, size_t *bufsiz)
+{
+ size_t bufmax = 0;
+ int rc = 0, tries = 0, ninters = 0;
+ char *bufptr = NULL;
+
+ assert(buf);
+ assert(bufsiz);
+
+ *bufsiz = 0;
+ *buf = NULL;
+
+ do {
+ ssize_t ret;
+
+ if (!bufptr || bufmax == *bufsiz) {
+ char *tmp;
+
+ bufmax = bufmax ? bufmax * 2 : (16 * 1024);
+ tmp = realloc(*buf, bufmax);
+ if (!tmp)
+ break;
+ *buf = tmp;
+ bufptr = tmp + *bufsiz;
+ }
+
+ errno = 0;
+ ret = read(fd, bufptr, bufmax - *bufsiz);
+
+ if (ret < 0) {
+ /* error */
+ if ((errno == EAGAIN || errno == EINTR) && (ninters++ < 5)) {
+ xusleep(200000);
+ continue;
+ }
+ break;
+
+ } if (ret > 0) {
+ /* success -- verify no event during read */
+ struct pollfd fds[] = {
+ { .fd = fd, .events = POLLPRI }
+ };
+
+ rc = poll(fds, 1, 0);
+ if (rc < 0)
+ break; /* poll() error */
+ if (rc > 0) {
+ /* event -- read all again */
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ break;
+ *bufsiz = 0;
+ bufptr = *buf;
+ tries++;
+
+ if (tries > 10)
+ /* busy system? -- wait */
+ xusleep(10000);
+ continue;
+ }
+
+ /* successful read() without active poll() */
+ (*bufsiz) += (size_t) ret;
+ bufptr += ret;
+ tries = ninters = 0;
+ } else {
+ /* end-of-file */
+ goto success;
+ }
+ } while (tries <= 100);
+
+ rc = errno ? -errno : 1;
+ free(*buf);
+ return rc;
+
+success:
+ return 0;
+}
+
+/*
+ * Create FILE stream for data from read_procfs_file()
+ */
+FILE *mnt_get_procfs_memstream(int fd, char **membuf)
+{
+ size_t sz = 0;
+ off_t cur;
+
+ *membuf = NULL;
+
+ /* in case of error, rewind to the original position */
+ cur = lseek(fd, 0, SEEK_CUR);
+
+ if (read_procfs_file(fd, membuf, &sz) == 0 && sz > 0) {
+ FILE *memf = fmemopen(*membuf, sz, "r");
+ if (memf)
+ return memf; /* success */
+
+ free(*membuf);
+ *membuf = NULL;
+ }
+
+ /* error */
+ if (cur != (off_t) -1)
+ lseek(fd, cur, SEEK_SET);
+ return NULL;
+}
+#else
+FILE *mnt_get_procfs_memstream(int fd __attribute((__unused__)),
+ char **membuf __attribute((__unused__)))
+{
+ return NULL;
+}
+#endif /* HAVE_FMEMOPEN */
+
+
+#ifdef TEST_PROGRAM
+static int test_proc_read(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *buf = NULL;
+ char *filename = argv[1];
+ size_t bufsiz = 0;
+ int rc = 0, fd = open(filename, O_RDONLY);
+
+ if (fd <= 0) {
+ warn("%s: cannot open", filename);
+ return -errno;
+ }
+
+ rc = read_procfs_file(fd, &buf, &bufsiz);
+ close(fd);
+
+ switch (rc) {
+ case 0:
+ fwrite(buf, 1, bufsiz, stdout);
+ free(buf);
+ break;
+ case 1:
+ warnx("too many attempts");
+ break;
+ default:
+ warn("%s: cannot read", filename);
+ break;
+ }
+
+ return rc;
+}
+
+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_appendstr(struct libmnt_test *ts, int argc, char *argv[])
+{
+ char *str = strdup(argv[1]);
+ const char *ap = argv[2];
+
+ append_string(&str, ap);
+ printf("new string: '%s'\n", str);
+
+ free(str);
+ 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 = 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;
+}
+
+
+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>" },
+ { "--append-string", test_appendstr, "<string> <appendix>" },
+ { "--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>" },
+ { "--read-procfs", test_proc_read, "<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..b69b09a
--- /dev/null
+++ b/libmount/src/version.c
@@ -0,0 +1,154 @@
+/* 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"
+
+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_MTAB
+ "mtab",
+#endif
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ "namespaces",
+#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