diff options
Diffstat (limited to 'libmount/src')
-rw-r--r-- | libmount/src/Makemodule.am | 137 | ||||
-rw-r--r-- | libmount/src/cache.c | 697 | ||||
-rw-r--r-- | libmount/src/context.c | 2297 | ||||
-rw-r--r-- | libmount/src/context_loopdev.c | 368 | ||||
-rw-r--r-- | libmount/src/context_mount.c | 935 | ||||
-rw-r--r-- | libmount/src/context_umount.c | 856 | ||||
-rw-r--r-- | libmount/src/fs.c | 1501 | ||||
-rw-r--r-- | libmount/src/init.c | 45 | ||||
-rw-r--r-- | libmount/src/iter.c | 78 | ||||
-rw-r--r-- | libmount/src/libmount.h.in | 653 | ||||
-rw-r--r-- | libmount/src/libmount.sym | 250 | ||||
-rw-r--r-- | libmount/src/lock.c | 697 | ||||
-rw-r--r-- | libmount/src/mountP.h | 402 | ||||
-rw-r--r-- | libmount/src/optmap.c | 233 | ||||
-rw-r--r-- | libmount/src/optstr.c | 1303 | ||||
-rw-r--r-- | libmount/src/tab.c | 1195 | ||||
-rw-r--r-- | libmount/src/tab_diff.c | 362 | ||||
-rw-r--r-- | libmount/src/tab_parse.c | 944 | ||||
-rw-r--r-- | libmount/src/tab_update.c | 862 | ||||
-rw-r--r-- | libmount/src/test.c | 59 | ||||
-rw-r--r-- | libmount/src/utils.c | 991 | ||||
-rw-r--r-- | libmount/src/version.c | 127 |
22 files changed, 14992 insertions, 0 deletions
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am new file mode 100644 index 0000000..494e02a --- /dev/null +++ b/libmount/src/Makemodule.am @@ -0,0 +1,137 @@ + +# libmount.h is generated, so it's store in builddir! +mountincdir = $(includedir)/libmount +nodist_mountinc_HEADERS = $(top_builddir)/libmount/src/libmount.h + +usrlib_exec_LTLIBRARIES += libmount.la +libmount_la_SOURCES = \ + include/list.h \ + \ + libmount/src/cache.c \ + libmount/src/context.c \ + libmount/src/context_loopdev.c \ + libmount/src/context_mount.c \ + libmount/src/context_umount.c \ + libmount/src/fs.c \ + libmount/src/init.c \ + libmount/src/iter.c \ + libmount/src/lock.c \ + libmount/src/mountP.h \ + 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 \ + $(mountinc_HEADERS) + +nodist_libmount_la_SOURCES = libmount/src/mountP.h + +libmount_la_LIBADD = libcommon.la libblkid.la $(SELINUX_LIBS) + +libmount_la_CFLAGS = \ + -I$(ul_libblkid_incdir) \ + -I$(ul_libmount_incdir) \ + -I$(top_srcdir)/libmount/src + +libmount_la_DEPENDENCIES = \ + $(libmount_la_LIBADD) \ + libmount/src/libmount.sym \ + libmount/src/libmount.h.in + +libmount_la_LDFLAGS = \ + -Wl,--version-script=$(top_srcdir)/libmount/src/libmount.sym \ + -version-info $(LIBMOUNT_VERSION_INFO) + +EXTRA_DIST += \ + libmount/src/libmount.sym \ + libmount/src/libmount.h.in + +if BUILD_LIBMOUNT_TESTS +check_PROGRAMS += \ + test_mount_cache \ + test_mount_context \ + test_mount_lock \ + test_mount_optstr \ + test_mount_tab \ + test_mount_tab_diff \ + test_mount_tab_update \ + test_mount_utils \ + test_mount_version + +libmount_tests_cflags = -DTEST_PROGRAM $(libmount_la_CFLAGS) +libmount_tests_ldflags = libblkid.la -static +libmount_tests_ldadd = libmount.la $(UUID_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_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) + +endif # BUILD_LIBMOUNT_TESTS + + +# move lib from $(usrlib_execdir) to $(libdir) if needed +install-exec-hook-libmount: + if test "$(usrlib_execdir)" != "$(libdir)"; 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/cache.c b/libmount/src/cache.c new file mode 100644 index 0000000..34419cb --- /dev/null +++ b/libmount/src/cache.c @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2009-2011 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: cache + * @title: Cache + * @short_description: paths and tags (UUID/LABEL) caching + * + * The cache is a very simple API for work 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" + +/* + * 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; + + /* 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; +}; + +/** + * 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, mnt_debug_h(cache, "alloc")); + return cache; +} + +/** + * mnt_free_cache: + * @cache: pointer to struct libmnt_cache instance + * + * Deallocates the cache. + */ +void mnt_free_cache(struct libmnt_cache *cache) +{ + size_t i; + + if (!cache) + return; + + DBG(CACHE, mnt_debug_h(cache, "free")); + + 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); +} + +/* note that the @key could be tha 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, mnt_debug_h(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 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; + + assert(cache); + assert(path); + + 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 (strcmp(path, e->key) == 0) + 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; + + assert(cache); + assert(token); + assert(value); + + 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; + const char *tags[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" }; + const char *blktags[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" }; + + assert(cache); + assert(devname); + + if (!cache || !devname) + return -EINVAL; + + DBG(CACHE, mnt_debug_h(cache, "tags for %s requested", devname)); + + /* check is 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 has been already 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); + + if (blkid_do_safeprobe(pr)) + goto error; + + DBG(CACHE, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(cache, "\tread %zd tags", ntags)); + blkid_free_probe(pr); + return ntags ? 0 : 1; +error: + blkid_free_probe(pr); + return -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 it @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 && strcmp(path, devname) == 0) + return 1; + return 0; +} + +/** + * 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) +{ + if (!cache || !devname || !token) + return NULL; + + if (mnt_cache_read_tags(cache, devname) != 0) + return NULL; + + return cache_find_tag_value(cache, devname, token); +} + +/** + * 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, mnt_debug_h(cache, "get %s FS type", devname)); + + if (cache) + return mnt_cache_find_tag_value(cache, devname, "TYPE"); + + /* + * 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); + + 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; +} + +/** + * 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; + char *key = NULL; + char *value = NULL; + + /*DBG(CACHE, mnt_debug_h(cache, "resolving path %s", path));*/ + + if (!path) + return NULL; + if (cache) + p = (char *) cache_find_path(cache, path); + + if (!p) { + 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_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: new allocated string with path, result has to be always 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"); + + /* 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) { + if (!cache) + free(pretty); /* not cached, deallocate */ + return tmp; /* return backing file */ + } + } + loopcxt_deinit(&lc); + + } + +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; + + assert(token); + assert(value); + + /*DBG(CACHE, mnt_debug_h(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; + + if (!spec) + return NULL; + + if (strchr(spec, '=')) { + char *tag, *val; + + if (!blkid_parse_tag_string(spec, &tag, &val)) { + cn = mnt_resolve_tag(tag, val, cache); + + free(tag); + free(val); + } + } else + cn = mnt_resolve_path(spec, cache); + + return cn; +} + + +#ifdef TEST_PROGRAM + +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_free_cache(cache); + return 0; +} + +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_free_cache(cache); + return 0; +} + +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); + + 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 (strchr(line, '=')) { + char *tag, *val; + const char *cn = NULL; + + if (!blkid_parse_tag_string(line, &tag, &val)) { + cn = cache_find_tag(cache, tag, val); + + free(tag); + free(val); + } + 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_free_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..d6f9050 --- /dev/null +++ b/libmount/src/context.c @@ -0,0 +1,2297 @@ +/* + * Copyright (C) 2010,2011,2012 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * 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 <sys/wait.h> + +/** + * mnt_new_context: + * + * Returns: newly allocated mount context + */ +struct libmnt_context *mnt_new_context(void) +{ + struct libmnt_context *cxt; + uid_t ruid, euid; + + cxt = calloc(1, sizeof(*cxt)); + if (!cxt) + return NULL; + + ruid = getuid(); + euid = geteuid(); + + mnt_context_reset_status(cxt); + + cxt->loopdev_fd = -1; + + /* if we're really root and aren't running setuid */ + cxt->restricted = (uid_t) 0 == ruid && ruid == euid ? 0 : 1; + + DBG(CXT, mnt_debug_h(cxt, "----> allocate %s", + cxt->restricted ? "[RESTRICTED]" : "")); + + mnt_has_regular_mtab(&cxt->mtab_path, &cxt->mtab_writable); + + if (!cxt->mtab_writable) + /* use /run/mount/utab if /etc/mtab is useless */ + mnt_has_regular_utab(&cxt->utab_path, &cxt->utab_writable); + + 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); + + if (!(cxt->flags & MNT_FL_EXTERN_FSTAB)) + mnt_free_table(cxt->fstab); + if (!(cxt->flags & MNT_FL_EXTERN_CACHE)) + mnt_free_cache(cxt->cache); + + mnt_context_clear_loopdev(cxt); + mnt_free_lock(cxt->lock); + mnt_free_update(cxt->update); + + free(cxt->children); + + DBG(CXT, mnt_debug_h(cxt, "<---- free")); + free(cxt); +} + +/** + * mnt_reset_context: + * @cxt: mount context + * + * Resets all information in the context that are directly related to + * the latest mount (spec, source, target, mount options, ....) + * + * The match patters, cached fstab, cached canonicalized paths and tags and + * [e]uid are not reseted. 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); + * + * + * to reset these 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, mnt_debug_h(cxt, "<---- reset [status=%d] ---->", + mnt_context_get_status(cxt))); + + fl = cxt->flags; + + if (!(cxt->flags & MNT_FL_EXTERN_FS)) + mnt_free_fs(cxt->fs); + + mnt_free_table(cxt->mtab); + + free(cxt->helper); + free(cxt->orig_user); + + cxt->fs = NULL; + cxt->mtab = NULL; + cxt->ambi = 0; + cxt->helper = NULL; + cxt->orig_user = NULL; + cxt->mountflags = 0; + cxt->user_mountflags = 0; + cxt->mountdata = NULL; + cxt->flags = MNT_FL_DEFAULT; + + mnt_context_reset_status(cxt); + + /* restore non-resetable flags */ + cxt->flags |= (fl & MNT_FL_EXTERN_FSTAB); + cxt->flags |= (fl & MNT_FL_EXTERN_CACHE); + 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); + 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 set_flag(struct libmnt_context *cxt, int flag, int enable) +{ + if (!cxt) + return -EINVAL; + if (enable) { + DBG(CXT, mnt_debug_h(cxt, "enabling flag %04x", flag)); + cxt->flags |= flag; + } else { + DBG(CXT, mnt_debug_h(cxt, "disabling flag %04x", flag)); + cxt->flags &= ~flag; + } + return 0; +} + +/** + * mnt_context_is_restricted: + * @cxt: mount context + * + * Returns: 0 for unrestricted mount (user is root), or 1 for non-root mounts + */ +int mnt_context_is_restricted(struct libmnt_context *cxt) +{ + assert(cxt); + return cxt->restricted; +} + +/** + * mnt_context_set_optsmode + * @cxt: mount context + * @mode: MNT_OMASK_* flags + * + * Controls how to use mount optionsmsource 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 is 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 other is defined + * - the flags are eavaluated 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_OMASK_* mask or zero. + */ + +int mnt_context_get_optsmode(struct libmnt_context *cxt) +{ + return cxt ? cxt->optsmode : 0; +} + +/** + * mnt_context_disable_canonicalize: + * @cxt: mount context + * @disable: TRUE or FALSE + * + * Enable/disable paths canonicalization and tags evaluation. The libmount context + * canonicalies paths when search in fstab and when prepare source and target paths + * for mount(2) syscall. + * + * This fuction has effect to the private (within context) fstab instance only + * (see mnt_context_set_fstab()). If you want to use an external fstab then you + * need 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 enabled or 0. + */ +int mnt_context_is_nocanonicalize(struct libmnt_context *cxt) +{ + return cxt && (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 && (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 && (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 if mount -F enabled and the current context is child, or 0 + */ +int mnt_context_is_child(struct libmnt_context *cxt) +{ + 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 see 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 && (cxt->flags & MNT_FL_RDONLY_UMOUNT) ? 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 && (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 && (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 && (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 && (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 && (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 && (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 && (cxt->flags & MNT_FL_VERBOSE) ? 1 : 0; +} + +/** + * mnt_context_enable_loopdel: + * @cxt: mount context + * @enable: TRUE or FALSE + * + * Enable/disable 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 && (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. Note that the external + * @fs instance is not deallocated by mnt_free_context() or mnt_reset_context(). + * + * The @fs will be modified by mnt_context_set_{source,target,options,fstype} + * functions, If the @fs is NULL then all current FS specific setting (source, + * target, etc., exclude spec) is reseted. + * + * Returns: 0 on success, negative number in case of error. + */ +int mnt_context_set_fs(struct libmnt_context *cxt, struct libmnt_fs *fs) +{ + if (!cxt) + return -EINVAL; + if (!(cxt->flags & MNT_FL_EXTERN_FS)) + mnt_free_fs(cxt->fs); + + set_flag(cxt, MNT_FL_EXTERN_FS, fs != NULL); + 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 calloc() errrr. + */ +struct libmnt_fs *mnt_context_get_fs(struct libmnt_context *cxt) +{ + if (!cxt) + return NULL; + if (!cxt->fs) { + cxt->fs = mnt_new_fs(); + cxt->flags &= ~MNT_FL_EXTERN_FS; + } + return cxt->fs; +} + +/** + * mnt_context_set_source: + * @cxt: mount context + * @source: mount source (device, directory, UUID, LABEL, ...) + * + * 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 pr 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 pr 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 the real FS type. For comma-separated list of + * filesystems or for "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) +{ + if (fstype && strchr(fstype, ',')) + return -EINVAL; + 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 pr 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 + * + * 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() may the mount options string + * also includes options set by mnt_context_set_mflags() or another 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 the private struct libmnt_table by default. + * This function allows to overwrite the private fstab with an external + * instance. Note that the external instance is not deallocated by mnt_free_context(). + * + * The fstab is used read-only and is not modified, it should be possible to + * share the fstab between more mount contexts (TODO: tests it.) + * + * If the @tb argument is NULL then the current private fstab instance is + * reseted. + * + * 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; + if (!(cxt->flags & MNT_FL_EXTERN_FSTAB)) + mnt_free_table(cxt->fstab); + + set_flag(cxt, MNT_FL_EXTERN_FSTAB, tb != NULL); + 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_cache *cache; + + 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); + cxt->flags &= ~MNT_FL_EXTERN_FSTAB; + rc = mnt_table_parse_fstab(cxt->fstab, NULL); + if (rc) + return rc; + } + + cache = mnt_context_get_cache(cxt); + + /* never touch an external fstab */ + if (!(cxt->flags & MNT_FL_EXTERN_FSTAB)) + mnt_table_set_cache(cxt->fstab, cache); + + 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 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) +{ + struct libmnt_cache *cache; + + if (!cxt) + return -EINVAL; + + if (!cxt->mtab) { + int rc; + + cxt->mtab = mnt_new_table(); + if (!cxt->mtab) + return -ENOMEM; + + if (cxt->table_errcb) + mnt_table_set_parser_errcb(cxt->mtab, cxt->table_errcb); + + rc = mnt_table_parse_mtab(cxt->mtab, cxt->mtab_path); + if (rc) + return rc; + } + + cache = mnt_context_get_cache(cxt); + mnt_table_set_cache(cxt->mtab, cache); + + if (tb) + *tb = cxt->mtab; + 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 use 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) +{ + struct libmnt_cache *cache; + int rc; + + 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); + + rc = mnt_table_parse_file(*tb, filename); + if (rc) { + mnt_free_table(*tb); + return rc; + } + + cache = mnt_context_get_cache(cxt); + if (cache) + mnt_table_set_cache(*tb, cache); + + return 0; +} + +/** + * 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; + + 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. Note that + * the external instance is not deallocated by mnt_free_context(). + * + * If the @cache argument is NULL then the current private cache instance is + * reseted. + * + * 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; + if (!(cxt->flags & MNT_FL_EXTERN_CACHE)) + mnt_free_cache(cxt->cache); + + set_flag(cxt, MNT_FL_EXTERN_CACHE, cache != NULL); + cxt->cache = 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) { + cxt->cache = mnt_new_cache(); + if (!cxt->cache) + return NULL; + cxt->flags &= ~MNT_FL_EXTERN_CACHE; + } + return cxt->cache; +} + +/** + * mnt_context_set_passwd_cb: + * @cxt: mount context + * @get: callback to get password + * @release: callback to release (delallocate) password + * + * Sets callbacks for encryption password (e.g encrypted loopdev) + * + * 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 udate) 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(cxt->mtab_writable ? + cxt->mtab_path : cxt->utab_path, 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"); + * + * these both calls have the same effect. + * + * 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 bitewise-OR the result with + * 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; + 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)); + } + 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 notest 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 bitewise-OR the result + * with 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 deallocated 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; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt || !cxt->fs) + return -EINVAL; + + DBG(CXT, mnt_debug_h(cxt, "preparing source path")); + + src = mnt_fs_get_source(cxt->fs); + + if (!src && (cxt->mountflags & MS_PROPAGATION)) + /* mount --make-{shared,private,...} */ + return mnt_fs_set_source(cxt->fs, "none"); + + /* ignore filesystems without source or filesystems + * where the source is quasi-path (//foo/bar) + */ + if (!src || mnt_fs_is_netfs(cxt->fs)) + return 0; + + DBG(CXT, mnt_debug_h(cxt, "srcpath '%s'", src)); + + 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, mnt_debug_h(cxt, "failed to prepare srcpath [rc=%d]", rc)); + return rc; + } + + if (!path) + path = src; + + if ((cxt->mountflags & (MS_BIND | MS_MOVE | MS_PROPAGATION)) || + mnt_fs_is_pseudofs(cxt->fs)) { + DBG(CXT, mnt_debug_h(cxt, "BIND/MOVE/pseudo FS source: %s", path)); + return rc; + } + + /* + * Initialize loop device + */ + if (mnt_context_is_loopdev(cxt)) { + rc = mnt_context_setup_loopdev(cxt); + if (rc) + return rc; + } + + DBG(CXT, mnt_debug_h(cxt, "final srcpath '%s'", + mnt_fs_get_source(cxt->fs))); + return 0; +} + +int mnt_context_prepare_target(struct libmnt_context *cxt) +{ + const char *tgt; + struct libmnt_cache *cache; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt || !cxt->fs) + return -EINVAL; + + DBG(CXT, mnt_debug_h(cxt, "preparing target path")); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) + return 0; + + cache = mnt_context_get_cache(cxt); + if (cache) { + char *path = mnt_resolve_path(tgt, cache); + if (path && strcmp(path, tgt) != 0) + rc = mnt_fs_set_target(cxt->fs, path); + } + + if (rc) + DBG(CXT, mnt_debug_h(cxt, "failed to prepare target '%s'", tgt)); + else + DBG(CXT, mnt_debug_h(cxt, "final target '%s'", + mnt_fs_get_target(cxt->fs))); + return 0; +} + +/* + * It's usully no error when we're not able to detect filesystem type -- we + * will try to use types from /{etc,proc}/filesystems. + */ +int mnt_context_guess_fstype(struct libmnt_context *cxt) +{ + char *type; + const char *dev; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt || !cxt->fs) + return -EINVAL; + + if (cxt->mountflags & (MS_BIND | MS_MOVE | MS_PROPAGATION)) + 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->flags & MS_REMOUNT) + goto none; + if (cxt->fstype_pattern) + goto done; + + dev = mnt_fs_get_srcpath(cxt->fs); + if (!dev) + goto done; + + if (access(dev, F_OK) == 0) { + struct libmnt_cache *cache = mnt_context_get_cache(cxt); + + type = mnt_get_fstype(dev, &cxt->ambi, cache); + if (type) { + rc = mnt_fs_set_fstype(cxt->fs, type); + if (!cache) + free(type); /* type is not cached */ + } + } else { + if (strchr(dev, ':') != NULL) + rc = mnt_fs_set_fstype(cxt->fs, "nfs"); + else if (!strncmp(dev, "//", 2)) + rc = mnt_fs_set_fstype(cxt->fs, "cifs"); + } + +done: + DBG(CXT, mnt_debug_h(cxt, "FS type: %s", mnt_fs_get_fstype(cxt->fs))); + 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; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!type) + type = mnt_fs_get_fstype(cxt->fs); + + if (mnt_context_is_nohelpers(cxt) + || !type + || !strcmp(type, "none") + || mnt_fs_is_swaparea(cxt->fs)) + return 0; + + 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, mnt_debug_h(cxt, "%-25s ... %s", helper, + rc ? "not found" : "found")); + if (rc) + continue; + + free(cxt->helper); + cxt->helper = strdup(helper); + if (!cxt->helper) + return -ENOMEM; + return 0; + } + + return 0; +} + +int mnt_context_merge_mflags(struct libmnt_context *cxt) +{ + unsigned long fl = 0; + int rc; + + assert(cxt); + + DBG(CXT, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(cxt, "prepare update")); + + if (cxt->mountflags & MS_PROPAGATION) { + DBG(CXT, mnt_debug_h(cxt, "skip update: MS_PROPAGATION")); + return 0; + } + + target = mnt_fs_get_target(cxt->fs); + + if (cxt->action == MNT_ACT_UMOUNT && target && !strcmp(target, "/")) + /* Don't try to touch mtab if umounting root FS */ + mnt_context_disable_mtab(cxt, TRUE); + + if (mnt_context_is_nomtab(cxt)) { + DBG(CXT, mnt_debug_h(cxt, "skip update: NOMTAB flag")); + return 0; + } + if (cxt->helper) { + DBG(CXT, mnt_debug_h(cxt, "skip update: external helper")); + return 0; + } + if (!cxt->mtab_writable && !cxt->utab_writable) { + DBG(CXT, mnt_debug_h(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, mnt_debug_h(cxt, + "skip update: syscall failed [status=%d]", + cxt->syscall_status)); + return 0; + } + if (!cxt->update) { + cxt->update = mnt_new_update(); + if (!cxt->update) + return -ENOMEM; + + mnt_update_set_filename(cxt->update, + cxt->mtab_writable ? cxt->mtab_path : cxt->utab_path, + !cxt->mtab_writable); + } + + 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; + + assert(cxt); + + if (mnt_context_is_nomtab(cxt)) { + DBG(CXT, mnt_debug_h(cxt, "don't update: NOMTAB flag")); + return 0; + } + if (cxt->helper) { + DBG(CXT, mnt_debug_h(cxt, "don't update: external helper")); + return 0; + } + if (!cxt->update || !mnt_update_is_ready(cxt->update)) { + DBG(CXT, mnt_debug_h(cxt, "don't update: no update prepared")); + return 0; + } + if (cxt->syscall_status) { + DBG(CXT, mnt_debug_h(cxt, "don't update: syscall failed/not called")); + return 0; + } + + 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); + + return mnt_update_table(cxt->update, cxt->lock); +} + +static int apply_table(struct libmnt_context *cxt, struct libmnt_table *tb, + int direction) +{ + struct libmnt_fs *fs = NULL; + const char *src = NULL, *tgt = NULL; + int rc; + + assert(cxt); + assert(cxt->fs); + + if (!cxt->fs) + return -EINVAL; + + 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 source (for + * example bind mount, symlink to 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, mnt_debug_h(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) + 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; + struct libmnt_table *tab = NULL; + const char *src = NULL, *tgt = NULL; + + assert(cxt); + assert(cxt->fs); + + if (!cxt) + return -EINVAL; + + if (mnt_context_tab_applied(cxt)) /* already applied */ + return 0; + + if (mnt_context_is_restricted(cxt)) { + DBG(CXT, mnt_debug_h(cxt, "force fstab usage for non-root users!")); + cxt->optsmode = MNT_OMODE_USER; + } else if (cxt->optsmode == 0) { + DBG(CXT, mnt_debug_h(cxt, "use default optmode")); + 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 (cxt->fs) { + src = mnt_fs_get_source(cxt->fs); + tgt = mnt_fs_get_target(cxt->fs); + } + + DBG(CXT, mnt_debug_h(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, mnt_debug_h(cxt, "fstab not required -- skip")); + return 0; + } + + if (!src && tgt + && !(cxt->optsmode & MNT_OMODE_FSTAB) + && !(cxt->optsmode & MNT_OMODE_MTAB)) { + DBG(CXT, mnt_debug_h(cxt, "only target; fstab/mtab not required " + "-- skip, probably MS_PROPAGATION")); + return 0; + } + + DBG(CXT, mnt_debug_h(cxt, + "trying to apply fstab (src=%s, target=%s)", src, tgt)); + + /* let's initialize cxt->fs */ + mnt_context_get_fs(cxt); + + /* try fstab */ + if (cxt->optsmode & MNT_OMODE_FSTAB) { + 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)) { + DBG(CXT, mnt_debug_h(cxt, "trying to apply from mtab")); + rc = mnt_context_get_mtab(cxt, &tab); + if (!rc) + rc = apply_table(cxt, tab, MNT_ITER_BACKWARD); + } + if (rc) + DBG(CXT, mnt_debug_h(cxt, "failed to find entry in fstab/mtab")); + 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 && (cxt->flags & MNT_FL_TAB_APPLIED); +} + +/** + * 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 inform + * 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 && (!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 be used only if [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, mnt_debug_h(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 yet. + * + * Returns: 0 or negative number in case of error. + */ +int mnt_context_strerror(struct libmnt_context *cxt __attribute__((__unused__)), + char *buf __attribute__((__unused__)), + size_t bufsiz __attribute__((__unused__))) +{ + /* TODO: based on cxt->syscall_errno or cxt->helper_status */ + return 0; +} + +/** + * mnt_context_init_helper + * @cxt: mount context + * @action: MNT_ACT_{UMOUNT,MOUNT} + * @flags: not used now + * + * This function infors 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 than 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 = mnt_context_disable_helpers(cxt, TRUE); + + if (!rc) + rc = set_flag(cxt, MNT_FL_HELPER, 1); + if (!rc) + cxt->action = action; + + DBG(CXT, mnt_debug_h(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 [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 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; + int rc; + + if (!cxt || !fs || !mounted) + return -EINVAL; + + rc = mnt_context_get_mtab(cxt, &mtab); + if (rc) + return rc; + + *mounted = mnt_table_is_fs_mounted(mtab, fs); + 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, mnt_debug_h(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; + + if (!mnt_context_is_parent(cxt)) + return -EINVAL; + + DBG(CXT, mnt_debug_h(cxt, "forking context")); + + DBG_FLUSH; + + pid = fork(); + + switch (pid) { + case -1: /* error */ + DBG(CXT, mnt_debug_h(cxt, "fork failed %m")); + return -errno; + + case 0: /* child */ + cxt->pid = getpid(); + mnt_context_enable_fork(cxt, FALSE); + DBG(CXT, mnt_debug_h(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, mnt_debug_h(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; +} + + + +#ifdef TEST_PROGRAM + +struct libmnt_lock *lock; + +static void lock_fallback(void) +{ + if (lock) + mnt_unlock_file(lock); +} + +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 <mountpont>|<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 internaly + * create and manage the lock + */ + lock = mnt_context_get_lock(cxt); + if (lock) + atexit(lock_fallback); + + rc = mnt_context_mount(cxt); + if (rc) + printf("failed to mount: %m\n"); + else + printf("successfully mounted\n"); + + lock = NULL; /* because we use atexit lock_fallback */ + mnt_free_context(cxt); + return rc; +} + +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 <mountpont>|<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; +} + +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 <mountpont>|<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; +} + +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 (!strcmp(argv[idx], "-O")) { + mnt_context_set_options_pattern(cxt, argv[idx + 1]); + idx += 2; + } + if (!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..da246e3 --- /dev/null +++ b/libmount/src/context_loopdev.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2011 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/* + * DOCS: - "lo@" prefix for fstype is unsupported + * - encyption= mount option for loop device is unssuported + */ + +#include <blkid.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 | + MNT_MS_ENCRYPTION)) { + + DBG(CXT, mnt_debug_h(cxt, "loopdev specific options detected")); + return 1; + } + + if (cxt->mountflags & (MS_BIND | MS_MOVE | MS_PROPAGATION)) + 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 empty loopdev where + * is no mountable filesystem... + * + * Note that there is not a restriction (on kernel side) that prevents 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) + return 1; + } + + return 0; +} + + +/* Check, if there already exists a mounted loop device on the mountpoint node + * with the same parameters. + */ +static int 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; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!target || !backing_file || mnt_context_get_mtab(cxt, &tb)) + return 0; + + DBG(CXT, mnt_debug_h(cxt, "checking if %s mounted on %s", + backing_file, target)); + + cache = mnt_context_get_cache(cxt); + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + /* Search for mountpoint node in mtab, procceed if any of these has the + * loop option set or the device is a loop device + */ + while (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; + int res = 0; + + if (!src || !mnt_fs_match_target(fs, target, cache)) + continue; + + if (strncmp(src, "/dev/loop", 9) == 0) { + res = loopdev_is_used((char *) src, backing_file, + offset, 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); + res = loopdev_is_used((char *) val, backing_file, + offset, LOOPDEV_FL_OFFSET); + free(val); + } + + if (res) { + DBG(CXT, mnt_debug_h(cxt, "%s already mounted", backing_file)); + return 1; + } + } + + return 0; +} + +int mnt_context_setup_loopdev(struct libmnt_context *cxt) +{ + const char *backing_file, *optstr, *loopdev = NULL; + char *val = NULL, *enc = NULL, *pwd = NULL; + size_t len; + struct loopdev_cxt lc; + int rc = 0, lo_flags = 0; + uint64_t offset = 0, sizelimit = 0; + + 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(CXT, mnt_debug_h(cxt, "trying to setup loopdev for %s", backing_file)); + + if (cxt->mountflags & MS_RDONLY) { + DBG(CXT, mnt_debug_h(cxt, "enabling READ-ONLY flag")); + lo_flags |= LO_FLAGS_READ_ONLY; + } + + rc = loopcxt_init(&lc, 0); + if (rc) + return rc; + + ON_DBG(CXT, loopcxt_enable_debug(&lc, 1)); + + 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) { + + val = strndup(val, len); + rc = val ? loopcxt_set_device(&lc, val) : -ENOMEM; + free(val); + + if (rc == 0) + loopdev = loopcxt_get_device(&lc); + } + + /* + * 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(CXT, mnt_debug_h(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(CXT, mnt_debug_h(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) { + enc = strndup(val, len); + if (val && !enc) + rc = -ENOMEM; + if (enc && cxt->pwd_get_cb) { + DBG(CXT, mnt_debug_h(cxt, "asking for pass")); + pwd = cxt->pwd_get_cb(cxt); + } + } + + if (rc == 0 && is_mounted_same_loopfile(cxt, + mnt_context_get_target(cxt), + backing_file, offset)) + rc = -EBUSY; + + 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) || + !cxt->mtab_writable) { + DBG(CXT, mnt_debug_h(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(CXT, mnt_debug_h(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 && enc && pwd) + loopcxt_set_encryption(&lc, enc, pwd); + if (!rc) + loopcxt_set_flags(&lc, lo_flags); + if (rc) { + DBG(CXT, mnt_debug_h(cxt, "failed to set loopdev attributes")); + goto done; + } + + /* setup the device */ + rc = loopcxt_setup_device(&lc); + if (!rc) + break; /* success */ + + if (loopdev || rc != -EBUSY) { + DBG(CXT, mnt_debug_h(cxt, "failed to setup device")); + rc = -MNT_ERR_LOOPDEV; + goto done; + } + DBG(CXT, mnt_debug_h(cxt, "loopdev stolen...trying again")); + } while (1); + + if (!rc) + rc = mnt_fs_set_source(cxt->fs, loopcxt_get_device(&lc)); + + if (!rc) { + /* success */ + cxt->flags |= MNT_FL_LOOPDEV_READY; + + if ((cxt->user_mountflags & MNT_MS_LOOP) && + loopcxt_is_autoclear(&lc)) { + /* + * autoclear flag accepted by kernel, don't store + * the "loop=" option to 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 auto-cleared by kernel + */ + cxt->loopdev_fd = loopcxt_get_fd(&lc); + loopcxt_set_fd(&lc, -1, 0); + } +done: + free(enc); + if (pwd && cxt->pwd_release_cb) { + DBG(CXT, mnt_debug_h(cxt, "release pass")); + cxt->pwd_release_cb(cxt, pwd); + } + loopcxt_deinit(&lc); + 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(CXT, mnt_debug_h(cxt, "loopdev 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(CXT, mnt_debug_h(cxt, "closing loopdev 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..f3d8ff1 --- /dev/null +++ b/libmount/src/context_mount.c @@ -0,0 +1,935 @@ +/* + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * 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" + +/* + * this has to be called after mnt_context_evaluate_permissions() + */ +static int fix_optstr(struct libmnt_context *cxt) +{ + int rc = 0; + char *next; + char *name, *val; + size_t namesz, valsz; + struct libmnt_fs *fs; +#ifdef HAVE_LIBSELINUX + int se_fix = 0, se_rem = 0; +#endif + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt) + return -EINVAL; + if (!cxt->fs || (cxt->flags & MNT_FL_MOUNTOPTS_FIXED)) + return 0; + + DBG(CXT, mnt_debug_h(cxt, "mount: fixing optstr")); + + fs = cxt->fs; + + /* The propagation flags should not be used together with any other + * flags (except MS_REC and MS_SILENT) */ + if (cxt->mountflags & MS_PROPAGATION) + cxt->mountflags &= (MS_PROPAGATION | MS_REC | MS_SILENT); + + /* + * The "user" options is our business (so we can modify the option), + * but exception is command line for /sbin/mount.<type> helpers. Let's + * save the original user=<name> to call the helpers with unchanged + * "user" setting. + * + * Don't check for MNT_MS_USER in cxt->user_mountflags, the flag maybe + * removed by evaluate_permissions(). + */ + if (!mnt_optstr_get_option(fs->user_optstr, "user", &val, &valsz)) { + if (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, mnt_debug_h(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, mnt_debug_h(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; + } + + 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 we have translate the contexts */ + se_fix = 1; + + if (!se_rem) { + /* de-duplicate SELinux options */ + mnt_optstr_deduplicate_option(&fs->fs_optstr, "context"); + mnt_optstr_deduplicate_option(&fs->fs_optstr, "fscontext"); + mnt_optstr_deduplicate_option(&fs->fs_optstr, "defcontext"); + mnt_optstr_deduplicate_option(&fs->fs_optstr, "rootcontext"); + mnt_optstr_deduplicate_option(&fs->fs_optstr, "seclabel"); + } +#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) && + namesz >= 7 && (!strncmp(name, "context", 7) || + !strncmp(name, "fscontext", 9) || + !strncmp(name, "defcontext", 10) || + !strncmp(name, "rootcontext", 11) || + !strncmp(name, "seclabel", 8))) { + 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 + if (rc) + goto done; + } + + if (!rc && cxt->user_mountflags & MNT_MS_USER) + rc = mnt_optstr_fix_user(&fs->user_optstr); + + /* 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, mnt_debug_h(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 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[1]; + char *next, *name, *val; + size_t namesz, valsz; + int rc = 0; + + assert(cxt); + assert(cxt->fs); + assert(optstr); + + DBG(CXT, mnt_debug_h(cxt, "mount: generate helper mount options")); + + *optstr = mnt_fs_strdup_options(cxt->fs); + if (!*optstr) + return -ENOMEM; + + 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); + next = *optstr; + + while (!mnt_optstr_next_option(&next, &name, &namesz, &val, &valsz)) { + const struct libmnt_optmap *ent; + + mnt_optmap_get_entry(maps, 1, 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> maybe be used by some filesystems as 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->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt) + return -EINVAL; + if (!cxt->fs) + return 0; + + DBG(CXT, mnt_debug_h(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; + cxt->user_mountflags &= ~MNT_MS_USER; + cxt->user_mountflags &= ~MNT_MS_USERS; + } else { + /* + * user mount + */ + if (!(cxt->flags & MNT_FL_TAB_APPLIED)) + { + DBG(CXT, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(cxt, "permissions evaluation ends with -EPERMS")); + return -EPERM; + } + } + + return 0; +} + +/* + * mnt_context_helper_setopt() backend + * + * This function applies 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; + default: + return 1; + break; + } + + return rc; +} + +static int exec_helper(struct libmnt_context *cxt) +{ + char *o = NULL; + int rc; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + DBG(CXT, mnt_debug_h(cxt, "mount: executing helper %s", cxt->helper)); + + rc = generate_helper_optstr(cxt, &o); + if (rc) + return -EINVAL; + + DBG_FLUSH; + + switch (fork()) { + case 0: + { + const char *args[12], *type; + int i = 0; + + if (setgid(getgid()) < 0) + exit(EXIT_FAILURE); + + if (setuid(getuid()) < 0) + 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 */ + + /* + * TODO: remove the exception for "nfs", -s is documented + * for years should be usable everywhere. + */ + if (mnt_context_is_sloppy(cxt) && + type && startswith(type, "nfs")) + 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 && !endswith(cxt->helper, type)) { + args[i++] = "-t"; /* 10 */ + args[i++] = type; /* 11 */ + } + args[i] = NULL; /* 12 */ +#ifdef CONFIG_LIBMOUNT_DEBUG + for (i = 0; args[i]; i++) + DBG(CXT, mnt_debug_h(cxt, "argv[%d] = \"%s\"", + i, args[i])); +#endif + DBG_FLUSH; + execv(cxt->helper, (char * const *) args); + exit(EXIT_FAILURE); + } + default: + { + int st; + wait(&st); + cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1; + + DBG(CXT, mnt_debug_h(cxt, "%s executed [status=%d]", + cxt->helper, cxt->helper_status)); + cxt->helper_exec_status = rc = 0; + break; + } + + case -1: + cxt->helper_exec_status = rc = -errno; + DBG(CXT, mnt_debug_h(cxt, "fork() failed")); + break; + } + + free(o); + return rc; +} + +/* + * The default is to use fstype from cxt->fs, this could be overwritten by + * @try_type argument. + * + * 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; + } + if (cxt->helper) + return exec_helper(cxt); + + flags = cxt->mountflags; + src = mnt_fs_get_srcpath(cxt->fs); + target = mnt_fs_get_target(cxt->fs); + + if (!target) + return -EINVAL; + if (!src) { + /* unnecessary, should be already resolved in + * mnt_context_prepare_srcpath(), but for sure... */ + DBG(CXT, mnt_debug_h(cxt, "WARNING: source is NULL -- using \"none\"!")); + src = "none"; + } + type = try_type ? : mnt_fs_get_fstype(cxt->fs); + + if (!(flags & MS_MGC_MSK)) + flags |= MS_MGC_VAL; + + DBG(CXT, mnt_debug_h(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)) + cxt->syscall_status = 0; + else { + if (mount(src, target, type, flags, cxt->mountdata)) { + cxt->syscall_status = -errno; + DBG(CXT, mnt_debug_h(cxt, "mount(2) failed [errno=%d %m]", + -cxt->syscall_status)); + return -cxt->syscall_status; + } + DBG(CXT, mnt_debug_h(cxt, "mount(2) success")); + cxt->syscall_status = 0; + } + + 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; +} + +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; + + assert(cxt); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!neg && pattern) { + /* + * try all types from the list + */ + char *p, *p0; + + DBG(CXT, mnt_debug_h(cxt, "trying to mount by FS pattern list")); + + p0 = p = strdup(pattern); + if (!p) + return -ENOMEM; + do { + char *end = strchr(p, ','); + if (end) + *end = '\0'; + rc = do_mount(cxt, p); + p = end ? end + 1 : NULL; + + } while (!mnt_context_get_status(cxt) && p); + + free(p0); + + if (mnt_context_get_status(cxt)) + return rc; + } + + /* + * try /etc/filesystems and /proc/filesystems + */ + DBG(CXT, mnt_debug_h(cxt, "trying to mount by filesystems lists")); + + rc = mnt_get_filesystems(&filesystems, neg ? pattern : NULL); + if (rc) + return rc; + + 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) + 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; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + 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; + + cxt->action = MNT_ACT_MOUNT; + + DBG(CXT, mnt_debug_h(cxt, "mount: preparing")); + + /* TODO: fstab is unnecessary for MS_{MOVE,BIND,STARED,...} */ + 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, mnt_debug_h(cxt, "mount: preparing failed")); + return rc; + } + cxt->flags |= MNT_FL_PREPARED; + 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 than you have to call mnt_reset_context(). + * + * If you want to call mount(2) for the same source and target with a 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! +* + * 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; + + 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, mnt_debug_h(cxt, "mount: do mount")); + + if (!(cxt->flags & MNT_FL_MOUNTDATA)) + cxt->mountdata = (char *) mnt_fs_get_fs_options(cxt->fs); + + type = mnt_fs_get_fstype(cxt->fs); + if (type) + res = do_mount(cxt, NULL); + else + res = do_mount_by_pattern(cxt, cxt->fstype_pattern); + + if (mnt_context_get_status(cxt) + && !mnt_context_is_fake(cxt) + && !cxt->helper) { + /* + * 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) + && !mnt_is_readonly(mnt_context_get_target(cxt))) + + 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_PROPAGATION | MS_MOVE)) + && mnt_is_readonly(mnt_context_get_target(cxt))) + + mnt_context_set_mflags(cxt, + cxt->mountflags | MS_RDONLY); + } + + 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 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 could be called only once. If you want to mount with + * different setting than you have to call mnt_reset_context(). It's NOT enough + * to call mnt_context_reset_status() if you want call this function more than + * once, whole context has to be reseted. + * + * 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! + * + * 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; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + rc = mnt_context_prepare_mount(cxt); + if (!rc) + rc = mnt_context_prepare_update(cxt); + if (!rc) + rc = mnt_context_do_mount(cxt); + + /* TODO: if mtab update is expected then check if the + * target is really mounted read-write to avoid 'ro' in + * mtab and 'rw' in /proc/mounts. + */ + if (!rc) + rc = mnt_context_update_tabs(cxt); + 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 not 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. + * + * Returns: 0 on success, + * <0 in case of error (!= mount(2) errors) + * 1 at the end of the list. + */ +int mnt_context_next_mount(struct libmnt_context *cxt, + struct libmnt_iter *itr, + struct libmnt_fs **fs, + int *mntrc, + int *ignored) +{ + struct libmnt_table *fstab, *mtab; + const char *o, *tgt; + int rc, mounted = 0; + + if (ignored) + *ignored = 0; + if (mntrc) + *mntrc = 0; + + if (!cxt || !fs || !itr) + return -EINVAL; + + 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, mnt_debug_h(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 not match with options patterns */ + (cxt->fstype_pattern && !mnt_fs_match_fstype(*fs, + cxt->fstype_pattern)) || + + /* ignore filesystems not match with type patterns */ + (cxt->optstr_pattern && !mnt_fs_match_options(*fs, + cxt->optstr_pattern))) { + if (ignored) + *ignored = 1; + DBG(CXT, mnt_debug_h(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) { + rc = mnt_context_mount(cxt); + if (mntrc) + *mntrc = rc; + } + + if (mnt_context_is_child(cxt)) { + DBG(CXT, mnt_debug_h(cxt, "next-mount: child exit [rc=%d]", rc)); + DBG_FLUSH; + exit(rc); + } + return 0; +} + diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c new file mode 100644 index 0000000..2c2975e --- /dev/null +++ b/libmount/src/context_umount.c @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * 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 forcibily 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 + + +static int lookup_umount_fs(struct libmnt_context *cxt) +{ + int rc, loopdev = 0; + const char *tgt; + struct libmnt_table *mtab = NULL; + struct libmnt_fs *fs; + + assert(cxt); + assert(cxt->fs); + + DBG(CXT, mnt_debug_h(cxt, "umount: lookup FS")); + + tgt = mnt_fs_get_target(cxt->fs); + if (!tgt) { + DBG(CXT, mnt_debug_h(cxt, "umount: undefined target")); + return -EINVAL; + } + rc = mnt_context_get_mtab(cxt, &mtab); + if (rc) { + DBG(CXT, mnt_debug_h(cxt, "umount: failed to read mtab")); + return rc; + } + +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 (mountpoint) */ + 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, mnt_debug_h(cxt, "mtab is broken?!?!")); + return -EINVAL; + } + if (fs != fs1) { + /* Something was stacked over `file' on the + * same mount point. */ + DBG(CXT, mnt_debug_h(cxt, + "umount: %s: %s is mounted " + "over it on the same point", + tgt, mnt_fs_get_source(fs1))); + return -EINVAL; + } + } + } + + if (!fs && !loopdev) { + /* + * Maybe target is /path/file.img, try to convert to /dev/loopN + */ + struct stat st; + + if (stat(tgt, &st) == 0 && S_ISREG(st.st_mode)) { + char *dev = NULL; + int count = loopdev_count_by_backing_file(tgt, &dev); + + if (count == 1) { + DBG(CXT, mnt_debug_h(cxt, + "umount: %s --> %s (retry)", tgt, dev)); + mnt_fs_set_source(cxt->fs, tgt); + mnt_fs_set_target(cxt->fs, dev); + free(dev); + tgt = mnt_fs_get_target(cxt->fs); + + loopdev = 1; /* to avoid endless loop */ + goto try_loopdev; + + } else if (count > 1) + DBG(CXT, mnt_debug_h(cxt, + "umount: warning: %s is associated " + "with more than one loodev", tgt)); + } + } + + if (!fs) { + DBG(CXT, mnt_debug_h(cxt, "umount: cannot find %s in mtab", tgt)); + return 0; + } + + 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, mnt_debug_h(cxt, "umount: failed to copy FS")); + return -errno; + } + DBG(CXT, mnt_debug_h(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; + char *val, *optstr; + size_t valsz; + int flags = 0; + + /* check if it begins with /dev/loop */ + if (strncmp(devname, _PATH_DEV_LOOP, sizeof(_PATH_DEV_LOOP))) + return 0; + + src = mnt_fs_get_srcpath(fs); + if (!src) + return 0; + + /* check for offset option in @fs */ + optstr = (char *) 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, flags); +} + +static int prepare_helper_from_options(struct libmnt_context *cxt, + const char *name) +{ + char *suffix = NULL; + const char *opts; + size_t valsz; + + 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, mnt_debug_h(cxt, "umount: umount.%s %s requested", suffix, name)); + + return mnt_context_prepare_helper(cxt, "umount", suffix); +} + +/* + * 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 (!cxt || !cxt->fs) + return -EINVAL; + + if (!mnt_context_is_restricted(cxt)) + return 0; /* superuser mount */ + + DBG(CXT, mnt_debug_h(cxt, "umount: evaluating permissions")); + + if (!(cxt->flags & MNT_FL_TAB_APPLIED)) { + DBG(CXT, mnt_debug_h(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 has 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, mnt_debug_h(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 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 releation + * between loopdev and the file. + */ + fs = mnt_table_find_target(fstab, tgt, MNT_ITER_FORWARD); + if (fs) { + const char *dev = mnt_fs_get_srcpath(cxt->fs); /* devname from mtab */ + + if (!dev || !is_associated_fs(dev, fs)) + fs = NULL; + } + if (!fs) { + DBG(CXT, mnt_debug_h(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, mnt_debug_h(cxt, + "umount: promiscuous setting ('users') in fstab")); + return 0; + } + /* + * Check user=<username> setting from mtab if there is user, owner or + * group option in /etc/fstab + */ + if (u_flags & (MNT_MS_USER | MNT_MS_OWNER | MNT_MS_GROUP)) { + + char *curr_user = NULL; + char *mtab_user = NULL; + size_t sz; + + DBG(CXT, mnt_debug_h(cxt, + "umount: checking user=<username> from mtab")); + + curr_user = mnt_get_username(getuid()); + + if (!curr_user) { + DBG(CXT, mnt_debug_h(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); + } + + if (ok) { + DBG(CXT, mnt_debug_h(cxt, "umount %s is allowed", tgt)); + return 0; + } +eperm: + DBG(CXT, mnt_debug_h(cxt, "umount is not allowed for you")); + return -EPERM; +} + +static int exec_helper(struct libmnt_context *cxt) +{ + int rc; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + assert(cxt->helper_exec_status == 1); + + DBG_FLUSH; + + switch (fork()) { + case 0: + { + const char *args[10], *type; + int i = 0; + + if (setgid(getgid()) < 0) + exit(EXIT_FAILURE); + + if (setuid(getuid()) < 0) + 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 && !endswith(cxt->helper, type)) { + args[i++] = "-t"; /* 8 */ + args[i++] = (char *) type; /* 9 */ + } + + args[i] = NULL; /* 10 */ +#ifdef CONFIG_LIBMOUNT_DEBUG + for (i = 0; args[i]; i++) + DBG(CXT, mnt_debug_h(cxt, "argv[%d] = \"%s\"", + i, args[i])); +#endif + DBG_FLUSH; + execv(cxt->helper, (char * const *) args); + exit(EXIT_FAILURE); + } + default: + { + int st; + wait(&st); + cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1; + + DBG(CXT, mnt_debug_h(cxt, "%s executed [status=%d]", + cxt->helper, cxt->helper_status)); + cxt->helper_exec_status = rc = 0; + break; + } + + case -1: + cxt->helper_exec_status = rc = -errno; + DBG(CXT, mnt_debug_h(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; + default: + return 1; + break; + } + + return rc; +} + +/* Check whether the kernel supports 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, mnt_debug_h(cxt, "do umount")); + + if (cxt->restricted && !mnt_context_is_fake(cxt)) { + /* + * extra paranoa 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; + + else if (mnt_context_is_force(cxt)) + flags |= MNT_FORCE; + + DBG(CXT, mnt_debug_h(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, mnt_debug_h(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_MGC_VAL | MS_REMOUNT | MS_RDONLY, NULL); + if (rc < 0) { + cxt->syscall_status = -errno; + DBG(CXT, mnt_debug_h(cxt, + "read-only re-mount(2) failed [errno=%d]", + -cxt->syscall_status)); + + return -cxt->syscall_status; + } + cxt->syscall_status = 0; + DBG(CXT, mnt_debug_h(cxt, "read-only re-mount(2) success")); + return 0; + } + + if (rc < 0) { + DBG(CXT, mnt_debug_h(cxt, "umount(2) failed [errno=%d]", + -cxt->syscall_status)); + return -cxt->syscall_status; + } + + cxt->syscall_status = 0; + DBG(CXT, mnt_debug_h(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; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + 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; + + free(cxt->helper); /* be paranoid */ + cxt->helper = NULL; + cxt->action = MNT_ACT_UMOUNT; + + 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 && 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, mnt_debug_h(cxt, "umount: preparing failed")); + return rc; + } + cxt->flags |= MNT_FL_PREPARED; + 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; + + 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)); + + rc = do_umount(cxt); + if (rc) + return rc; + + 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 && cxt->mtab_writable) + rc = mnt_update_set_fs(cxt->update, + cxt->mountflags, NULL, cxt->fs); + } + } + 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; + + assert(cxt); + assert(cxt->fs); + assert(cxt->helper_exec_status == 1); + assert(cxt->syscall_status == 1); + + DBG(CXT, mnt_debug_h(cxt, "umount: %s", mnt_context_get_target(cxt))); + + 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); + 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 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); + cxt->mtab = mtab; + + if (rc) + return rc; + + 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, mnt_debug_h(cxt, "next-umount: trying %s", tgt)); + + /* ignore root filesystem */ + if ((tgt && (strcmp(tgt, "/") == 0 || strcmp(tgt, "root") == 0)) || + + /* ignore filesystems not match with options patterns */ + (cxt->fstype_pattern && !mnt_fs_match_fstype(*fs, + cxt->fstype_pattern)) || + + /* ignore filesystems not match with type patterns */ + (cxt->optstr_pattern && !mnt_fs_match_options(*fs, + cxt->optstr_pattern))) { + if (ignored) + *ignored = 1; + DBG(CXT, mnt_debug_h(cxt, "next-umount: 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; + } + + rc = mnt_context_set_fs(cxt, *fs); + if (rc) + return rc; + rc = mnt_context_umount(cxt); + if (mntrc) + *mntrc = rc; + return 0; +} diff --git a/libmount/src/fs.c b/libmount/src/fs.c new file mode 100644 index 0000000..c3b0cb2 --- /dev/null +++ b/libmount/src/fs.c @@ -0,0 +1,1501 @@ +/* + * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * 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: + * + * 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; + + /*DBG(FS, mnt_debug_h(fs, "alloc"));*/ + INIT_LIST_HEAD(&fs->ents); + return fs; +} + +/** + * mnt_free_fs: + * @fs: fs pointer + * + * Deallocates the fs. + */ +void mnt_free_fs(struct libmnt_fs *fs) +{ + if (!fs) + return; + list_del(&fs->ents); + + /*DBG(FS, mnt_debug_h(fs, "free"));*/ + + 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); +} + +/** + * mnt_reset_fs: + * @fs: fs pointer + * + * Resets (zeroize) @fs. + */ +void mnt_reset_fs(struct libmnt_fs *fs) +{ + if (fs) + memset(fs, 0, sizeof(*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 **) (old + offset); + char **n = (char **) (new + offset); + + if (*n) + return 0; /* already set, not 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 overwrited. + * + * 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 (!dest) { + dest = mnt_new_fs(); + if (!dest) + return NULL; + } + + /*DBG(FS, mnt_debug_h(dest, "copy from %p", src));*/ + + 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(); + + 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) +{ + return fs ? fs->userdata : NULL; +} + +/** + * 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) +{ + assert(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 parser ONLY (@source has to be allocated on error) + */ +int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source) +{ + char *t = NULL, *v = NULL; + + assert(fs); + + if (source && strchr(source, '=')) { + if (blkid_parse_tag_string(source, &t, &v) != 0) + return -1; + } + + 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 tailing slash is ignored. + * See also 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_except_trailing_slash(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 tailing slash is ignored. + * See also 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_except_trailing_slash(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" has to + * be always 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 that 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) +{ + assert(fs); + return fs ? fs->target : NULL; +} + +/** + * mnt_fs_set_target: + * @fs: fstab/mtab/mountinfo entry + * @target: mountpoint + * + * This function creates a private copy (strdup()) of @target. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_target(struct libmnt_fs *fs, const char *target) +{ + char *p = NULL; + + assert(fs); + + if (!fs) + return -EINVAL; + if (target) { + p = strdup(target); + if (!p) + return -ENOMEM; + } + free(fs->target); + fs->target = p; + + return 0; +} + +static int mnt_fs_get_flags(struct libmnt_fs *fs) +{ + return fs ? fs->flags : 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) +{ + assert(fs); + return fs ? fs->fstype : NULL; +} + +/* Used by 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 leading "r[ow],", "," and 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 the one options string + * and returns the result. This function does not modigy @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; + + assert(fs); + + 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) { + if (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) +{ + assert(fs); + return fs ? fs->optstr : NULL; +} + + +/** + * mnt_fs_set_options: + * @fs: fstab/mtab/mountinfo entry pointer + * @optstr: options string + * + * Splits @optstr to VFS, FS and userspace mount options and update relevat + * parts of @fs. + * + * Returns: 0 on success, or negative number icase of error. + */ +int mnt_fs_set_options(struct libmnt_fs *fs, const char *optstr) +{ + char *v = NULL, *f = NULL, *u = NULL, *n = NULL; + + assert(fs); + + 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) + 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; + + assert(fs); + + if (!fs) + return -EINVAL; + if (!optstr) + return 0; + + rc = mnt_split_optstr((char *) optstr, &u, &v, &f, 0, 0); + 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 prepands 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; + + assert(fs); + + if (!fs) + return -EINVAL; + if (!optstr) + return 0; + + rc = mnt_split_optstr((char *) optstr, &u, &v, &f, 0, 0); + 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) +{ + assert(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) +{ + assert(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) +{ + assert(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) +{ + assert(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 send to kernel and are not interpreted by + * libmount. The attributes are stored in /run/mount/utab only. + * + * The atrtributes 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) +{ + char *p = NULL; + + if (!fs) + return -EINVAL; + if (optstr) { + p = strdup(optstr); + if (!p) + return -ENOMEM; + } + free(fs->attrs); + fs->attrs = p; + + return 0; +} + +/** + * 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) +{ + assert(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) +{ + assert(fs); + 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) +{ + assert(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) +{ + assert(fs); + 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) +{ + assert(fs); + return fs ? fs->root : NULL; +} + +/** + * mnt_fs_set_root: + * @fs: mountinfo entry + * @root: path + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_fs_set_root(struct libmnt_fs *fs, const char *root) +{ + char *p = NULL; + + assert(fs); + if (!fs) + return -EINVAL; + if (root) { + p = strdup(root); + if (!p) + return -ENOMEM; + } + free(fs->root); + fs->root = p; + return 0; +} + +/** + * mnt_fs_get_swaptype: + * @fs: /proc/swaps entry + * + * Returns: swap type or NULL + */ +const char *mnt_fs_get_swaptype(struct libmnt_fs *fs) +{ + assert(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) +{ + assert(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) +{ + assert(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) +{ + assert(fs); + return fs ? fs->priority : 0; +} + +/** + * mnt_fs_set_priority: + * @fs: /proc/swaps entry + * + * Returns: 0 or -1 in case of error + */ +int mnt_fs_set_priority(struct libmnt_fs *fs, int prio) +{ + assert(fs); + 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) +{ + assert(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) +{ + char *p = NULL; + + assert(fs); + if (!fs) + return -EINVAL; + if (src) { + p = strdup(src); + if (!p) + return -ENOMEM; + } + free(fs->bindsrc); + fs->bindsrc = p; + return 0; +} + +/** + * 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) +{ + assert(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) +{ + assert(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) +{ + assert(fs); + return fs ? fs->devno : 0; +} + +/** + * mnt_fs_get_tid: + * @fs: /proc/<tid>/mountinfo entry + * + * Returns: TID (task ID) for filesystems read from mountinfo file + */ +pid_t mnt_fs_get_tid(struct libmnt_fs *fs) +{ + assert(fs); + return fs ? fs->tid : 0; +} + +/** + * mnt_fs_get_option: + * @fs: fstab/mtab/mountinfo entry pointer + * @name: option name + * @value: returns pointer to the begin of the value (e.g. name=VALUE) or NULL + * @valsz: returns size of options value or 0 + * + * Returns: 0 on success, 1 when not found the @name 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->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 begin of the value (e.g. name=VALUE) or NULL + * @valsz: returns size of options value or 0 + * + * Returns: 0 on success, 1 when not found the @name 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->attrs) + rc = mnt_optstr_get_option(fs->attrs, name, value, valsz); + return rc; +} + +/** + * 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. + * + * 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_path(target, cache); + rc = (cn && strcmp(cn, fs->target) == 0); + + /* 3) - canonicalized and canonicalized */ + if (!rc && cn && !mnt_fs_is_kernel(fs)) { + char *tcn = mnt_resolve_path(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 + * + * Possible are four attempts: + * 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 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 @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 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) + return -EINVAL; + fprintf(file, "------ fs: %p\n", fs); + 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_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)); + + return 0; +} + +/** + * mnt_free_mntent: + * @mnt: mount entry + * + * Deallocates "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 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 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..58d4c18 --- /dev/null +++ b/libmount/src/init.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: init + * @title: Library initialization + * @short_description: initialize debuging + */ + +#include <stdarg.h> + +#include "mountP.h" + +int libmount_debug_mask; + +/** + * mnt_init_debug: + * @mask: debug mask (0xffff to enable full debuging) + * + * If the @mask is not specified then this function reads + * LIBMOUNT_DEBUG environment variable to get the mask. + * + * Already initialized debugging stuff cannot be changed. It does not + * have effect to call this function twice. + */ +void mnt_init_debug(int mask) +{ + if (libmount_debug_mask & MNT_DEBUG_INIT) + return; + if (!mask) { + char *str = getenv("LIBMOUNT_DEBUG"); + if (str) + libmount_debug_mask = strtoul(str, 0, 0); + } else + libmount_debug_mask = mask; + + if (libmount_debug_mask) + fprintf(stderr, "libmount: debug mask set to 0x%04x.\n", + libmount_debug_mask); + libmount_debug_mask |= MNT_DEBUG_INIT; +} diff --git a/libmount/src/iter.c b/libmount/src/iter.c new file mode 100644 index 0000000..393c23e --- /dev/null +++ b/libmount/src/iter.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: iter + * @title: Iterator + * @short_description: unified iterator + * + * The iterator keeps direction and 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 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 derection unchanged + * + * Resets iterator. + */ +void mnt_reset_iter(struct libmnt_iter *itr, int direction) +{ + assert(itr); + + if (direction == -1 && itr) + direction = itr->direction; + + if (itr) { + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; + } +} + +/** + * mnt_iter_get_direction: + * @itr: iterator pointer + * + * Returns: MNT_INTER_{FOR,BACK}WARD or negative number in case of error. + */ +int mnt_iter_get_direction(struct libmnt_iter *itr) +{ + assert(itr); + return itr ? itr->direction : -EINVAL; +} diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in new file mode 100644 index 0000000..43646ac --- /dev/null +++ b/libmount/src/libmount.h.in @@ -0,0 +1,653 @@ +/* + * mount.h - libmount API + * + * Copyright (C) 2008-2009 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> + +#define LIBMOUNT_VERSION "@LIBMOUNT_VERSION@" + +/** + * libmnt_cache: + * + * Stores canonicalized paths and evaluated tags + */ +struct libmnt_cache; + +/** + * libmnt_lock: + * + * Stores information about locked file (e.g. /etc/mtab) + */ +struct libmnt_lock; + +/** + * libmnt_iter: + * + * Generic iterator (stores state about lists) + */ +struct libmnt_iter; + +/** + * libmnt_optmap: + * + * Mount options description (map) + */ +struct libmnt_optmap +{ + const char *name; /* option name[=%<type>] (e.g. "loop[=%s]") */ + int id; /* option ID or MS_* flags (e.g MS_RDONLY) */ + int mask; /* MNT_{NOMTAB,INVERT,...} 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_tabdiff: + * + * Stores mountinfo state + */ +struct libmnt_tabdiff; + +/* + * 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(). + */ +#define MNT_ERR_NOFSTAB 5000 /* not found required entry in fstab */ +#define MNT_ERR_NOFSTYPE 5001 /* failed to detect filesystem type */ +#define MNT_ERR_NOSOURCE 5002 /* required mount source undefined */ +#define MNT_ERR_LOOPDEV 5003 /* loopdev setup failed, errno set by libc */ +#define MNT_ERR_MOUNTOPT 5004 /* failed to parse/use userspace mount options */ + +/* 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); +extern char *mnt_unmangle(const char *str); +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); +extern int mnt_match_options(const char *optstr, const char *pattern); +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); + +/* cache.c */ +extern struct libmnt_cache *mnt_new_cache(void); +extern void mnt_free_cache(struct libmnt_cache *cache); +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); +extern char *mnt_resolve_path(const char *path, struct libmnt_cache *cache); +extern char *mnt_resolve_tag(const char *token, const char *value, + struct libmnt_cache *cache); +extern char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache); + +extern char *mnt_pretty_path(const char *path, struct libmnt_cache *cache); + +/* 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); +extern void mnt_free_iter(struct libmnt_iter *itr); +extern void mnt_reset_iter(struct libmnt_iter *itr, int direction); +extern int mnt_iter_get_direction(struct libmnt_iter *itr); + +/* 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); +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); +extern void mnt_free_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); +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 *target); +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); +extern int mnt_fs_streq_target(struct libmnt_fs *fs, const char *path); + +extern char *mnt_fs_strdup_options(struct libmnt_fs *fs); +extern const char *mnt_fs_get_options(struct libmnt_fs *fs); +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 *root); +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_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); +extern struct libmnt_table *mnt_new_table_from_dir(const char *dirname); +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); +extern void mnt_free_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_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_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); + +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_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); +extern void mnt_free_update(struct libmnt_update *upd); +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); +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); + +/* 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); +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); + +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_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); +extern int mnt_context_is_rdonly_umount(struct libmnt_context *cxt); +extern int mnt_context_is_sloppy(struct libmnt_context *cxt); +extern int mnt_context_is_fake(struct libmnt_context *cxt); +extern int mnt_context_is_nomtab(struct libmnt_context *cxt); +extern int mnt_context_is_force(struct libmnt_context *cxt); +extern int mnt_context_is_verbose(struct libmnt_context *cxt); +extern int mnt_context_is_loopdel(struct libmnt_context *cxt); +extern int mnt_context_is_nohelpers(struct libmnt_context *cxt); +extern int mnt_context_is_nocanonicalize(struct libmnt_context *cxt); +extern int mnt_context_is_swapmatch(struct libmnt_context *cxt); + +extern int mnt_context_is_fork(struct libmnt_context *cxt); +extern int mnt_context_is_parent(struct libmnt_context *cxt); +extern int mnt_context_is_child(struct libmnt_context *cxt); + +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 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 *)); + +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); + +extern int mnt_context_mount(struct libmnt_context *cxt); +extern int mnt_context_umount(struct libmnt_context *cxt); +extern int mnt_context_next_mount(struct libmnt_context *cxt, + struct libmnt_iter *itr, struct libmnt_fs **fs, + int *mntrc, int *ignored); +extern int mnt_context_next_umount(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); +extern int mnt_context_do_mount(struct libmnt_context *cxt); +extern int mnt_context_finalize_mount(struct libmnt_context *cxt); + +extern int mnt_context_prepare_umount(struct libmnt_context *cxt); +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) + +/* + * 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 also elsewhere */ +#endif +#ifndef MS_MOVE +#define MS_MOVE 0x2000 /* 8192: Atomically move tree */ +#endif +#ifndef MS_REC +#define MS_REC 0x4000 /* 16384: Recursive loopback */ +#endif +#ifndef MS_SILENT +#define MS_SILENT 0x8000 /* 32768 */ +#endif +#ifndef MS_RELATIME +#define MS_RELATIME 0x200000 /* 200000: Update access times relative + to mtime/ctime */ +#endif +#ifndef MS_UNBINDABLE +#define MS_UNBINDABLE (1<<17) /* 131072 unbindable*/ +#endif +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) /* 262144 Private*/ +#endif +#ifndef MS_SLAVE +#define MS_SLAVE (1<<19) /* 524288 Slave*/ +#endif +#ifndef MS_SHARED +#define MS_SHARED (1<<20) /* 1048576 Shared*/ +#endif +#ifndef MS_I_VERSION +#define MS_I_VERSION (1<<23) /* update inode I_version field */ +#endif +#ifndef MS_STRICTATIME +#define MS_STRICTATIME (1<<24) /* strict atime semantics */ +#endif + +/* + * Magic mount flag number. Had to be or-ed to the flag values. + */ +#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..9e0f2c4 --- /dev/null +++ b/libmount/src/libmount.sym @@ -0,0 +1,250 @@ +/* + * The symbol versioning ensures that a new application requiring symbol foo; + * can't run with old libblkid.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; diff --git a/libmount/src/lock.c b/libmount/src/lock.c new file mode 100644 index 0000000..9f04725 --- /dev/null +++ b/libmount/src/lock.c @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: lock + * @title: Locking + * @short_description: locking methods for /etc/mtab or another libmount files + * + * The mtab lock is backwardly compatible with the standard linux /etc/mtab + * locking. Note, it's necessary to use the same locking schema in all + * application 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 "pathnames.h" +#include "mountP.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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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; + } + + 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 never ending 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; + + gettimeofday(&now, NULL); + + 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); + + DBG(LOCKS, mnt_debug_h(ml, "(%d) waiting for F_SETLKW", getpid())); + + 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, mnt_debug_h(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 + * now both are 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 very small number of attempts and extremely long waiting (1s) + * that is useless on machines with large number of mount processes. + * + * Now we wait few thousand microseconds between attempts and we have a global + * time limit (30s) rather than limit for number of attempts. The advantage + * is that this method also counts time which we spend in fcntl(F_SETLKW) and + * 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 first and 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 have (probably) all files, but we don't own the lock, + * Really? Check it! Maybe ml->locked wasn't set properly + * because code was interrupted by 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, mnt_debug_h(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, 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); + + gettimeofday(&maxtime, NULL); + 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); + + if (ml->lockfile_fd < 0) { + /* Strange... Maybe the file was just deleted? */ + int errsv = errno; + gettimeofday(&now, NULL); + 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, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(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 lock file (e.g. /etc/mtab~). Note that this function may + * use alarm(). + * + * Your application has to always 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 fcnlt(2)) + * b) link() failed: wait (max 30s) on F_SETLKW lock, goto 2. + * + * 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 on the + * lock status (for example from exit(3)). + */ +void mnt_unlock_file(struct libmnt_lock *ml) +{ + if (!ml) + return; + + DBG(LOCKS, mnt_debug_h(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, mnt_debug_h(ml, "restoring sigmask")); + sigprocmask(SIG_SETMASK, &ml->oldsigmask, NULL); + } +} + +#ifdef TEST_PROGRAM + +struct libmnt_lock *lock; + +/* + * read number from @filename, increment the number and + * write the number back to the file + */ +void increment_data(const char *filename, int verbose, int loopno) +{ + long num; + FILE *f; + char buf[256]; + + if (!(f = fopen(filename, "r"))) + 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"))) + err(EXIT_FAILURE, "%d: failed to open: %s", getpid(), filename); + + fprintf(f, "%ld", num); + fclose(f); + + if (verbose) + fprintf(stderr, "%d: %s: %ld --> %ld (loop=%d)\n", getpid(), + filename, num - 1, num, loopno); +} + +void clean_lock(void) +{ + if (!lock) + return; + mnt_unlock_file(lock); + mnt_free_lock(lock); +} + +void __attribute__((__noreturn__)) sig_handler(int sig) +{ + errx(EXIT_FAILURE, "\n%d: catch signal: %s\n", getpid(), strsignal(sig)); +} + +int test_lock(struct libmnt_test *ts, int argc, char *argv[]) +{ + time_t synctime = 0; + unsigned int usecs; + struct timeval tv; + 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) { + gettimeofday(&tv, NULL); + if (synctime && synctime - tv.tv_sec > 1) { + usecs = ((synctime - tv.tv_sec) * 1000000UL) - + (1000000UL - tv.tv_usec); + usleep(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 finish after mtab update. We + * simulate this via short sleep -- it's also enough to make + * concurrent processes happy. + */ + if (synctime) + usleep(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/mountP.h b/libmount/src/mountP.h new file mode 100644 index 0000000..0ef073a --- /dev/null +++ b/libmount/src/mountP.h @@ -0,0 +1,402 @@ +/* + * mountP.h - private library header file + * + * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#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 <unistd.h> + +#include "c.h" +#include "list.h" +#include "libmount.h" + +/* features */ +#define CONFIG_LIBMOUNT_ASSERT +#define CONFIG_LIBMOUNT_DEBUG + +#ifdef CONFIG_LIBMOUNT_ASSERT +#include <assert.h> +#endif + +/* + * Debug + */ +#if defined(TEST_PROGRAM) && !defined(LIBMOUNT_DEBUG) +#define CONFIG_LIBMOUNT_DEBUG +#endif + +#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_OPTS (1 << 7) +#define MNT_DEBUG_UPDATE (1 << 8) +#define MNT_DEBUG_UTILS (1 << 9) +#define MNT_DEBUG_CXT (1 << 10) +#define MNT_DEBUG_DIFF (1 << 11) +#define MNT_DEBUG_ALL 0xFFFF + +#ifdef CONFIG_LIBMOUNT_DEBUG +# include <stdio.h> +# include <stdarg.h> + +# define ON_DBG(m, x) do { \ + if ((MNT_DEBUG_ ## m) & libmount_debug_mask) { \ + x; \ + } \ + } while (0) + +# define DBG(m, x) do { \ + if ((MNT_DEBUG_ ## m) & libmount_debug_mask) { \ + fprintf(stderr, "%d: libmount: %8s: ", getpid(), # m); \ + x; \ + } \ + } while (0) + +# define DBG_FLUSH do { \ + if (libmount_debug_mask && \ + libmount_debug_mask != MNT_DEBUG_INIT) \ + fflush(stderr); \ + } while(0) + +extern int libmount_debug_mask; + +static inline void __attribute__ ((__format__ (__printf__, 1, 2))) +mnt_debug(const char *mesg, ...) +{ + va_list ap; + va_start(ap, mesg); + vfprintf(stderr, mesg, ap); + va_end(ap); + fputc('\n', stderr); +} + +static inline void __attribute__ ((__format__ (__printf__, 2, 3))) +mnt_debug_h(void *handler, const char *mesg, ...) +{ + va_list ap; + + fprintf(stderr, "[%p]: ", handler); + va_start(ap, mesg); + vfprintf(stderr, mesg, ap); + va_end(ap); + fputc('\n', stderr); +} + +#else /* !CONFIG_LIBMOUNT_DEBUG */ +# define ON_DBG(m,x) do { ; } while (0) +# define DBG(m,x) do { ; } while (0) +# define DBG_FLUSH do { ; } while(0) +#endif + +/* 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 endswith(const char *s, const char *sx); +extern int startswith(const char *s, const char *sx); + +extern int mnt_is_readonly(const char *path); + +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 char *mnt_get_fs_root(const char *path, const char *mountpoint); +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); + +/* tab.c */ +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 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 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 *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 */ + + 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 valid entries */ + + struct libmnt_cache *cache; /* canonicalized paths/tags cache */ + + int (*errcb)(struct libmnt_table *tb, + const char *filename, int line); + + struct list_head ents; /* list of entries (libmnt_fs) */ +}; + +extern struct libmnt_table *__mnt_new_table_from_file(const char *filename, int fmt); + +/* + * 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 */ +}; + + +/* + * 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 */ + + int (*table_errcb)(struct libmnt_table *tb, /* callback for libmnt_table structs */ + const char *filename, int line); + + 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 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 */ + int ambi; /* libblkid returns ambivalent result */ + + 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 */ +}; + +/* 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_EXTERN_FS (1 << 15) /* cxt->fs is not private */ +#define MNT_FL_EXTERN_FSTAB (1 << 16) /* cxt->fstab is not private */ +#define MNT_FL_EXTERN_CACHE (1 << 17) /* cxt->cache is not private */ + +#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 library */ +#define MNT_FL_MOUNTOPTS_FIXED (1 << 27) + +/* default flags */ +#define MNT_FL_DEFAULT 0 + +/* 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); +extern int __mnt_fs_set_source_ptr(struct libmnt_fs *fs, char *source); +extern int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype); + +/* context.c */ +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_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); +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); + +/* tab_update.c */ +extern int mnt_update_set_filename(struct libmnt_update *upd, + const char *filename, int userspace_only); + +#endif /* _LIBMOUNT_PRIVATE_H */ diff --git a/libmount/src/optmap.c b/libmount/src/optmap.c new file mode 100644 index 0000000..68217ce --- /dev/null +++ b/libmount/src/optmap.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * 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: (usully 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" + +/* + * 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 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 + { 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 _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_NOMTAB | MNT_PREFIX }, /* x- options */ + + { "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 - userpace 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; +} + +/* + * Lookups for 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..16d6d13 --- /dev/null +++ b/libmount/src/optstr.c @@ -0,0 +1,1303 @@ +/* + * Copyright (C) 2008,2009,2012 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: optstr + * @title: Options string + * @short_description: low-level API for work with mount options + * + * This is simple and low-level API to work with mount options that are stored + * in string. + */ +#include <ctype.h> + +#ifdef HAVE_LIBSELINUX +#include <selinux/selinux.h> +#include <selinux/context.h> +#endif + +#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))) + +/* + * Parses the first option from @optstr. The @optstr pointer is set to begin 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; /* begin of the option item */ + if (*p == '"') + open_quote ^= 1; /* reverse the status */ + if (open_quote) + continue; /* still in quoted block */ + if (!sep && *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, mnt_debug("parse error: \"%s\"", optstr0)); + return -EINVAL; +} + +/* + * Locates the first option that match with @name. The @end is set to + * 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); + assert(optstr); + + 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 position to next option + * @name: returns option name + * @namesz: returns option name length + * @value: returns option value or NULL + * @valuesz: returns 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); + + osz = *optstr ? strlen(*optstr) : 0; + + sz = osz + nsz + 1; /* 1: '\0' */ + if (osz) + sz++; /* ',' options separator */ + if (vsz) + 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 (vsz) { + *p++ = '='; + memcpy(p, value, vsz); + p += vsz; + } + *p = '\0'; + + return 0; +} + +/** + * mnt_optstr_append_option: + * @optstr: option string or NULL, returns reallocated string + * @name: value name + * @value: value + * + * Returns: 0 on success or -1 in case of error. After error the @optstr should + * be unmodified. + */ +int mnt_optstr_append_option(char **optstr, const char *name, const char *value) +{ + size_t vsz, nsz; + + 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 reallocated string + * @name: value name + * @value: value + * + * Returns: 0 on success or -1 in case of error. After error the @optstr should + * be unmodified. + */ +int mnt_optstr_prepend_option(char **optstr, const char *name, const char *value) +{ + int rc = 0; + char *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, mnt_debug("failed to prepend '%s[=%s]' to '%s'", + name, value, *optstr)); + return rc; +} + +/** + * mnt_optstr_get_option: + * @optstr: string with comma separated list of options + * @name: requested option name + * @value: returns pointer to the begin 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; + + 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 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 = *optstr; + + do { + struct libmnt_optloc ol; + + mnt_init_optloc(&ol); + + rc = mnt_optstr_locate_option(opt, name, &ol); + if (!rc) { + if (begin) { + /* remove previous instance */ + size_t shift = strlen(*optstr); + + mnt_optstr_remove_option_at(optstr, begin, end); + + /* now all offset are not valied 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 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 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 = NULL; + int sep; + + /* is it necessary to prepend '=' before the substring ? */ + sep = !(pos > *str && *(pos - 1) == '='); + + /* save an offset of the place where we need add substr */ + posoff = pos - *str; + + p = realloc(*str, strsz + sep + subsz + 1); + if (!p) + return -ENOMEM; + + /* zeroize new allocated memory -- valgind loves is... */ + memset(p + strsz, 0, sep + subsz + 1); + + /* set pointers to the reallocated string */ + *str = p; + pos = p + posoff; + + if (possz) + /* create a room for 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 comma separated list of options + * @name: requested option + * @value: new value or NULL + * + * Set or unset 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) + 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 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; + + 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 does 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 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]; + + assert(optstr); + + 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) */ + + 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); + if (fs) + free(*fs); + if (user) + free(*user); + return rc; + } + } + + return 0; +} + +/** + * mnt_optstr_get_options + * @optstr: string with 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 belongs 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 does not belong to mtab + * are ignored. + * + * Returns: 0 on success, or 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; + 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 set/unset + * bites 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; + + assert(optstr); + + 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; + + 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; + + assert(optstr); + + if (!optstr || !map) + return -EINVAL; + + DBG(CXT, mnt_debug("applying 0x%08lu flags to '%s'", flags, *optstr)); + + maps[0] = map; + next = *optstr; + fl = flags; + + /* + * There is a convetion that 'rw/ro' flags is always at the begin of + * the string (athough 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 + * the @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->id) + 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; + } + } + } + + /* add missing options */ + if (fl) { + 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, mnt_debug("new optstr '%s'", *optstr)); + return rc; +err: + DBG(CXT, mnt_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, 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, mnt_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, mnt_debug("SELinux context '%s' translated to '%s'", + p, rc == -1 ? "FAILED" : (char *) raw)); + + free(p); + if (rc == -1 || !raw) + return -EINVAL; + + + /* create 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 comma separated list of options + * @value: pointer to the begin 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, negative number in case of error. + */ +int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next) +{ + int rc = 0; + char *end; + + if (!optstr || !*optstr || !value || !valsz) + return -EINVAL; + + DBG(CXT, mnt_debug("fixing uid")); + + end = value + valsz; + + if (valsz == 7 && !strncmp(value, "useruid", 7) && + (*(value + 7) == ',' || !*(value + 7))) + rc = set_uint_value(optstr, getuid(), value, end, next); + + else if (!isdigit(*value)) { + uid_t id; + char *p = strndup(value, valsz); + if (!p) + return -ENOMEM; + rc = mnt_get_uid(p, &id); + free(p); + + if (!rc) + rc = set_uint_value(optstr, id, value, end, next); + + } else if (next) { + /* nothing */ + *next = value + valsz; + if (**next == ',') + (*next)++; + } + + return rc; +} + +/* + * @optstr: string with comma separated list of options + * @value: pointer to the begin 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, negative number in case of error. + */ +int mnt_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next) +{ + int rc = 0; + char *end; + + if (!optstr || !*optstr || !value || !valsz) + return -EINVAL; + + DBG(CXT, mnt_debug("fixing gid")); + + end = value + valsz; + + if (valsz == 7 && !strncmp(value, "usergid", 7) && + (*(value + 7) == ',' || !*(value + 7))) + rc = set_uint_value(optstr, getgid(), value, end, next); + + else if (!isdigit(*value)) { + gid_t id; + char *p = strndup(value, valsz); + if (!p) + return -ENOMEM; + rc = mnt_get_gid(p, &id); + free(p); + + if (!rc) + rc = set_uint_value(optstr, id, value, end, next); + + } else if (next) { + /* nothing */ + *next = value + valsz; + if (**next == ',') + (*next)++; + } + return rc; +} + +/* + * 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, mnt_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; +} + +#ifdef TEST_PROGRAM + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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..1c159b5 --- /dev/null +++ b/libmount/src/tab.c @@ -0,0 +1,1195 @@ +/* + * Copyright (C) 2008-2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * 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 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 returns the first entry (if UUID matches with the device). + */ +#include <blkid.h> + +#include "mountP.h" +#include "strutils.h" + +/** + * 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, mnt_debug_h(tb, "alloc")); + + INIT_LIST_HEAD(&tb->ents); + return tb; +} + +/** + * mnt_reset_table: + * @tb: tab pointer + * + * Dealocates all entries (filesystems) from the table + * + * 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, mnt_debug_h(tb, "reset")); + + while (!list_empty(&tb->ents)) { + struct libmnt_fs *fs = list_entry(tb->ents.next, + struct libmnt_fs, ents); + mnt_free_fs(fs); + } + + tb->nents = 0; + return 0; +} + +/** + * mnt_free_table: + * @tb: tab pointer + * + * Deallocates tab struct and all entries. + */ +void mnt_free_table(struct libmnt_table *tb) +{ + if (!tb) + return; + + mnt_reset_table(tb); + + DBG(TAB, mnt_debug_h(tb, "free")); + free(tb); +} + +/** + * mnt_table_get_nents: + * @tb: pointer to tab + * + * Returns: number of valid entries in tab. + */ +int mnt_table_get_nents(struct libmnt_table *tb) +{ + assert(tb); + return tb ? tb->nents : 0; +} + +/** + * mnt_table_set_cache: + * @tb: pointer to tab + * @mpc: pointer to struct libmnt_cache instance + * + * Setups 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. + * + * 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) +{ + assert(tb); + if (!tb) + return -EINVAL; + 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) +{ + assert(tb); + return tb ? tb->cache : NULL; +} + +/** + * mnt_table_add_fs: + * @tb: tab pointer + * @fs: new entry + * + * Adds a new entry to tab. + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + assert(tb); + assert(fs); + + if (!tb || !fs) + return -EINVAL; + + list_add_tail(&fs->ents, &tb->ents); + + DBG(TAB, mnt_debug_h(tb, "add entry: %s %s", + mnt_fs_get_source(fs), mnt_fs_get_target(fs))); + tb->nents++; + return 0; +} + +/** + * mnt_table_remove_fs: + * @tb: tab pointer + * @fs: new entry + * + * Returns: 0 on success or negative number in case of error. + */ +int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs) +{ + assert(tb); + assert(fs); + + if (!tb || !fs) + return -EINVAL; + list_del(&fs->ents); + tb->nents--; + return 0; +} + +/** + * mnt_table_get_root_fs: + * @tb: mountinfo file (/proc/self/mountinfo) + * @root: returns pointer to the root filesystem (/) + * + * Returns: 0 on success or -1 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; + + assert(tb); + assert(root); + + if (!tb || !root) + return -EINVAL; + + DBG(TAB, mnt_debug_h(tb, "lookup root fs")); + + 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 (!id) + break; /* @tab is not mountinfo file? */ + + if (!*root || id < root_id) { + *root = fs; + root_id = id; + } + } + + return root_id ? 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 how was mounted (according to + * IDs in /proc/self/mountinfo). + * + * Returns: 0 on success, negative number in case of error or 1 at 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) + return -EINVAL; + + DBG(TAB, mnt_debug_h(tb, "lookup next child of %s", + mnt_fs_get_target(parent))); + + parent_id = mnt_fs_get_id(parent); + if (!parent_id) + return -EINVAL; + + /* 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 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_id) + 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 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); + * } + * mnt_free_table(fi); + * </programlisting> + * </informalexample> + * + * lists all mountpoints from fstab in backward order. + */ +int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs **fs) +{ + int rc = 1; + + assert(tb); + assert(itr); + assert(fs); + + 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_find_next_fs: + * @tb: table + * @itr: iterator + * @match_func: function returns 1 or 0 + * @userdata: extra data for match_func + * @fs: returns pointer to the next matching table entry + * + * This function allows search 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, mnt_debug_h(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; +} + +/** + * 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) +{ + assert(tb); + assert(itr); + assert(fs); + + if (!tb || !itr || !fs) + return -EINVAL; + + MNT_ITER_INIT(itr, &tb->ents); + itr->p = &fs->ents; + + return 0; +} + +/** + * mnt_table_find_target: + * @tb: tab pointer + * @path: mountpoint directory + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * Try to lookup an entry in given tab, possible are three iterations, first + * with @path, second with realpath(@path) and third with realpath(@path) + * against realpath(fs->target). The 2nd and 3rd iterations are 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_target(struct libmnt_table *tb, const char *path, int direction) +{ + struct libmnt_iter itr; + struct libmnt_fs *fs = NULL; + char *cn; + + assert(tb); + assert(path); + + if (!tb || !path) + return NULL; + + DBG(TAB, mnt_debug_h(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; + } + if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache))) + return NULL; + + /* 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-canonicaled path in struct libmnt_table + * -- note that mountpoint in /proc/self/mountinfo is already + * canonicalized by 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_path(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 given tab, possible are four iterations, first + * with @path, second with realpath(@path), third with tags (LABEL, UUID, ..) + * from @path and fourth with realpath(@path) against realpath(entry->srcpath). + * + * The 2nd, 3rd and 4th iterations are not performed when @tb cache is not + * set (see mnt_table_set_cache()). + * + * 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; + char *cn; + const char *p; + + assert(tb); + + DBG(TAB, mnt_debug_h(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)) + 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; + + /* canonicalized paths in struct libmnt_table */ + if (ntags < mnt_table_get_nents(tb)) { + 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 unaccessible, try evaluate all TAGs in @tb + * by udev symlinks -- this could be expensive on systems + * with 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 <= mnt_table_get_nents(tb)) { + 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 given tab, first attempt is lookup by @tag and + * @val, for the second attempt the tag is evaluated (converted to the device + * name) and mnt_table_find_srcpath() is preformed. 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; + + assert(tb); + assert(tag); + assert(val); + + if (!tb || !tag || !val) + return NULL; + + DBG(TAB, mnt_debug_h(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_source: + * @tb: tab pointer + * @source: TAG or path + * @direction: MNT_ITER_{FORWARD,BACKWARD} + * + * This is high-level API for mnt_table_find_{srcpath,tag}. You needn't to care + * about @source format (device, LABEL, UUID, ...). This function parses @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 = NULL; + + assert(tb); + + if (!tb) + return NULL; + + DBG(TAB, mnt_debug_h(tb, "lookup SOURCE: %s", source)); + + if (source && strchr(source, '=')) { + char *tag, *val; + + if (blkid_parse_tag_string(source, &tag, &val) == 0) { + + fs = mnt_table_find_tag(tb, tag, val, direction); + + free(tag); + free(val); + } + } else + fs = mnt_table_find_srcpath(tb, source, direction); + + 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 that + * 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; + + assert(tb); + assert(target); + + if (!tb || !target) + return NULL; + + DBG(TAB, mnt_debug_h(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 valid device number for 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; + + assert(tb); + + if (!tb) + return NULL; + + DBG(TAB, mnt_debug_h(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; +} + +/* + * tb: /proc/self/mountinfo + * fs: filesystem + * mountflags: MS_BIND or 0 + * fsroot: fs-root that will be probably used in the mountinfo file + * for @fs after mount(2) + * + * For btrfs subvolumes this function returns NULL, but @fsroot properly set. + * + * 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, *mnt = NULL; + const char *fstype; + struct libmnt_fs *src_fs = NULL; + + assert(fs); + assert(fsroot); + + DBG(TAB, mnt_debug("lookup fs-root for %s", mnt_fs_get_source(fs))); + + fstype = mnt_fs_get_fstype(fs); + + if (tb && (mountflags & MS_BIND)) { + const char *src, *src_root; + char *xsrc = NULL; + + DBG(TAB, mnt_debug("fs-root for bind")); + + src = xsrc = mnt_resolve_spec(mnt_fs_get_source(fs), tb->cache); + if (src) + mnt = mnt_get_mountpoint(src); + if (mnt) + root = mnt_get_fs_root(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, mnt_debug("not found '%s' in mountinfo -- using default", mnt)); + goto dflt; + } + + /* on btrfs the subvolume is used as fs-root in + * /proc/self/mountinfo, so we have to get the original subvolume + * name from src_fs and prepend the subvolume name to the + * fs-root path + */ + src_root = mnt_fs_get_root(src_fs); + if (src_root && !startswith(root, src_root)) { + size_t sz = strlen(root) + strlen(src_root) + 1; + char *tmp = malloc(sz); + + if (!tmp) + goto err; + snprintf(tmp, sz, "%s%s", src_root, root); + free(root); + root = tmp; + } + } + + /* + * btrfs-subvolume mount -- get subvolume name and use it as a root-fs path + */ + else if (fstype && !strcmp(fstype, "btrfs")) { + char *vol = NULL, *p; + size_t sz, volsz = 0; + + if (mnt_fs_get_option(fs, "subvol", &vol, &volsz)) + goto dflt; + + DBG(TAB, mnt_debug("setting FS root: btrfs subvol")); + + 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'; + } +dflt: + if (!root) { + root = strdup("/"); + if (!root) + goto err; + } + *fsroot = root; + + DBG(TAB, mnt_debug("FS root result: %s", root)); + + free(mnt); + return src_fs; +err: + free(root); + free(mnt); + return NULL; +} + +static 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_table_is_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 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 + * triggers automount on autofs mountpoints! + * + * Don't use it if you want to know if a device is mounted, just use + * mnt_table_find_source() for the device. + * + * This function is designed mostly for "mount -a". + * + * TODO: check for loopdev (see mount/mount.c is_fstab_entry_mounted(). + * + * Returns: 0 or 1 + */ +int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs) +{ + char *root = NULL; + const char *src = NULL, *tgt = NULL; + char *xtgt = NULL; + int rc = 0; + + assert(tb); + assert(fstab_fs); + + if (mnt_fs_is_swaparea(fstab_fs) || mnt_table_get_nents(tb) == 0) + return 0; + + if (is_mountinfo(tb)) { + /* @tb is mountinfo, so we can try to use fs-roots */ + struct libmnt_fs *fs; + int flags = 0; + + if (mnt_fs_get_option(fstab_fs, "bind", NULL, NULL) == 0) + flags = MS_BIND; + + fs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root); + if (fs) + src = mnt_fs_get_srcpath(fs); + } + + 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); + + tgt = mnt_fs_get_target(fstab_fs); + + if (tgt && src) { + struct libmnt_iter itr; + struct libmnt_fs *fs; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + + while (mnt_table_next_fs(tb, &itr, &fs) == 0) { + + if (!mnt_fs_streq_srcpath(fs, src)) + continue; + + if (root) { + const char *r = mnt_fs_get_root(fs); + if (!r || strcmp(r, root) != 0) + continue; + } + + /* + * Compare target, try to minimize 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 */ + } + + free(root); + return rc; +} + +#ifdef TEST_PROGRAM + +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 default */ +} + +struct libmnt_table *create_table(const char *file) +{ + struct libmnt_table *tb; + + if (!file) + return NULL; + tb = mnt_new_table(); + if (!tb) + goto err; + + 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_free_table(tb); + return NULL; +} + +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]); + 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_free_fs(fs); + rc = 0; +done: + mnt_free_table(tb); + return rc; +} + +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; + + tb = create_table(argv[1]); + if (!tb) + return -1; + + itr = mnt_new_iter(MNT_ITER_FORWARD); + if (!itr) + goto done; + + while(mnt_table_next_fs(tb, itr, &fs) == 0) + mnt_fs_print_debug(fs, stdout); + rc = 0; +done: + mnt_free_iter(itr); + mnt_free_table(tb); + return rc; +} + +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); + if (!tb) + goto done; + + /* create a cache for canonicalized paths */ + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, 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_free_table(tb); + mnt_free_cache(mpc); + return rc; +} + +int test_find_bw(struct libmnt_test *ts, int argc, char *argv[]) +{ + return test_find(ts, argc, argv, MNT_ITER_BACKWARD); +} + +int test_find_fw(struct libmnt_test *ts, int argc, char *argv[]) +{ + return test_find(ts, argc, argv, MNT_ITER_FORWARD); +} + +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]); + if (!tb) + return -1; + mpc = mnt_new_cache(); + if (!mpc) + goto done; + mnt_table_set_cache(tb, 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_free_table(tb); + mnt_free_cache(mpc); + 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 rc; + + 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]); + 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); + + 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)); + } + + rc = 0; +done: + mnt_free_table(tb); + mnt_free_table(fstab); + mnt_free_cache(mpc); + mnt_free_iter(itr); + return rc; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--parse", test_parse, "<file> parse and print tab" }, + { "--find-forward", test_find_fw, "<file> <source|target> <string>" }, + { "--find-backward", test_find_bw, "<file> <source|target> <string>" }, + { "--find-pair", test_find_pair, "<file> <source> <target>" }, + { "--copy-fs", test_copy_fs, "<file> copy root FS from the file" }, + { "--is-mounted", test_is_mounted, "<fstab> check what from <file> are 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..f01f889 --- /dev/null +++ b/libmount/src/tab_diff.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2011 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: tabdiff + * @title: Monitor mountinfo changes + * @short_description: monitor 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 unuused 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, mnt_debug_h(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); + 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, mnt_debug_h(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 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; + + assert(df); + assert(df); + + 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, mnt_debug_h(df, "reseting")); + + /* zeroize all entries and move them to the list of unuused + */ + 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); + + 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, mnt_debug_h(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); + + 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, mnt_debug_h(df, "analyze new=%p (%d entries), " + "old=%p (%d entries)", + new_tab, nn, old_tab, 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) { + de->oper = MNT_TABDIFF_MOVE; + de->old_fs = fs; + } else + tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT); + } + } +done: + DBG(DIFF, mnt_debug_h(df, "%d changes detected", df->nchanges)); + return df->nchanges; +} + +#ifdef TEST_PROGRAM + +int test_diff(struct libmnt_test *ts, int argc, char *argv[]) +{ + struct libmnt_table *tb_old = NULL, *tb_new = NULL; + struct libmnt_tabdiff *diff = NULL; + 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_free_table(tb_old); + mnt_free_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..ab5d51a --- /dev/null +++ b/libmount/src/tab_parse.c @@ -0,0 +1,944 @@ +/* + * Copyright (C) 2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#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 "at.h" +#include "mangle.h" +#include "mountP.h" +#include "pathnames.h" +#include "strutils.h" + +static inline char *skip_spaces(char *s) +{ + assert(s); + + while (*s == ' ' || *s == '\t') + s++; + return s; +} + +static int next_number(char **s, int *num) +{ + char *end = NULL; + + assert(num); + assert(s); + + *s = skip_spaces(*s); + if (!**s) + return -1; + *num = strtol(*s, &end, 10); + if (end == NULL || *s == end) + return -1; + + *s = end; + + /* valid end of number is space or terminator */ + if (*end == ' ' || *end == '\t' || *end == '\0') + return 0; + return -1; +} + +/* + * Parses one line from {fs,m}tab + */ +static int mnt_parse_table_line(struct libmnt_fs *fs, 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 does 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, mnt_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); + rc = -EINVAL; + } + + if (rc) { + free(src); + free(fstype); + free(optstr); + DBG(TAB, mnt_debug("tab parse error: [set vars, rc=%d]\n", rc)); + return rc; /* error */ + } + + fs->passno = fs->freq = 0; + + if (xrc == 4 && n) + s = skip_spaces(s + n); + if (xrc == 4 && *s) { + if (next_number(&s, &fs->freq) != 0) { + if (*s) { + DBG(TAB, mnt_debug("tab parse error: [freq]")); + rc = -EINVAL; + } + } else if (next_number(&s, &fs->passno) != 0 && *s) { + DBG(TAB, mnt_debug("tab parse error: [passno]")); + rc = -EINVAL; + } + } + + return rc; +} + +/* + * Parses one line from mountinfo file + */ +static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, char *s) +{ + int rc, end = 0; + unsigned int maj, min; + char *fstype = NULL, *src = NULL, *p; + + rc = sscanf(s, "%u " /* (1) id */ + "%u " /* (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, mnt_debug("mountinfo parse error: not found separator")); + return -EINVAL; + } + s = p + 3; + + rc += sscanf(s, UL_SCNsA" " /* (8) FS type */ + UL_SCNsA" " /* (9) source */ + UL_SCNsA, /* (10) fs options (fs specific) */ + + &fstype, + &src, + &fs->fs_optstr); + + if (rc >= 10) { + fs->flags |= MNT_FS_KERNEL; + fs->devno = makedev(maj, min); + + 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 the one string */ + fs->optstr = mnt_fs_strdup_options(fs); + if (!fs->optstr) + rc = -ENOMEM; + } else { + free(fstype); + free(src); + DBG(TAB, mnt_debug( + "mountinfo parse error [sscanf rc=%d]: '%s'", rc, s)); + rc = -EINVAL; + } + 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) { + 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, mnt_debug("utab parse error: ENOMEM")); + return -ENOMEM; +} + +/* + * Parses one line from /proc/swaps + */ +static int mnt_parse_swaps_line(struct libmnt_fs *fs, char *s) +{ + uintmax_t fsz, usz; + int rc; + char *src = NULL; + + rc = sscanf(s, UL_SCNsA" " /* (1) source */ + UL_SCNsA" " /* (2) type */ + "%jd" /* (3) size */ + "%jd" /* (4) used */ + "%d", /* priority */ + + &src, + &fs->swaptype, + &fsz, + &usz, + &fs->priority); + + if (rc == 5) { + size_t sz; + + fs->size = fsz; + fs->usedsize = usz; + + unmangle_string(src); + + /* remove "(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'; + } + + rc = mnt_fs_set_source(fs, src); + if (!rc) + mnt_fs_set_fstype(fs, "swap"); + free(src); + } else { + DBG(TAB, mnt_debug("tab parse error: [sscanf rc=%d]: '%s'", rc, s)); + rc = -EINVAL; + } + + return rc; +} + + +/* + * Returns {m,fs}tab or mountinfo file format (MNT_FMT_*) + * + * Note that we aren't trying to guess utab file format, because this file has + * to be always parsed by private libmount routines with explicitly defined + * format. + * + * mountinfo: "<number> <number> ... " + */ +static int guess_table_format(char *line) +{ + unsigned int a, b; + + DBG(TAB, mnt_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 */ +} + +/* + * Read and parse the next line from {fs,m}tab or mountinfo + */ +static int mnt_table_parse_next(struct libmnt_table *tb, FILE *f, struct libmnt_fs *fs, + const char *filename, int *nlines) +{ + char buf[BUFSIZ]; + char *s; + int rc; + + assert(tb); + assert(f); + assert(fs); + + /* read the next non-blank non-comment line */ +next_line: + do { + if (fgets(buf, sizeof(buf), f) == NULL) + return -EINVAL; + ++*nlines; + s = index (buf, '\n'); + if (!s) { + /* Missing final newline? Otherwise extremely */ + /* long line - assume file was corrupted */ + if (feof(f)) { + DBG(TAB, mnt_debug_h(tb, + "%s: no final newline", filename)); + s = index (buf, '\0'); + } else { + DBG(TAB, mnt_debug_h(tb, + "%s:%d: missing newline at line", + filename, *nlines)); + goto err; + } + } + *s = '\0'; + if (--s >= buf && *s == '\r') + *s = '\0'; + s = skip_spaces(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, mnt_debug_h(tb, "%s:%d: %s parse error", filename, *nlines, + 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 + * errcb() function. See mnt_table_set_parser_errcb(). + */ + return tb->errcb ? tb->errcb(tb, filename, *nlines) : 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, mnt_debug("TID for %s is %d", filename, tid)); +done: + free(path); + return tid; +} + +/** + * 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 nlines = 0; + int rc = -1; + int flags = 0; + pid_t tid = -1; + + assert(tb); + assert(f); + assert(filename); + + DBG(TAB, mnt_debug_h(tb, "%s: start parsing (%d entries)", + filename, mnt_table_get_nents(tb))); + + /* 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)) { + struct libmnt_fs *fs = mnt_new_fs(); + + if (!fs) + goto err; + + rc = mnt_table_parse_next(tb, f, fs, filename, &nlines); + if (!rc) { + rc = mnt_table_add_fs(tb, fs); + fs->flags |= flags; + if (tb->fmt == MNT_FMT_MOUNTINFO && filename) { + if (tid == -1) + tid = path_to_tid(filename); + fs->tid = tid; + } + } + if (rc) { + mnt_free_fs(fs); + if (rc == 1) + continue; /* recoverable error */ + if (feof(f)) + break; + goto err; /* fatal error */ + } + } + + DBG(TAB, mnt_debug_h(tb, "%s: stop parsing (%d entries)", + filename, mnt_table_get_nents(tb))); + return 0; +err: + DBG(TAB, mnt_debug_h(tb, "%s: parse error (rc=%d)", filename, rc)); + return rc; +} + +/** + * mnt_table_parse_file: + * @tb: tab pointer + * @filename: file + * + * Parses whole table (e.g. /etc/mtab) and appends new records to the @tab. + * + * The libmount parser ignores broken (syntax error) lines, these lines are + * reported to caller by 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; + + assert(tb); + assert(filename); + + if (!filename || !tb) + return -EINVAL; + + f = fopen(filename, "r"); + if (f) { + rc = mnt_table_parse_stream(tb, f, filename); + fclose(f); + } else + return -errno; + + 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 (fstat_at(dd, ".", d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dd, ".", d->d_name, O_RDONLY, "r"); + 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 use "at" functions rather than play 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 (fstat_at(dirfd(dir), _PATH_MNTTAB_DIR, d->d_name, &st, 0) || + !S_ISREG(st.st_mode)) + continue; + + f = fopen_at(dirfd(dir), _PATH_MNTTAB_DIR, + d->d_name, O_RDONLY, "r"); + 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 starts 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) +{ + struct libmnt_table *tb; + struct stat st; + + assert(filename); + + if (!filename) + return NULL; + if (stat(filename, &st)) + return NULL; + tb = mnt_new_table(); + if (tb) { + tb->fmt = fmt; + if (mnt_table_parse_file(tb, filename) != 0) { + mnt_free_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 to use 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) +{ + return __mnt_new_table_from_file(filename, MNT_FMT_GUESS); +} + +/** + * 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; + + assert(dirname); + + if (!dirname) + return NULL; + tb = mnt_new_table(); + if (tb && mnt_table_parse_dir(tb, dirname) != 0) { + mnt_free_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 syntax error. The callback function could be used for errors + * evaluation, libmount will continue/stop parsing according to callback return + * codes: + * + * <0 : fatal error (abort parsing) + * 0 : success (parsing continue) + * >0 : recoverable error (the line is ignored, parsing continue). + * + * 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)) +{ + assert(tb); + tb->errcb = cb; + 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) +{ + assert(tb); + + 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; + + assert(tb); + + 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 found 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; + + assert(tb); + assert(uf); + if (!tb || !uf) + return NULL; + + DBG(TAB, mnt_debug_h(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, mnt_debug_h(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, mnt_debug_h(tb, "found fs:")); + DBG(TAB, mnt_fs_print_debug(fs, stderr)); + } + return fs; +} + +/** + * mnt_table_parse_mtab: + * @tb: table + * @filename: overwrites default (/etc/mtab or $LIBMOUNT_MTAB) or NULL + * + * This function parses /etc/mtab or /proc/self/mountinfo + + * /run/mount/utabs or /proc/mounts. + * + * 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) +{ + int rc; + const char *utab = NULL; + + if (mnt_has_regular_mtab(&filename, NULL)) { + + DBG(TAB, mnt_debug_h(tb, "force %s usage", filename)); + + rc = mnt_table_parse_file(tb, filename); + if (!rc) + return 0; + filename = NULL; /* failed */ + } + + /* + * useless /etc/mtab + * -- read kernel information from /proc/self/mountinfo + */ + tb->fmt = MNT_FMT_MOUNTINFO; + rc = mnt_table_parse_file(tb, _PATH_PROC_MOUNTINFO); + if (rc) { + /* hmm, old kernel? ...try /proc/mounts */ + tb->fmt = MNT_FMT_MTAB; + return mnt_table_parse_file(tb, _PATH_PROC_MOUNTS); + } + + /* + * try to read user specific information from /run/mount/utabs + */ + utab = mnt_get_utab_path(); + if (utab) { + struct libmnt_table *u_tb = __mnt_new_table_from_file(utab, MNT_FMT_UTAB); + + if (u_tb) { + struct libmnt_fs *u_fs; + struct libmnt_iter itr; + + mnt_reset_iter(&itr, MNT_ITER_BACKWARD); + + /* merge user options into mountinfo from kernel */ + while(mnt_table_next_fs(u_tb, &itr, &u_fs) == 0) + mnt_table_merge_user_fs(tb, u_fs); + + mnt_free_table(u_tb); + } + } + return 0; +} diff --git a/libmount/src/tab_update.c b/libmount/src/tab_update.c new file mode 100644 index 0000000..4f04c8e --- /dev/null +++ b/libmount/src/tab_update.c @@ -0,0 +1,862 @@ +/* + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: update + * @title: Tables update + * @short_description: userspace mount information management + * + * The struct libmnt_update provides abstraction to manage mount options in + * userspace independently on system configuration. This low-level API works on + * system with and without /etc/mtab. On systems without the regular /etc/mtab + * file are userspace mount options (e.g. user=) stored to 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, mnt_debug_h(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, mnt_debug_h(upd, "free")); + + mnt_free_fs(upd->fs); + mnt_free_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; + + assert(upd); + + /* 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 + */ + mnt_has_regular_mtab(&path, &rw); + 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 file name (e.g. /etc/mtab) for 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 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; + + assert(upd); + assert(target || fs); + + if (!upd) + return -EINVAL; + if ((mountflags & MS_MOVE) && (!fs || !mnt_fs_get_srcpath(fs))) + return -EINVAL; + if (target && fs) + return -EINVAL; + + DBG(UPDATE, mnt_debug_h(upd, + "resetting FS [fs=0x%p, target=%s, flags=0x%08lx]", + fs, target, mountflags)); + if (fs) { + DBG(UPDATE, mnt_debug_h(upd, "FS template:")); + DBG(UPDATE, mnt_fs_print_debug(fs, stderr)); + } + + mnt_free_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, mnt_debug_h(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, mnt_debug_h(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 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 utabs 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 = NULL, *a = NULL; + char *u = NULL; + + assert(fs); + assert(upd); + assert(upd->fs == NULL); + assert(!(mountflags & MS_MOVE)); + + DBG(UPDATE, mnt_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, mnt_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, mnt_debug("utab entry OK")); + return 0; +err: + free(u); + mnt_free_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 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; + int rc = 0; + + DBG(UPDATE, mnt_debug("setting FS root")); + + assert(upd); + assert(upd->fs); + assert(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; + } + } + + 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; + char *m1, *m2, *m3, *m4; + int rc; + + assert(fs); + assert(f); + + 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) { + 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; + + assert(fs); + assert(f); + + 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, mnt_debug_h(upd, "%s: updating", upd->filename)); + + fd = mnt_open_uniq_filename(upd->filename, &uq); + if (fd < 0) + return fd; /* error */ + + f = fdopen(fd, "w"); + if (f) { + struct stat st; + struct libmnt_iter itr; + struct libmnt_fs *fs; + + mnt_reset_iter(&itr, MNT_ITER_FORWARD); + 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, mnt_debug_h(upd, + "%s: write entry failed: %m", uq)); + goto leave; + } + } + + if (fflush(f) != 0) { + rc = -errno; + DBG(UPDATE, mnt_debug_h(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); + return rc; +} + +static int add_file_entry(struct libmnt_table *tb, struct libmnt_update *upd) +{ + struct libmnt_fs *fs = mnt_copy_fs(NULL, upd->fs); + + if (!fs) + return -ENOMEM; + + mnt_table_add_fs(tb, 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, mnt_debug_h(upd, "%s: add entry", upd->filename)); + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return rc; + + tb = __mnt_new_table_from_file(upd->filename, + upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB); + if (tb) + rc = add_file_entry(tb, upd); + if (lc) + mnt_unlock_file(lc); + + mnt_free_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, mnt_debug_h(upd, "%s: remove entry", upd->filename)); + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return rc; + + tb = __mnt_new_table_from_file(upd->filename, + upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB); + 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); + mnt_free_fs(rem); + } + } + if (lc) + mnt_unlock_file(lc); + + mnt_free_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; + + DBG(UPDATE, mnt_debug_h(upd, "%s: modify target", upd->filename)); + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return rc; + + tb = __mnt_new_table_from_file(upd->filename, + upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB); + 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_free_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, mnt_debug_h(upd, "%s: modify options", upd->filename)); + + fs = upd->fs; + + if (lc) + rc = mnt_lock_file(lc); + if (rc) + return rc; + + tb = __mnt_new_table_from_file(upd->filename, + upd->userspace_only ? MNT_FMT_UTAB : MNT_FMT_MTAB); + 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_free_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 + * the 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; + + assert(upd); + + if (!upd || !upd->filename) + return -EINVAL; + if (!upd->ready) + return 0; + + DBG(UPDATE, mnt_debug_h(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, mnt_debug_h(upd, "%s: update tab: done [rc=%d]", + upd->filename, rc)); + if (lc != lc0) + mnt_free_lock(lc); + 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, mnt_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; + } + + /* [... here should be mount(2) call ...] */ + + 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_free_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_free_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_free_fs(fs); + return rc; +} + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--add", test_add, "<src> <target> <type> <options> add 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" }, + { 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..2da00b4 --- /dev/null +++ b/libmount/src/test.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * + * 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..63d1079 --- /dev/null +++ b/libmount/src/utils.c @@ -0,0 +1,991 @@ +/* + * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: utils + * @title: Utils + * @short_description: misc utils. + */ +#include <ctype.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> + +#include "strutils.h" +#include "pathnames.h" +#include "mountP.h" +#include "mangle.h" +#include "canonicalize.h" +#include "env.h" +#include "match.h" + +int endswith(const char *s, const char *sx) +{ + ssize_t off; + + assert(s); + assert(sx); + + off = strlen(s); + if (!off) + return 0; + off -= strlen(sx); + if (off < 0) + return 0; + + return !strcmp(s + off, sx); +} + +int startswith(const char *s, const char *sx) +{ + size_t off; + + assert(s); + assert(sx); + + off = strlen(sx); + if (!off) + return 0; + + return !strncmp(s, sx, off); +} + +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 = *(const char **)v1; + const char *s2 = *(const char **)v2; + + return strcmp(s1, s2); +} + +/* returns basename and keeps dirname in the @path, if @path is "/" (root) + * then returns empty string */ +static char *stripoff_last_component(char *path) +{ + char *p = path ? strrchr(path, '/') : NULL; + + if (!p) + return NULL; + *p = '\0'; + return p + 1; +} + +/* + * Note that the @target has to be absolute path (so at least "/"). The + * @filename returns allocated buffer with 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, mnt_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, mnt_debug("failed to chdir to %s: %m", parent)); + rc = -errno; + goto err; + } + if (!getcwd(cwd, sizeof(cwd))) { + DBG(UTILS, mnt_debug("failed to obtain current directory: %m")); + rc = -errno; + goto err; + } + if (strcmp(cwd, parent) != 0) { + DBG(UTILS, mnt_debug( + "unexpected chdir (expected=%s, cwd=%s)", parent, cwd)); + goto err; + } + + DBG(CXT, mnt_debug( + "current directory moved to %s [last_component='%s']", + parent, last)); + + *filename = buf; + + if (!last || !*last) + memcpy(*filename, ".", 2); + else + memcpy(*filename, last, strlen(last) + 1); + return 0; +err: + free(buf); + return rc; +} + +/* + * Check if @path is on read-only filesystem independently on 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_FUTIMENS + /* + * 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 read-write filesystem with read-only VFS node (aka -o remount,ro,bind) + */ + { + struct timespec times[2]; + + 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: new 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: new 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", + "configfs", + "cpuset", + "debugfs", + "devfs", + "devpts", + "devtmpfs", + "dlmfs", + "fuse.gvfs-fuse-daemon", + "fusectl", + "hugetlbfs", + "mqueue", + "nfsd", + "none", + "pipefs", + "proc", + "pstore", + "ramfs", + "rootfs", + "rpc_pipefs", + "securityfs", + "sockfs", + "spufs", + "sysfs", + "tmpfs" + }; + + 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 (!type) + return 0; + 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; +} + +/** + * mnt_match_fstype: + * @type: filesystem type + * @pattern: filesystem name or comma delimited list of names + * + * The @pattern list of filesystem 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); +} + + +/* Returns 1 if needle found or noneedle not found in haystack + * Otherwise returns 0 + */ +static int check_option(const char *haystack, size_t len, + const char *needle, size_t needle_len) +{ + const char *p; + int no = 0; + + if (needle_len >= 1 && *needle == '+') { + needle++; + needle_len--; + } else if (needle_len >= 2 && !strncmp(needle, "no", 2)) { + no = 1; + needle += 2; + needle_len -= 2; + } + + for (p = haystack; p && p < haystack + len; p++) { + char *sep = strchr(p, ','); + size_t plen = sep ? (size_t) (sep - p) : + len - (p - haystack); + + if (plen == needle_len) { + if (!strncmp(p, needle, plen)) + return !no; /* foo or nofoo was found */ + } + p += plen; + } + + return no; /* foo or nofoo was not found */ +} + +/** + * mnt_match_options: + * @optstr: options string + * @pattern: comma delimited list of options + * + * The "no" could 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 disable by "+" prefix, for example + * "+noauto" matches if @optstr literally contains "noauto" string. + * + * "xxx,yyy,zzz" : "nozzz" -> False + * + * "xxx,yyy,zzz" : "xxx,noeee" -> True + * + * "bar,zzz" : "nofoo" -> True + * + * "nofoo,bar" : "+nofoo" -> True + * + * "bar,zzz" : "+nofoo" -> False + * + * + * 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) +{ + const char *p; + size_t len, optstr_len = 0; + + if (!pattern && !optstr) + return 1; + if (!pattern) + return 0; + + len = strlen(pattern); + if (optstr) + optstr_len = strlen(optstr); + + for (p = pattern; p < pattern + len; p++) { + char *sep = strchr(p, ','); + size_t plen = sep ? (size_t) (sep - p) : + len - (p - pattern); + + if (!plen) + continue; /* if two ',' appear in a row */ + + if (!check_option(optstr, optstr_len, p, plen)) + return 0; /* any match failure means failure */ + + p += plen; + } + + /* no match failures in list means success */ + return 1; +} + +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); + if (!name) + goto err; + (*filesystems)[n] = name; + (*filesystems)[n + 1] = NULL; + 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[128]; + + f = fopen(filename, "r"); + if (!f) + return 0; + + 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 (pattern && !mnt_match_fstype(name, pattern)) + continue; + rc = add_filesystem(filesystems, name); + if (rc) + break; + } + + fclose(f); + return rc; +} + +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) + return rc; + return get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern); +} + +static size_t get_pw_record_size(void) +{ +#ifdef _SC_GETPW_R_SIZE_MAX + long sz = sysconf(_SC_GETPW_R_SIZE_MAX); + if (sz > 0) + return sz; +#endif + return 16384; +} + +/* + * Returns allocated string with username or NULL. + */ +char *mnt_get_username(const uid_t uid) +{ + struct passwd pwd; + struct passwd *res; + size_t sz = get_pw_record_size(); + char *buf, *username = NULL; + + buf = malloc(sz); + if (!buf) + return NULL; + + if (!getpwuid_r(uid, &pwd, buf, sz, &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; + size_t sz = get_pw_record_size(); + char *buf; + + if (!username || !uid) + return -EINVAL; + + buf = malloc(sz); + if (!buf) + return -ENOMEM; + + if (!getpwnam_r(username, &pwd, buf, sz, &pw) && pw) { + *uid= pw->pw_uid; + rc = 0; + } else { + DBG(UTILS, mnt_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; + size_t sz = get_pw_record_size(); + char *buf; + + if (!groupname || !gid) + return -EINVAL; + + buf = malloc(sz); + if (!buf) + return -ENOMEM; + + if (!getgrnam_r(groupname, &grp, buf, sz, &gr) && gr) { + *gid= gr->gr_gid; + rc = 0; + } else { + DBG(UTILS, mnt_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) +{ + int fd; + + if (!filename) + return -EINVAL; + + fd = open(filename, O_RDWR|O_CREAT, S_IWUSR| \ + S_IRUSR|S_IRGRP|S_IROTH); + if (fd >= 0) { + close(fd); + return 0; + } + return -errno; +} + +/** + * 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, mnt_debug("mtab: %s", filename)); + + rc = lstat(filename, &st); + + if (rc == 0) { + /* file exist */ + if (S_ISREG(st.st_mode)) { + if (writable) + *writable = !try_write(filename); + return 1; + } + goto done; + } + + /* try to create the file */ + if (writable) { + *writable = !try_write(filename); + if (*writable) + return 1; + } + +done: + DBG(UTILS, mnt_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, mnt_debug("utab: %s", filename)); + + rc = lstat(filename, &st); + + if (rc == 0) { + /* file exist */ + if (S_ISREG(st.st_mode)) { + if (writable) + *writable = !try_write(filename); + return 1; + } + goto done; /* it's not 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); + free(dirname); + if (rc && errno != EEXIST) + goto done; /* probably EACCES */ + + *writable = !try_write(filename); + if (*writable) + return 1; + } +done: + DBG(UTILS, mnt_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 *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 uniques filename + */ +int mnt_open_uniq_filename(const char *filename, char **name) +{ + int rc, fd; + char *n; + + assert(filename); + + if (name) + *name = NULL; + + rc = asprintf(&n, "%s.XXXXXX", filename); + if (rc <= 0) + return -errno; + + fd = mkstemp(n); + if (fd >= 0 && name) + *name = n; + else + free(n); + + return fd < 0 ? -errno : 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. + * + * Returns: target of mounted device or NULL on error + */ +char *mnt_get_mountpoint(const char *path) +{ + char *mnt = strdup(path); + struct stat st; + dev_t dir, base; + + if (!mnt) + return NULL; + if (*mnt == '/' && *(mnt + 1) == '\0') + goto done; + + if (stat(mnt, &st)) + goto err; + base = st.st_dev; + + do { + char *p = stripoff_last_component(mnt); + + if (!p) + break; + if (stat(*mnt ? mnt : "/", &st)) + goto err; + dir = st.st_dev; + if (dir != base) { + if (p > mnt) + *(p - 1) = '/'; + goto done; + } + base = dir; + } while (mnt && *(mnt + 1) != '\0'); + + memcpy(mnt, "/", 2); +done: + DBG(UTILS, mnt_debug("%s mountpoint is %s", path, mnt)); + return mnt; +err: + free(mnt); + return NULL; +} + +char *mnt_get_fs_root(const char *path, const char *mnt) +{ + char *m = (char *) mnt, *res; + const char *p; + size_t sz; + + if (!m) + m = mnt_get_mountpoint(path); + if (!m) + return NULL; + + sz = strlen(m); + p = sz > 1 ? path + sz : path; + + if (m != mnt) + free(m); + + res = *p ? strdup(p) : strdup("/"); + DBG(UTILS, mnt_debug("%s fs-root is %s", path, res)); + return res; +} + +#ifdef TEST_PROGRAM +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +int test_fsroot(struct libmnt_test *ts, int argc, char *argv[]) +{ + char *path = canonicalize_path(argv[1]), + *mnt = path ? mnt_get_fs_root(path, NULL) : NULL; + + printf("%s: %s\n", argv[1], mnt ? : "unknown"); + free(mnt); + free(path); + return 0; +} + +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; +} + +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; +} + + +int main(int argc, char *argv[]) +{ + struct libmnt_test tss[] = { + { "--match-fstype", test_match_fstype, "<type> <pattern> FS types matching" }, + { "--match-options", test_match_options, "<options> <pattern> options matching" }, + { "--filesystems", test_filesystems, "[<pattern>] list /{etc,proc}/filesystems" }, + { "--starts-with", test_startswith, "<string> <prefix>" }, + { "--ends-with", test_endswith, "<string> <prefix>" }, + { "--mountpoint", test_mountpoint, "<path>" }, + { "--fs-root", test_fsroot, "<path>" }, + { "--cd-parent", test_chdir, "<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..00e4f99 --- /dev/null +++ b/libmount/src/version.c @@ -0,0 +1,127 @@ +/* + * version.c - Return the version of the blkid library + * + * Copyright (C) 2008 Karel Zak <kzak@redhat.com> + * [Based on libblkid/version.c by Theodore Ts'o] + * + * See COPYING.libmount for the License of this software. + */ + +/** + * SECTION: version + * @title: Version functions + * @short_description: functions to get 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 CONFIG_LIBMOUNT_DEBUG + "debug", +#endif + 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; + + 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 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 then 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 +int test_version(struct libmnt_test *ts, int argc, char *argv[]) +{ + const char *ver; + const char **features; + + 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++); + + 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 |