diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
commit | 8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch) | |
tree | a9b2e7baeca1be40eb734371e3c8b11b02294497 /libmount/src | |
parent | Initial commit. (diff) | |
download | util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.tar.xz util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.zip |
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libmount/src')
-rw-r--r-- | libmount/src/Makemodule.am | 166 | ||||
-rw-r--r-- | libmount/src/btrfs.c | 175 | ||||
-rw-r--r-- | libmount/src/cache.c | 839 | ||||
-rw-r--r-- | libmount/src/context.c | 3162 | ||||
-rw-r--r-- | libmount/src/context_loopdev.c | 446 | ||||
-rw-r--r-- | libmount/src/context_mount.c | 1748 | ||||
-rw-r--r-- | libmount/src/context_umount.c | 1209 | ||||
-rw-r--r-- | libmount/src/fs.c | 1602 | ||||
-rw-r--r-- | libmount/src/init.c | 105 | ||||
-rw-r--r-- | libmount/src/iter.c | 78 | ||||
-rw-r--r-- | libmount/src/libmount.h.in | 988 | ||||
-rw-r--r-- | libmount/src/libmount.sym | 343 | ||||
-rw-r--r-- | libmount/src/lock.c | 721 | ||||
-rw-r--r-- | libmount/src/monitor.c | 978 | ||||
-rw-r--r-- | libmount/src/mountP.h | 461 | ||||
-rw-r--r-- | libmount/src/optmap.c | 255 | ||||
-rw-r--r-- | libmount/src/optstr.c | 1440 | ||||
-rw-r--r-- | libmount/src/tab.c | 2003 | ||||
-rw-r--r-- | libmount/src/tab_diff.c | 377 | ||||
-rw-r--r-- | libmount/src/tab_parse.c | 1217 | ||||
-rw-r--r-- | libmount/src/tab_update.c | 1065 | ||||
-rw-r--r-- | libmount/src/test.c | 65 | ||||
-rw-r--r-- | libmount/src/utils.c | 1315 | ||||
-rw-r--r-- | libmount/src/version.c | 151 |
24 files changed, 20909 insertions, 0 deletions
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am new file mode 100644 index 0000000..a59b98b --- /dev/null +++ b/libmount/src/Makemodule.am @@ -0,0 +1,166 @@ + +# 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_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) + +libmount_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_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..7adadb8 --- /dev/null +++ b/libmount/src/cache.c @@ -0,0 +1,839 @@ +/* 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 allows 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; + else { + 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..b9c18d9 --- /dev/null +++ b/libmount/src/context.c @@ -0,0 +1,3162 @@ +/* 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); + + mnt_unref_table(cxt->fstab); + mnt_unref_cache(cxt->cache); + + 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, 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); + + return 0; +} + +/** + * 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_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,PREPAND,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 allows 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; + + 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_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 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 allows 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 allows 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 allows 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 allows 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)) + 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 loop device + */ + 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; +} + +/* create a mountpoint if X-mount.mkdir[=<mode>] specified */ +static int mkdir_target(const char *tgt, struct libmnt_fs *fs) +{ + char *mstr = NULL; + size_t mstr_sz = 0; + mode_t mode = 0; + struct stat st; + int rc; + + assert(tgt); + assert(fs); + + 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; + + DBG(CXT, ul_debug("mkdir %s (%s) wanted", tgt, mstr)); + + if (mnt_stat_mountpoint(tgt, &st) == 0) + return 0; + + 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)); + return -MNT_ERR_MOUNTOPT; + } + } + + if (!mode) + mode = S_IRWXU | /* 0755 */ + S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; + + rc = mkdir_p(tgt, mode); + if (rc) + DBG(CXT, ul_debug("mkdir %s failed: %m", tgt)); + + return rc; +} + +int mnt_context_prepare_target(struct libmnt_context *cxt) +{ + const char *tgt; + struct libmnt_cache *cache; + int rc = 0; + struct libmnt_ns *ns_old; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, ul_debugobj(cxt, "preparing target path")); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + + ns_old = mnt_context_switch_target_ns(cxt); + if (!ns_old) + return -MNT_ERR_NAMESPACE; + + /* mkdir target */ + if (cxt->action == MNT_ACT_MOUNT + && !mnt_context_is_restricted(cxt) + && (cxt->user_mountflags & MNT_MS_XCOMMENT || + cxt->user_mountflags & MNT_MS_XFSTABCOMM)) { + + rc = mkdir_target(tgt, cxt->fs); + if (rc) { + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return rc; /* mkdir or parse error */ + } + } + + /* canonicalize the path */ + 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; + + if (rc) + DBG(CXT, ul_debugobj(cxt, "failed to prepare target '%s'", tgt)); + else + DBG(CXT, ul_debugobj(cxt, "final target '%s'", + mnt_fs_get_target(cxt->fs))); + return 0; +} + +/* 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 = mnt_fs_get_srcpath(cxt->fs); + + *type = NULL; + + if (!dev) + goto done; + + 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 (cache && *type) + *type = strdup(*type); + if (ambi) + rc = -MNT_ERR_AMBIFS; + } else { + DBG(CXT, ul_debugobj(cxt, "access(%s) failed [%m]", dev)); + if (strchr(dev, ':') != NULL) + *type = strdup("nfs"); + else if (!strncmp(dev, "//", 2)) + *type = strdup("cifs"); + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + +done: + 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; + + 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; + + path = strtok_r(search_path, ":", &p); + while (path) { + char helper[PATH_MAX]; + struct stat st; + int rc; + + rc = snprintf(helper, sizeof(helper), "%s/%s.%s", + path, name, type); + path = strtok_r(NULL, ":", &p); + + if (rc < 0 || (size_t) rc >= sizeof(helper)) + continue; + + rc = stat(helper, &st); + if (rc == -1 && errno == ENOENT && strchr(type, '.')) { + /* If type ends with ".subtype" try without it */ + char *hs = strrchr(helper, '.'); + if (hs) + *hs = '\0'; + rc = stat(helper, &st); + } + + DBG(CXT, ul_debugobj(cxt, "%-25s ... %s", helper, + rc ? "not found" : "found")); + if (rc) + continue; + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + free(cxt->helper); + cxt->helper = strdup(helper); + if (!cxt->helper) + return -ENOMEM; + return 0; + } + + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + return 0; +} + +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; +} + +static int apply_table(struct libmnt_context *cxt, struct libmnt_table *tb, + int direction) +{ + struct libmnt_fs *fs = NULL; + const char *src, *tgt; + int rc; + + 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 */ + + DBG(CXT, ul_debugobj(cxt, "apply entry:")); + DBG(CXT, mnt_fs_print_debug(fs, stderr)); + + /* copy from tab 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) + return rc; + + 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; + return rc; +} + +/** + * 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: ignore=%d, append=%d, prepend=%d, " + "replace=%d, force=%d, fstab=%d, mtab=%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, + 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) + vsnprintf(buf, bufsz, fmt, va); + + 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 */ + } else if (rc) + return rc; + + *mounted = mnt_table_is_fs_mounted(mtab, fs); + + 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 curremt 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 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>" }, + { 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..f914c9b --- /dev/null +++ b/libmount/src/context_mount.c @@ -0,0 +1,1748 @@ +/* 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); + + 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; + + DBG(CXT, ul_debugobj(cxt, "mount: fixing optstr")); + + fs = cxt->fs; + + /* + * 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 allow to remount 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 && cxt->restricted && (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->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_prepare_target(cxt); + if (!rc) + rc = mnt_context_guess_fstype(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 allows 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 + if (!mnt_context_switch_ns(cxt, ns_old)) + return -MNT_ERR_NAMESPACE; + + return res; +} + +/** + * 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; try mount 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 */ + { + 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; + char *pattern; + int rc, mounted = 0; + + if (ignored) + *ignored = 0; + if (mntrc) + *mntrc = 0; + + if (!cxt || !fs || !itr) + return -EINVAL; + + mtab = cxt->mtab; + cxt->mtab = NULL; /* do not reset mtab */ + mnt_reset_context(cxt); + cxt->mtab = mtab; + + 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; + } + + 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 */ + + rc = mnt_context_set_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. + */ + 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; +} + +/* + * 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: device 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; + } else if (rc == -MNT_ERR_NAMESPACE) { + if (buf) + snprintf(buf, bufsz, _("filesystem was mounted, but failed to switch namespace back")); + return MNT_EX_SYSERR; + + } else 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 (stat(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: + { + struct libmnt_table *tb; + + if (!buf) + break; + if (mflags & MS_REMOUNT) { + snprintf(buf, bufsz, _("mount point is busy")); + break; + } + 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)) { + snprintf(buf, bufsz, _("%s already mounted on %s"), s, t); + break; + } + } + } + if (!*buf) + snprintf(buf, bufsz, _("%s already mounted or mount point busy"), src); + break; + } + case ENOENT: + if (tgt && lstat(tgt, &st)) { + if (buf) + snprintf(buf, bufsz, _("mount point does not exist")); + } else if (tgt && stat(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 (stat(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..d72f5cc --- /dev/null +++ b/libmount/src/context_umount.c @@ -0,0 +1,1209 @@ +/* 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 + +/** + * 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) +{ + int rc; + struct libmnt_ns *ns_old; + struct libmnt_table *mtab = NULL; + struct libmnt_fs *fs; + char *loopdev = NULL; + + 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 */ + + /* + * 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 allows 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)); + } + } + + if (pfs) + *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; +} + +/* 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; +} + +/* this is umount replacement to mnt_context_apply_fstab(), use + * mnt_context_tab_applied() to check result. + */ +static int lookup_umount_fs(struct libmnt_context *cxt) +{ + const char *tgt; + struct stat st; + struct libmnt_fs *fs = NULL; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) { + DBG(CXT, ul_debugobj(cxt, "umount: undefined target")); + return -EINVAL; + } + + /* + * 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)) { + + const char *type = mnt_fs_get_fstype(cxt->fs); + + DBG(CXT, ul_debugobj(cxt, "umount: disable mtab")); + + /* !mnt_context_mtab_writable(cxt) && has_utab_entry() verified that there + * is no stuff in utab, so disable all mtab/utab related actions */ + mnt_context_disable_mtab(cxt, TRUE); + + if (!type) { + struct statfs vfs; + + DBG(CXT, ul_debugobj(cxt, "umount: trying statfs()")); + if (statfs(tgt, &vfs) == 0) + type = mnt_statfs_get_fstype(&vfs); + if (type) { + rc = mnt_fs_set_fstype(cxt->fs, type); + if (rc) + return rc; + } + } + if (type) { + DBG(CXT, ul_debugobj(cxt, + "umount: mountinfo unnecessary [type=%s]", type)); + return 0; + } + } + + rc = mnt_context_find_umount_fs(cxt, tgt, &fs); + if (rc < 0) + return rc; + + if (rc == 1 || !fs) { + DBG(CXT, ul_debugobj(cxt, "umount: cannot find '%s' in mtab", tgt)); + return 0; /* this is correct! */ + } + + if (fs != cxt->fs) { + /* copy from mtab to our FS description + */ + 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, "umount: failed to copy FS")); + return -errno; + } + DBG(CXT, ul_debugobj(cxt, "umount: mtab applied")); + } + + cxt->flags |= MNT_FL_TAB_APPLIED; + return rc; +} + +/* check if @devname is loopdev and if the device is associated + * with a source from @fstab_fs + */ +static int is_associated_fs(const char *devname, struct libmnt_fs *fs) +{ + uintmax_t offset = 0; + const char *src, *optstr; + char *val; + size_t valsz; + int flags = 0; + + /* check if it begins with /dev/loop */ + if (strncmp(devname, _PATH_DEV_LOOP, sizeof(_PATH_DEV_LOOP) - 1)) + 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; +} + +/* + * 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, 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> */ + } + + /* + * 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)) + 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 (cxt->restricted && !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; + } else if (rc == -MNT_ERR_LOCK) { + if (buf) + snprintf(buf, bufsz, _("locking failed")); + return MNT_EX_FILEIO; + } else 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")); + + } else 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; + } else if (rc == -MNT_ERR_NAMESPACE) { + if (buf) + snprintf(buf, bufsz, _("filesystem was unmounted, but failed to switch namespace back")); + return MNT_EX_SYSERR; + + } else if (rc < 0) + return mnt_context_get_generic_excode(rc, buf, bufsz, + _("filesystem was unmounted, but any subsequent operation failed: %m")); + + return MNT_EX_SOFTWARE; /* internal error */ + } + + /* + * umount(2) errors + */ + if (buf) { + int syserr = mnt_context_get_syscall_errno(cxt); + + switch (syserr) { + case ENXIO: + snprintf(buf, bufsz, _("invalid block device")); /* ??? */ + break; + case EINVAL: + snprintf(buf, bufsz, _("not mounted")); + break; + case EIO: + snprintf(buf, bufsz, _("can't write superblock")); + break; + case EBUSY: + snprintf(buf, bufsz, _("target is busy")); + break; + case ENOENT: + snprintf(buf, bufsz, _("no mount point specified")); + break; + case EPERM: + snprintf(buf, bufsz, _("must be superuser to unmount")); + break; + case EACCES: + snprintf(buf, bufsz, _("block devices are not permitted on filesystem")); + break; + default: + return mnt_context_get_generic_excode(syserr, buf, bufsz,_("umount(2) system call failed: %m")); + } + } + return MNT_EX_FAIL; +} diff --git a/libmount/src/fs.c b/libmount/src/fs.c new file mode 100644 index 0000000..9971d6a --- /dev/null +++ b/libmount/src/fs.c @@ -0,0 +1,1602 @@ +/* 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; +} + +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->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 (cpy_str_at_offset(n, fs, offsetof(struct libmnt_fs, source))) + goto err; + if (cpy_str_at_offset(n, fs, offsetof(struct libmnt_fs, target))) + goto err; + if (cpy_str_at_offset(n, fs, offsetof(struct libmnt_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 (cpy_str_at_offset(n, fs, offsetof(struct libmnt_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_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..a229222 --- /dev/null +++ b/libmount/src/init.c @@ -0,0 +1,105 @@ +/* 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" }, + + { 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..63ba322 --- /dev/null +++ b/libmount/src/libmount.h.in @@ -0,0 +1,988 @@ +/* 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)); + +/* 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_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_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_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 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 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_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) + +/* + * 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_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..ad7bd47 --- /dev/null +++ b/libmount/src/libmount.sym @@ -0,0 +1,343 @@ +/* + * 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; diff --git a/libmount/src/lock.c b/libmount/src/lock.c new file mode 100644 index 0000000..e6eefa1 --- /dev/null +++ b/libmount/src/lock.c @@ -0,0 +1,721 @@ +/* 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; + + 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 = fchmod(ml->lockfile_fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + 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; + } else { + /* 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..2383a73 --- /dev/null +++ b/libmount/src/monitor.c @@ -0,0 +1,978 @@ +/* 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; + } else 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; /* success */ +} + +/** + * 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..8f4fba7 --- /dev/null +++ b/libmount/src/mountP.h @@ -0,0 +1,461 @@ +/* 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_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_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_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path); +extern int mnt_stat_mountpoint(const char *target, struct stat *st); + +/* 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); + +/* + * 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; + + 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_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 */ + + 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 */ + +/* 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 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); + +/* 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..6136e27 --- /dev/null +++ b/libmount/src/optmap.c @@ -0,0 +1,255 @@ +/* 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 + { 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> */ + + { 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; + else 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)) + 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..c0f438f --- /dev/null +++ b/libmount/src/optstr.c @@ -0,0 +1,1440 @@ +/* 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; + } + } while (rc == 0 && opt && *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%08lu 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); + + else 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); + + else 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))) { + 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..d80fb05 --- /dev/null +++ b/libmount/src/tab.c @@ -0,0 +1,2003 @@ + +/* 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_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; + + mnt_ref_fs(fs); + list_add_tail(&fs->ents, &tb->ents); + tb->nents++; + + DBG(TAB, ul_debugobj(tb, "add entry: %s %s", + mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + return 0; +} + +/** + * 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) + return -EINVAL; + + list_del(&fs->ents); + INIT_LIST_HEAD(&fs->ents); /* otherwise FS still points to the list */ + + 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; + + 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; +} + +/** + * 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) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs; + + char *root = NULL; + char *src2 = NULL; + const char *src = NULL, *tgt = NULL; + char *xtgt = 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) { + const char *unc_subdir = get_cifs_unc_subdir_path(src); + const char *path_on_fs = mnt_fs_get_root(fs); + if (!unc_subdir || !path_on_fs || !streq_paths(unc_subdir, path_on_fs)) + 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 (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); + + DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc)); + free(src2); + return rc; +} + +#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(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-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..70440bf --- /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(&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)) || (f1 && f2 && strcmp(f1, f2))) + 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..45ca459 --- /dev/null +++ b/libmount/src/tab_parse.c @@ -0,0 +1,1217 @@ +/* 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_number(const char *s, int *num, int *ok) +{ + char *end = NULL; + + assert(num); + assert(s); + assert(ok); + + *ok = 0; + s = skip_blank(s); + if (!s || !*s) + return s; + + *num = strtol(s, &end, 10); + if (end == NULL || s == end) + return s; + + /* valid end of number is a space or a terminator */ + if (*end == ' ' || *end == '\t' || *end == '\0') + *ok = 1; + + return end; +} + +/* + * Parses one line from {fs,m}tab + */ +static int mnt_parse_table_line(struct libmnt_fs *fs, const char *s) +{ + int rc, n = 0, xrc; + char *src = NULL, *fstype = NULL, *optstr = NULL; + + rc = sscanf(s, UL_SCNsA" " /* (1) source */ + UL_SCNsA" " /* (2) target */ + UL_SCNsA" " /* (3) FS type */ + UL_SCNsA" " /* (4) options */ + "%n", /* byte count */ + + &src, + &fs->target, + &fstype, + &optstr, + &n); + xrc = rc; + + if (rc == 3 || rc == 4) { /* options are optional */ + unmangle_string(src); + unmangle_string(fs->target); + unmangle_string(fstype); + + if (optstr && *optstr) + unmangle_string(optstr); + + /* note that __foo functions do not reallocate the string + */ + rc = __mnt_fs_set_source_ptr(fs, src); + if (!rc) { + src = NULL; + rc = __mnt_fs_set_fstype_ptr(fs, fstype); + if (!rc) + fstype = NULL; + } + if (!rc && optstr) + rc = mnt_fs_set_options(fs, optstr); + free(optstr); + optstr = NULL; + } else { + DBG(TAB, ul_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); + rc = -EINVAL; + } + + if (rc) { + free(src); + free(fstype); + free(optstr); + DBG(TAB, ul_debug("tab parse error: [set vars, rc=%d]\n", rc)); + return rc; /* error */ + } + + fs->passno = fs->freq = 0; + + if (xrc == 4 && n) + s = skip_blank(s + n); + if (xrc == 4 && *s) { + int ok = 0; + + s = next_number(s, &fs->freq, &ok); + if (!ok) { + if (s && *s) { + DBG(TAB, ul_debug("tab parse error: [freq]")); + rc = -EINVAL; + } + } else { + s = next_number(s, &fs->passno, &ok); + if (!ok && s && *s) { + DBG(TAB, ul_debug("tab parse error: [passno]")); + rc = -EINVAL; + } + } + } + + return rc; +} + +/* + * Parses one line from a mountinfo file + */ +static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, const char *s) +{ + int rc, end = 0; + unsigned int maj, min; + char *fstype = NULL, *src = NULL; + const char *p; + + rc = sscanf(s, "%d " /* (1) id */ + "%d " /* (2) parent */ + "%u:%u " /* (3) maj:min */ + UL_SCNsA" " /* (4) mountroot */ + UL_SCNsA" " /* (5) target */ + UL_SCNsA /* (6) vfs options (fs-independent) */ + "%n", /* number of read bytes */ + + &fs->id, + &fs->parent, + &maj, &min, + &fs->root, + &fs->target, + &fs->vfs_optstr, + &end); + + if (rc >= 7 && end > 0) + s += end; + + /* (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 = p + 3; + + end = 0; + rc += sscanf(s, UL_SCNsA"%n", /* (8) FS type */ + + &fstype, + &end); + + if (rc >= 8 && end > 0) + s += end; + if (s[0] == ' ') + s++; + + /* (9) source can unfortunately be an empty string "" and scanf does + * not work well with empty string. Test with: + * $ sudo mount -t tmpfs "" /tmp/bb + * $ mountpoint /tmp/bb + * */ + if (s[0] == ' ') { + src = strdup(""); + s++; + rc++; + rc += sscanf(s, UL_SCNsA, /* (10) fs options (fs specific) */ + + &fs->fs_optstr); + } else { + rc += sscanf(s, UL_SCNsA" " /* (9) source */ + UL_SCNsA, /* (10) fs options (fs specific) */ + + &src, + &fs->fs_optstr); + } + + if (rc >= 10) { + size_t sz; + + fs->flags |= MNT_FS_KERNEL; + fs->devno = makedev(maj, min); + + /* remove "\040(deleted)" suffix */ + sz = strlen(fs->target); + if (sz > PATH_DELETED_SUFFIX_SZ) { + char *ptr = fs->target + (sz - PATH_DELETED_SUFFIX_SZ); + + if (strcmp(ptr, PATH_DELETED_SUFFIX) == 0) + *ptr = '\0'; + } + + unmangle_string(fs->root); + unmangle_string(fs->target); + unmangle_string(fs->vfs_optstr); + unmangle_string(fstype); + unmangle_string(src); + unmangle_string(fs->fs_optstr); + + rc = __mnt_fs_set_fstype_ptr(fs, fstype); + if (!rc) { + fstype = NULL; + rc = __mnt_fs_set_source_ptr(fs, src); + if (!rc) + src = NULL; + } + + /* merge VFS and FS options to one string */ + fs->optstr = mnt_fs_strdup_options(fs); + if (!fs->optstr) + rc = -ENOMEM; + } else { + DBG(TAB, ul_debug( + "mountinfo parse error [sscanf rc=%d]: '%s'", rc, s)); + rc = -EINVAL; + } + + free(fstype); + free(src); + + 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; + __mnt_fs_set_source_ptr(fs, 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) +{ + uintmax_t fsz, usz; + int rc; + char *src = NULL; + + rc = sscanf(s, UL_SCNsA" " /* (1) source */ + UL_SCNsA" " /* (2) type */ + "%ju" /* (3) size */ + "%ju" /* (4) used */ + "%d", /* priority */ + + &src, + &fs->swaptype, + &fsz, + &usz, + &fs->priority); + + if (rc == 5) { + size_t sz; + + fs->size = fsz; + fs->usedsize = usz; + + /* remove "\040(deleted)" suffix */ + sz = strlen(src); + if (sz > PATH_DELETED_SUFFIX_SZ) { + char *p = src + (sz - PATH_DELETED_SUFFIX_SZ); + if (strcmp(p, PATH_DELETED_SUFFIX) == 0) + *p = '\0'; + } + + unmangle_string(src); + + rc = mnt_fs_set_source(fs, src); + if (!rc) + mnt_fs_set_fstype(fs, "swap"); + } else { + DBG(TAB, ul_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); + rc = -EINVAL; + } + + free(src); + + 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; +} + +/** + * mnt_table_parse_stream: + * @tb: tab pointer + * @f: file stream + * @filename: filename used for debug and error messages + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_parse_stream(struct libmnt_table *tb, FILE *f, const char *filename) +{ + int rc = -1; + int flags = 0; + pid_t tid = -1; + struct libmnt_fs *fs = NULL; + 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; + + while (!feof(f)) { + if (!fs) { + fs = mnt_new_fs(); + if (!fs) + goto err; + } + + rc = mnt_table_parse_next(&pa, tb, fs); + + if (!rc && tb->fltrcb && tb->fltrcb(fs, tb->fltrcb_data)) + rc = 1; /* filtered out by callback... */ + + if (!rc) { + 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); + } + } + + if (rc) { + if (rc > 0) { + mnt_reset_fs(fs); + assert(fs->refcount == 1); + continue; /* recoverable error, reuse fs*/ + } + + mnt_unref_fs(fs); + if (feof(f)) + break; + goto err; /* fatal error */ + } + mnt_unref_fs(fs); + fs = NULL; + } + + DBG(TAB, ul_debugobj(tb, "%s: stop parsing (%d entries)", + filename, mnt_table_get_nents(tb))); + parser_cleanup(&pa); + return 0; +err: + DBG(TAB, ul_debugobj(tb, "%s: parse error (rc=%d)", filename, rc)); + parser_cleanup(&pa); + return rc; +} + +/** + * mnt_table_parse_file: + * @tb: tab pointer + * @filename: file + * + * Parses the whole table (e.g. /etc/fstab) and appends new records to the @tab. + * + * The libmount parser ignores broken (syntax error) lines, these lines are + * reported to the caller by the errcb() function (see mnt_table_set_parser_errcb()). + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_table_parse_file(struct libmnt_table *tb, const char *filename) +{ + FILE *f; + int rc; + + if (!filename || !tb) + return -EINVAL; + + f = fopen(filename, "r" UL_CLOEXECSTR); + if (f) { + rc = mnt_table_parse_stream(tb, f, filename); + fclose(f); + } else + rc = -errno; + + DBG(TAB, ul_debugobj(tb, "parsing done [filename=%s, rc=%d]", filename, rc)); + return rc; +} + +static int mnt_table_parse_dir_filter(const struct dirent *d) +{ + size_t namesz; + +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && + d->d_type != DT_LNK) + return 0; +#endif + if (*d->d_name == '.') + return 0; + +#define MNT_MNTTABDIR_EXTSIZ (sizeof(MNT_MNTTABDIR_EXT) - 1) + + namesz = strlen(d->d_name); + if (!namesz || namesz < MNT_MNTTABDIR_EXTSIZ + 1 || + strcmp(d->d_name + (namesz - MNT_MNTTABDIR_EXTSIZ), + MNT_MNTTABDIR_EXT)) + return 0; + + /* Accept this */ + return 1; +} + +#ifdef HAVE_SCANDIRAT +static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + int n = 0, i; + int dd; + struct dirent **namelist = NULL; + + dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dd < 0) + return -errno; + + n = scandirat(dd, ".", &namelist, mnt_table_parse_dir_filter, versionsort); + if (n <= 0) { + close(dd); + return 0; + } + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + struct stat st; + FILE *f; + + if (fstatat(dd, d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + mnt_table_parse_stream(tb, f, d->d_name); + fclose(f); + } + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + close(dd); + return 0; +} +#else +static int __mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + int n = 0, i, r = 0; + DIR *dir = NULL; + struct dirent **namelist = NULL; + + n = scandir(dirname, &namelist, mnt_table_parse_dir_filter, versionsort); + if (n <= 0) + return 0; + + /* let's use "at" functions rather than playing crazy games with paths... */ + dir = opendir(dirname); + if (!dir) { + r = -errno; + goto out; + } + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + struct stat st; + FILE *f; + + if (fstatat(dirfd(dir), d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dirfd(dir), d->d_name, + O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR); + if (f) { + mnt_table_parse_stream(tb, f, d->d_name); + fclose(f); + } + } + +out: + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + if (dir) + closedir(dir); + return r; +} +#endif + +/** + * mnt_table_parse_dir: + * @tb: mount table + * @dirname: directory + * + * The directory: + * - files are sorted by strverscmp(3) + * - files that start with "." are ignored (e.g. ".10foo.fstab") + * - files without the ".fstab" extension are ignored + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_parse_dir(struct libmnt_table *tb, const char *dirname) +{ + return __mnt_table_parse_dir(tb, dirname); +} + +struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt, int empty_for_enoent) +{ + struct libmnt_table *tb; + 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 || stat(filename, &st)) + return -EINVAL; + + 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; + + 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) { + /* 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..9d6047f --- /dev/null +++ b/libmount/src/utils.c @@ -0,0 +1,1315 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com> + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +/** + * SECTION: utils + * @title: Utils + * @short_description: misc utils. + */ +#include <ctype.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <blkid.h> + +#include "strutils.h" +#include "pathnames.h" +#include "mountP.h" +#include "mangle.h" +#include "canonicalize.h" +#include "env.h" +#include "match.h" +#include "fileutils.h" +#include "statfs_magic.h" +#include "sysfs.h" + +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("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 +} + +/* + * 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", + "autofs", + "bdev", + "binfmt_misc", + "cgroup", + "cgroup2", + "configfs", + "cpuset", + "debugfs", + "devfs", + "devpts", + "devtmpfs", + "dlmfs", + "efivarfs", + "fuse.gvfs-fuse-daemon", + "fusectl", + "hugetlbfs", + "mqueue", + "nfsd", + "none", + "nsfs", + "overlay", + "pipefs", + "proc", + "pstore", + "ramfs", + "rootfs", + "rpc_pipefs", + "securityfs", + "sockfs", + "spufs", + "sysfs", + "tmpfs" + }; + + 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, "smbfs") == 0 || + strncmp(type,"nfs", 3) == 0 || + strcmp(type, "afs") == 0 || + strcmp(type, "ncpfs") == 0 || + strncmp(type,"9p", 2) == 0) + return 1; + return 0; +} + +const char *mnt_statfs_get_fstype(struct statfs *vfs) +{ + assert(vfs); + + switch (vfs->f_type) { + case STATFS_ADFS_MAGIC: return "adfs"; + case STATFS_AFFS_MAGIC: return "affs"; + case STATFS_AFS_MAGIC: return "afs"; + case STATFS_AUTOFS_MAGIC: return "autofs"; + case STATFS_BDEVFS_MAGIC: return "bdev"; + case STATFS_BEFS_MAGIC: return "befs"; + case STATFS_BFS_MAGIC: return "befs"; + case STATFS_BINFMTFS_MAGIC: return "binfmt_misc"; + case STATFS_BTRFS_MAGIC: return "btrfs"; + case STATFS_CEPH_MAGIC: return "ceph"; + case STATFS_CGROUP_MAGIC: return "cgroup"; + case STATFS_CIFS_MAGIC: return "cifs"; + case STATFS_CODA_MAGIC: return "coda"; + case STATFS_CONFIGFS_MAGIC: return "configfs"; + case STATFS_CRAMFS_MAGIC: return "cramfs"; + case STATFS_DEBUGFS_MAGIC: return "debugfs"; + case STATFS_DEVPTS_MAGIC: return "devpts"; + case STATFS_ECRYPTFS_MAGIC: return "ecryptfs"; + case STATFS_EFIVARFS_MAGIC: return "efivarfs"; + case STATFS_EFS_MAGIC: return "efs"; + case STATFS_EXOFS_MAGIC: return "exofs"; + case STATFS_EXT4_MAGIC: return "ext4"; /* all extN use the same magic */ + case STATFS_F2FS_MAGIC: return "f2fs"; + case STATFS_FUSE_MAGIC: return "fuse"; + case STATFS_FUTEXFS_MAGIC: return "futexfs"; + case STATFS_GFS2_MAGIC: return "gfs2"; + case STATFS_HFSPLUS_MAGIC: return "hfsplus"; + case STATFS_HOSTFS_MAGIC: return "hostfs"; + case STATFS_HPFS_MAGIC: return "hpfs"; + case STATFS_HPPFS_MAGIC: return "hppfs"; + case STATFS_HUGETLBFS_MAGIC: return "hugetlbfs"; + case STATFS_ISOFS_MAGIC: return "iso9660"; + case STATFS_JFFS2_MAGIC: return "jffs2"; + case STATFS_JFS_MAGIC: return "jfs"; + case STATFS_LOGFS_MAGIC: return "logfs"; + case STATFS_MINIX2_MAGIC: + case STATFS_MINIX2_MAGIC2: + case STATFS_MINIX3_MAGIC: + case STATFS_MINIX_MAGIC: + case STATFS_MINIX_MAGIC2: return "minix"; + case STATFS_MQUEUE_MAGIC: return "mqueue"; + case STATFS_MSDOS_MAGIC: return "vfat"; + case STATFS_NCP_MAGIC: return "ncp"; + case STATFS_NFS_MAGIC: return "nfs"; + case STATFS_NILFS_MAGIC: return "nilfs2"; + case STATFS_NTFS_MAGIC: return "ntfs"; + case STATFS_OCFS2_MAGIC: return "ocfs2"; + case STATFS_OMFS_MAGIC: return "omfs"; + case STATFS_OPENPROMFS_MAGIC: return "openpromfs"; + case STATFS_PIPEFS_MAGIC: return "pipefs"; + case STATFS_PROC_MAGIC: return "proc"; + case STATFS_PSTOREFS_MAGIC: return "pstore"; + case STATFS_QNX4_MAGIC: return "qnx4"; + case STATFS_QNX6_MAGIC: return "qnx6"; + case STATFS_RAMFS_MAGIC: return "ramfs"; + case STATFS_REISERFS_MAGIC: return "reiser4"; + case STATFS_ROMFS_MAGIC: return "romfs"; + case STATFS_SECURITYFS_MAGIC: return "securityfs"; + case STATFS_SELINUXFS_MAGIC: return "selinuxfs"; + case STATFS_SMACKFS_MAGIC: return "smackfs"; + case STATFS_SMB_MAGIC: return "smb"; + case STATFS_SOCKFS_MAGIC: return "sockfs"; + case STATFS_SQUASHFS_MAGIC: return "squashfs"; + case STATFS_SYSFS_MAGIC: return "sysfs"; + case STATFS_TMPFS_MAGIC: return "tmpfs"; + case STATFS_UBIFS_MAGIC: return "ubifs"; + case STATFS_UDF_MAGIC: return "udf"; + case STATFS_UFS2_MAGIC: + case STATFS_UFS_MAGIC: return "ufs"; + case STATFS_V9FS_MAGIC: return "9p"; + case STATFS_VXFS_MAGIC: return "vxfs"; + case STATFS_XENFS_MAGIC: return "xenfs"; + case STATFS_XFS_MAGIC: return "xfs"; + default: + break; + } + + return NULL; +} + + +/** + * mnt_match_fstype: + * @type: filesystem type + * @pattern: filesystem name or comma delimited list of names + * + * The @pattern list of filesystems can be prefixed with a global + * "no" prefix to invert matching of the whole list. The "no" could + * also be used for individual items in the @pattern list. So, + * "nofoo,bar" has the same meaning as "nofoo,nobar". + * + * "bar" : "nofoo,bar" -> False (global "no" prefix) + * + * "bar" : "foo,bar" -> True + * + * "bar" : "foo,nobar" -> False + * + * Returns: 1 if type is matching, else 0. This function also returns + * 0 if @pattern is NULL and @type is non-NULL. + */ +int mnt_match_fstype(const char *type, const char *pattern) +{ + return match_fstype(type, pattern); +} + +void mnt_free_filesystems(char **filesystems) +{ + char **p; + + if (!filesystems) + return; + for (p = filesystems; *p; p++) + free(*p); + free(filesystems); +} + +static int add_filesystem(char ***filesystems, char *name) +{ + int n = 0; + + assert(filesystems); + assert(name); + + if (*filesystems) { + char **p; + for (n = 0, p = *filesystems; *p; p++, n++) { + if (strcmp(*p, name) == 0) + return 0; + } + } + + #define MYCHUNK 16 + + if (n == 0 || !((n + 1) % MYCHUNK)) { + size_t items = ((n + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK; + char **x = realloc(*filesystems, items * sizeof(char *)); + + if (!x) + goto err; + *filesystems = x; + } + name = strdup(name); + (*filesystems)[n] = name; + (*filesystems)[n + 1] = NULL; + if (!name) + goto err; + return 0; +err: + mnt_free_filesystems(*filesystems); + return -ENOMEM; +} + +static int get_filesystems(const char *filename, char ***filesystems, const char *pattern) +{ + int rc = 0; + FILE *f; + char line[129]; + + f = fopen(filename, "r" UL_CLOEXECSTR); + if (!f) + return 1; + + DBG(UTILS, ul_debug("reading filesystems list from: %s", filename)); + + while (fgets(line, sizeof(line), f)) { + char name[sizeof(line)]; + + if (*line == '#' || strncmp(line, "nodev", 5) == 0) + continue; + if (sscanf(line, " %128[^\n ]\n", name) != 1) + continue; + if (strcmp(name, "*") == 0) { + rc = 1; + break; /* end of the /etc/filesystems */ + } + if (pattern && !mnt_match_fstype(name, pattern)) + continue; + rc = add_filesystem(filesystems, name); + if (rc) + break; + } + + fclose(f); + return rc; +} + +/* + * Always check the @filesystems pointer! + * + * man mount: + * + * ...mount will try to read the file /etc/filesystems, or, if that does not + * exist, /proc/filesystems. All of the filesystem types listed there will + * be tried, except for those that are labeled "nodev" (e.g., devpts, + * proc and nfs). If /etc/filesystems ends in a line with a single * only, + * mount will read /proc/filesystems afterwards. + */ +int mnt_get_filesystems(char ***filesystems, const char *pattern) +{ + int rc; + + if (!filesystems) + return -EINVAL; + + *filesystems = NULL; + + rc = get_filesystems(_PATH_FILESYSTEMS, filesystems, pattern); + if (rc != 1) + return rc; + + rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern); + if (rc == 1 && *filesystems) + rc = 0; /* /proc/filesystems not found */ + + return rc; +} + +/* + * Returns an allocated string with username or NULL. + */ +char *mnt_get_username(const uid_t uid) +{ + struct passwd pwd; + struct passwd *res; + char *buf, *username = NULL; + + buf = malloc(UL_GETPW_BUFSIZ); + if (!buf) + return NULL; + + if (!getpwuid_r(uid, &pwd, buf, UL_GETPW_BUFSIZ, &res) && res) + username = strdup(pwd.pw_name); + + free(buf); + return username; +} + +int mnt_get_uid(const char *username, uid_t *uid) +{ + int rc = -1; + struct passwd pwd; + struct passwd *pw; + char *buf; + + if (!username || !uid) + return -EINVAL; + + buf = malloc(UL_GETPW_BUFSIZ); + if (!buf) + return -ENOMEM; + + if (!getpwnam_r(username, &pwd, buf, UL_GETPW_BUFSIZ, &pw) && pw) { + *uid= pw->pw_uid; + rc = 0; + } else { + DBG(UTILS, ul_debug( + "cannot convert '%s' username to UID", username)); + 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; + } else if (errno != ENOENT) { + DBG(UTILS, ul_debug(" access FAILED")); + return -errno; + } else 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; + } else +#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 maybe 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; +} + +/* + * 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 + */ +int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path) +{ + char buf[PATH_MAX]; + char *dev = NULL, *spec = NULL; + unsigned int x, y; + int allocated = 0; + + assert(path); + + DBG(UTILS, ul_debug("guessing system root [devno %u:%u]", major(devno), minor(devno))); + + /* The pseudo-fs, net-fs or btrfs devno is useless, otherwise it + * usually matches with the source device, let's try to use it. + */ + if (major(devno) > 0) { + dev = sysfs_devno_to_devpath(devno, buf, sizeof(buf)); + if (dev) { + DBG(UTILS, ul_debug(" devno converted to %s", dev)); + goto done; + } + } + + /* Let's try to use root= kernel command line option + */ + spec = mnt_get_kernel_cmdline_option("root="); + if (!spec) + goto done; + + /* maj:min notation */ + if (sscanf(spec, "%u:%u", &x, &y) == 2) { + dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf)); + if (dev) { + DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev)); + goto done; + } + + /* hexhex notation */ + } else if (isxdigit_string(spec)) { + char *end = NULL; + uint32_t n; + + errno = 0; + n = strtoul(spec, &end, 16); + + if (errno || spec == end || (end && *end)) + DBG(UTILS, ul_debug(" failed to parse root='%s'", spec)); + else { + /* kernel new_decode_dev() */ + x = (n & 0xfff00) >> 8; + y = (n & 0xff) | ((n >> 12) & 0xfff00); + dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf)); + if (dev) { + DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev)); + goto done; + } + } + + /* devname or PARTUUID= etc. */ + } else { + DBG(UTILS, ul_debug(" converting root='%s'", spec)); + + dev = mnt_resolve_spec(spec, cache); + if (dev && !cache) + allocated = 1; + } +done: + free(spec); + if (dev) { + *path = allocated ? dev : strdup(dev); + if (!*path) + return -ENOMEM; + return 0; + } + + return 1; +} + + +#ifdef TEST_PROGRAM +static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *type = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", mnt_match_fstype(type, pattern) ? "MATCH" : "NOT-MATCH"); + return 0; +} + +static int test_match_options(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", mnt_match_options(optstr, pattern) ? "MATCH" : "NOT-MATCH"); + return 0; +} + +static int test_startswith(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", startswith(optstr, pattern) ? "YES" : "NOT"); + return 0; +} + +static int test_endswith(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *optstr = argv[1]; + char *pattern = argv[2]; + + printf("%s\n", endswith(optstr, pattern) ? "YES" : "NOT"); + return 0; +} + +static int test_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>" }, + + { 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..5dde17b --- /dev/null +++ b/libmount/src/version.c @@ -0,0 +1,151 @@ +/* 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 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 |