summaryrefslogtreecommitdiffstats
path: root/libmount/src/context_umount.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 13:14:44 +0000
commit30ff6afe596eddafacf22b1a5b2d1a3d6254ea15 (patch)
tree9b788335f92174baf7ee18f03ca8330b8c19ce2b /libmount/src/context_umount.c
parentInitial commit. (diff)
downloadutil-linux-30ff6afe596eddafacf22b1a5b2d1a3d6254ea15.tar.xz
util-linux-30ff6afe596eddafacf22b1a5b2d1a3d6254ea15.zip
Adding upstream version 2.36.1.upstream/2.36.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libmount/src/context_umount.c')
-rw-r--r--libmount/src/context_umount.c1333
1 files changed, 1333 insertions, 0 deletions
diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c
new file mode 100644
index 0000000..df9a2eb
--- /dev/null
+++ b/libmount/src/context_umount.c
@@ -0,0 +1,1333 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2010-2018 Karel Zak <kzak@redhat.com>
+ *
+ * libmount is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+
+/**
+ * SECTION: context-umount
+ * @title: Umount context
+ * @short_description: high-level API to umount operation.
+ */
+
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#include "pathnames.h"
+#include "loopdev.h"
+#include "strutils.h"
+#include "mountP.h"
+
+/*
+ * umount2 flags
+ */
+#ifndef MNT_FORCE
+# define MNT_FORCE 0x00000001 /* Attempt to forcibly umount */
+#endif
+
+#ifndef MNT_DETACH
+# define MNT_DETACH 0x00000002 /* Just detach from the tree */
+#endif
+
+#ifndef UMOUNT_NOFOLLOW
+# define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+#endif
+
+#ifndef UMOUNT_UNUSED
+# define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+#endif
+
+/* search in mountinfo/mtab */
+static int __mtab_find_umount_fs(struct libmnt_context *cxt,
+ const char *tgt,
+ struct libmnt_fs **pfs)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+ struct libmnt_table *mtab = NULL;
+ struct libmnt_fs *fs;
+ char *loopdev = NULL;
+
+ assert(cxt);
+ assert(tgt);
+ assert(pfs);
+
+ *pfs = NULL;
+ DBG(CXT, ul_debugobj(cxt, " search %s in mountinfo", tgt));
+
+ /*
+ * The mount table may be huge, and on systems with utab we have to
+ * merge userspace mount options into /proc/self/mountinfo. This all is
+ * expensive. The tab filter can be used to filter out entries, then a mount
+ * table and utab are very tiny files.
+ *
+ * The filter uses mnt_fs_streq_{target,srcpath} function where all
+ * paths should be absolute and canonicalized. This is done within
+ * mnt_context_get_mtab_for_target() where LABEL, UUID or symlinks are
+ * canonicalized. If --no-canonicalize is enabled than the target path
+ * is expected already canonical.
+ *
+ * Anyway it's better to read huge mount table than canonicalize target
+ * paths. It means we use the filter only if --no-canonicalize enabled.
+ *
+ * It also means that we have to read mount table from kernel
+ * (non-writable mtab).
+ */
+ if (mnt_context_is_nocanonicalize(cxt) &&
+ !mnt_context_mtab_writable(cxt) && *tgt == '/')
+ rc = mnt_context_get_mtab_for_target(cxt, &mtab, tgt);
+ else
+ rc = mnt_context_get_mtab(cxt, &mtab);
+
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "umount: failed to read mtab"));
+ return rc;
+ }
+
+ if (mnt_table_get_nents(mtab) == 0) {
+ DBG(CXT, ul_debugobj(cxt, "umount: mtab empty"));
+ return 1;
+ }
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+try_loopdev:
+ fs = mnt_table_find_target(mtab, tgt, MNT_ITER_BACKWARD);
+ if (!fs && mnt_context_is_swapmatch(cxt)) {
+ /*
+ * Maybe the option is source rather than target (sometimes
+ * people use e.g. "umount /dev/sda1")
+ */
+ fs = mnt_table_find_source(mtab, tgt, MNT_ITER_BACKWARD);
+
+ if (fs) {
+ struct libmnt_fs *fs1 = mnt_table_find_target(mtab,
+ mnt_fs_get_target(fs),
+ MNT_ITER_BACKWARD);
+ if (!fs1) {
+ DBG(CXT, ul_debugobj(cxt, "mtab is broken?!?!"));
+ rc = -EINVAL;
+ goto err;
+ }
+ if (fs != fs1) {
+ /* Something was stacked over `file' on the
+ * same mount point. */
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: %s: %s is mounted "
+ "over it on the same point",
+ tgt, mnt_fs_get_source(fs1)));
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+ }
+
+ if (!fs && !loopdev && mnt_context_is_swapmatch(cxt)) {
+ /*
+ * Maybe the option is /path/file.img, try to convert to /dev/loopN
+ */
+ struct stat st;
+
+ if (mnt_stat_mountpoint(tgt, &st) == 0 && S_ISREG(st.st_mode)) {
+ int count;
+ struct libmnt_cache *cache = mnt_context_get_cache(cxt);
+ const char *bf = cache ? mnt_resolve_path(tgt, cache) : tgt;
+
+ count = loopdev_count_by_backing_file(bf, &loopdev);
+ if (count == 1) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: %s --> %s (retry)", tgt, loopdev));
+ tgt = loopdev;
+ goto try_loopdev;
+
+ } else if (count > 1)
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: warning: %s is associated "
+ "with more than one loopdev", tgt));
+ }
+ }
+
+ *pfs = fs;
+ free(loopdev);
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ DBG(CXT, ul_debugobj(cxt, "umount fs: %s", fs ? mnt_fs_get_target(fs) :
+ "<not found>"));
+ return fs ? 0 : 1;
+err:
+ free(loopdev);
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+ return rc;
+}
+
+/**
+ * mnt_context_find_umount_fs:
+ * @cxt: mount context
+ * @tgt: mountpoint, device, ...
+ * @pfs: returns point to filesystem
+ *
+ * Returns: 0 on success, <0 on error, 1 if target filesystem not found
+ */
+int mnt_context_find_umount_fs(struct libmnt_context *cxt,
+ const char *tgt,
+ struct libmnt_fs **pfs)
+{
+ if (pfs)
+ *pfs = NULL;
+
+ if (!cxt || !tgt || !pfs)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "umount: lookup FS for '%s'", tgt));
+
+ if (!*tgt)
+ return 1; /* empty string is not an error */
+
+ /* In future this function should be extended to support for example
+ * fsinfo() (or another cheap way kernel will support), for now the
+ * default is expensive mountinfo/mtab.
+ */
+ return __mtab_find_umount_fs(cxt, tgt, pfs);
+}
+
+/* Check if there is something important in the utab file. The parsed utab is
+ * stored in context->utab and deallocated by mnt_free_context().
+ *
+ * This function exists to avoid (if possible) /proc/self/mountinfo usage, so
+ * don't use things like mnt_resolve_target(), mnt_context_get_mtab() etc here.
+ * See lookup_umount_fs() for more details.
+ */
+static int has_utab_entry(struct libmnt_context *cxt, const char *target)
+{
+ struct libmnt_cache *cache = NULL;
+ struct libmnt_fs *fs;
+ struct libmnt_iter itr;
+ char *cn = NULL;
+ int rc = 0;
+
+ assert(cxt);
+
+ if (!cxt->utab) {
+ const char *path = mnt_get_utab_path();
+
+ if (!path || is_file_empty(path))
+ return 0;
+ cxt->utab = mnt_new_table();
+ if (!cxt->utab)
+ return 0;
+ cxt->utab->fmt = MNT_FMT_UTAB;
+ if (mnt_table_parse_file(cxt->utab, path))
+ return 0;
+ }
+
+ /* paths in utab are canonicalized */
+ cache = mnt_context_get_cache(cxt);
+ cn = mnt_resolve_path(target, cache);
+ mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
+
+ while (mnt_table_next_fs(cxt->utab, &itr, &fs) == 0) {
+ if (mnt_fs_streq_target(fs, cn)) {
+ rc = 1;
+ break;
+ }
+ }
+
+ if (!cache)
+ free(cn);
+ return rc;
+}
+
+/* returns: 1 not found; <0 on error; 1 success */
+static int lookup_umount_fs_by_statfs(struct libmnt_context *cxt, const char *tgt)
+{
+ struct stat st;
+ const char *type;
+
+ DBG(CXT, ul_debugobj(cxt, " lookup by statfs"));
+
+ /*
+ * Let's try to avoid mountinfo usage at all to minimize performance
+ * degradation. Don't forget that kernel has to compose *whole*
+ * mountinfo about all mountpoints although we look for only one entry.
+ *
+ * All we need is fstype and to check if there is no userspace mount
+ * options for the target (e.g. helper=udisks to call /sbin/umount.udisks).
+ *
+ * So, let's use statfs() if possible (it's bad idea for --lazy/--force
+ * umounts as target is probably unreachable NFS, also for --detach-loop
+ * as this additionally needs to know the name of the loop device).
+ */
+ if (mnt_context_is_restricted(cxt)
+ || *tgt != '/'
+ || (cxt->flags & MNT_FL_HELPER)
+ || mnt_context_mtab_writable(cxt)
+ || mnt_context_is_force(cxt)
+ || mnt_context_is_lazy(cxt)
+ || mnt_context_is_nocanonicalize(cxt)
+ || mnt_context_is_loopdel(cxt)
+ || mnt_stat_mountpoint(tgt, &st) != 0 || !S_ISDIR(st.st_mode)
+ || has_utab_entry(cxt, tgt))
+ return 1; /* not found */
+
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ DBG(CXT, ul_debugobj(cxt, " umount: disabling mtab"));
+ mnt_context_disable_mtab(cxt, TRUE);
+
+ if (!type) {
+ struct statfs vfs;
+ int fd;
+
+ DBG(CXT, ul_debugobj(cxt, " trying fstatfs()"));
+
+ /* O_PATH avoids triggering automount points. */
+ fd = open(tgt, O_PATH);
+ if (fd >= 0) {
+ if (fstatfs(fd, &vfs) == 0)
+ type = mnt_statfs_get_fstype(&vfs);
+ close(fd);
+ }
+ if (type) {
+ int rc = mnt_fs_set_fstype(cxt->fs, type);
+ if (rc)
+ return rc;
+ }
+ }
+ if (type) {
+ DBG(CXT, ul_debugobj(cxt,
+ " mountinfo unnecessary [type=%s]", type));
+ return 0;
+ }
+
+ return 1; /* not found */
+}
+
+/* returns: 1 not found; <0 on error; 1 success */
+static int lookup_umount_fs_by_mountinfo(struct libmnt_context *cxt, const char *tgt)
+{
+ struct libmnt_fs *fs = NULL;
+ int rc;
+
+ DBG(CXT, ul_debugobj(cxt, " lookup by mountinfo"));
+
+ /* search */
+ rc = __mtab_find_umount_fs(cxt, tgt, &fs);
+ if (rc != 0)
+ return rc;
+
+ /* apply result */
+ if (fs != cxt->fs) {
+ mnt_fs_set_source(cxt->fs, NULL);
+ mnt_fs_set_target(cxt->fs, NULL);
+
+ if (!mnt_copy_fs(cxt->fs, fs)) {
+ DBG(CXT, ul_debugobj(cxt, " failed to copy FS"));
+ return -errno;
+ }
+ DBG(CXT, ul_debugobj(cxt, " mtab applied"));
+ }
+
+ cxt->flags |= MNT_FL_TAB_APPLIED;
+ return 0;
+}
+
+/* This finction search for FS according to cxt->fs->target,
+ * apply result to cxt->fs and it's umount replacement to
+ * mnt_context_apply_fstab(), use mnt_context_tab_applied()
+ * to check result.
+ *
+ * The goal is to minimize situations when we need to parse
+ * /proc/self/mountinfo.
+ */
+static int lookup_umount_fs(struct libmnt_context *cxt)
+{
+ const char *tgt;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->fs);
+
+ DBG(CXT, ul_debugobj(cxt, "umount: lookup FS"));
+
+ tgt = mnt_fs_get_target(cxt->fs);
+ if (!tgt) {
+ DBG(CXT, ul_debugobj(cxt, " undefined target"));
+ return -EINVAL;
+ }
+
+ /* try get fs type by statfs() */
+ rc = lookup_umount_fs_by_statfs(cxt, tgt);
+ if (rc <= 0)
+ return rc;
+
+ /* get complete fs from fs entry from mountinfo */
+ rc = lookup_umount_fs_by_mountinfo(cxt, tgt);
+ if (rc <= 0)
+ return rc;
+
+ DBG(CXT, ul_debugobj(cxt, " cannot find '%s'", tgt));
+ return 0; /* this is correct! */
+}
+
+/* check if @devname is loopdev and if the device is associated
+ * with a source from @fstab_fs
+ */
+static int is_associated_fs(const char *devname, struct libmnt_fs *fs)
+{
+ uintmax_t offset = 0;
+ const char *src, *optstr;
+ char *val;
+ size_t valsz;
+ int flags = 0;
+
+ /* check if it begins with /dev/loop */
+ if (strncmp(devname, _PATH_DEV_LOOP, sizeof(_PATH_DEV_LOOP) - 1) != 0)
+ return 0;
+
+ src = mnt_fs_get_srcpath(fs);
+ if (!src)
+ return 0;
+
+ /* check for the offset option in @fs */
+ optstr = mnt_fs_get_user_options(fs);
+
+ if (optstr &&
+ mnt_optstr_get_option(optstr, "offset", &val, &valsz) == 0) {
+ flags |= LOOPDEV_FL_OFFSET;
+
+ if (mnt_parse_offset(val, valsz, &offset) != 0)
+ return 0;
+ }
+
+ return loopdev_is_used(devname, src, offset, 0, flags);
+}
+
+static int prepare_helper_from_options(struct libmnt_context *cxt,
+ const char *name)
+{
+ char *suffix = NULL;
+ const char *opts;
+ size_t valsz;
+ int rc;
+
+ if (mnt_context_is_nohelpers(cxt))
+ return 0;
+
+ opts = mnt_fs_get_user_options(cxt->fs);
+ if (!opts)
+ return 0;
+
+ if (mnt_optstr_get_option(opts, name, &suffix, &valsz))
+ return 0;
+
+ suffix = strndup(suffix, valsz);
+ if (!suffix)
+ return -ENOMEM;
+
+ DBG(CXT, ul_debugobj(cxt, "umount: umount.%s %s requested", suffix, name));
+
+ rc = mnt_context_prepare_helper(cxt, "umount", suffix);
+ free(suffix);
+
+ return rc;
+}
+
+static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv)
+{
+ struct libmnt_ns *ns_old;
+ const char *type = mnt_fs_get_fstype(cxt->fs);
+ const char *optstr;
+ char *user_id = NULL;
+ size_t sz;
+ uid_t uid;
+ char uidstr[sizeof(stringify_value(ULONG_MAX))];
+
+ *errsv = 0;
+
+ if (!type)
+ return 0;
+
+ if (strcmp(type, "fuse") != 0 &&
+ strcmp(type, "fuseblk") != 0 &&
+ strncmp(type, "fuse.", 5) != 0 &&
+ strncmp(type, "fuseblk.", 8) != 0)
+ return 0;
+
+ /* get user_id= from mount table */
+ optstr = mnt_fs_get_fs_options(cxt->fs);
+ if (!optstr)
+ return 0;
+
+ if (mnt_optstr_get_option(optstr, "user_id", &user_id, &sz) != 0)
+ return 0;
+
+ if (sz == 0 || user_id == NULL)
+ return 0;
+
+ /* get current user */
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old) {
+ *errsv = -MNT_ERR_NAMESPACE;
+ return 0;
+ }
+
+ uid = getuid();
+
+ if (!mnt_context_switch_ns(cxt, ns_old)) {
+ *errsv = -MNT_ERR_NAMESPACE;
+ return 0;
+ }
+
+ snprintf(uidstr, sizeof(uidstr), "%lu", (unsigned long) uid);
+ return strncmp(user_id, uidstr, sz) == 0;
+}
+
+/*
+ * Note that cxt->fs contains relevant mtab entry!
+ */
+static int evaluate_permissions(struct libmnt_context *cxt)
+{
+ struct libmnt_table *fstab;
+ unsigned long u_flags = 0;
+ const char *tgt, *src, *optstr;
+ int rc = 0, ok = 0;
+ struct libmnt_fs *fs;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ if (!mnt_context_is_restricted(cxt))
+ return 0; /* superuser mount */
+
+ DBG(CXT, ul_debugobj(cxt, "umount: evaluating permissions"));
+
+ if (!mnt_context_tab_applied(cxt)) {
+ DBG(CXT, ul_debugobj(cxt,
+ "cannot find %s in mtab and you are not root",
+ mnt_fs_get_target(cxt->fs)));
+ goto eperm;
+ }
+
+ if (cxt->user_mountflags & MNT_MS_UHELPER) {
+ /* on uhelper= mount option based helper */
+ rc = prepare_helper_from_options(cxt, "uhelper");
+ if (rc)
+ return rc;
+ if (cxt->helper)
+ return 0; /* we'll call /sbin/umount.<uhelper> */
+ }
+
+ /*
+ * Check if this is a fuse mount for the current user,
+ * if so then unmounting is allowed
+ */
+ if (is_fuse_usermount(cxt, &rc)) {
+ DBG(CXT, ul_debugobj(cxt, "fuse user mount, umount is allowed"));
+ return 0;
+ }
+ if (rc)
+ return rc;
+
+ /*
+ * User mounts have to be in /etc/fstab
+ */
+ rc = mnt_context_get_fstab(cxt, &fstab);
+ if (rc)
+ return rc;
+
+ tgt = mnt_fs_get_target(cxt->fs);
+ src = mnt_fs_get_source(cxt->fs);
+
+ if (mnt_fs_get_bindsrc(cxt->fs)) {
+ src = mnt_fs_get_bindsrc(cxt->fs);
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: using bind source: %s", src));
+ }
+
+ /* If fstab contains the two lines
+ * /dev/sda1 /mnt/zip auto user,noauto 0 0
+ * /dev/sda4 /mnt/zip auto user,noauto 0 0
+ * then "mount /dev/sda4" followed by "umount /mnt/zip" used to fail.
+ * So, we must not look for the file, but for the pair (dev,file) in fstab.
+ */
+ fs = mnt_table_find_pair(fstab, src, tgt, MNT_ITER_FORWARD);
+ if (!fs) {
+ /*
+ * It's possible that there is /path/file.img in fstab and
+ * /dev/loop0 in mtab -- then we have to check the relation
+ * between loopdev and the file.
+ */
+ fs = mnt_table_find_target(fstab, tgt, MNT_ITER_FORWARD);
+ if (fs) {
+ struct libmnt_cache *cache = mnt_context_get_cache(cxt);
+ const char *sp = mnt_fs_get_srcpath(cxt->fs); /* devname from mtab */
+ const char *dev = sp && cache ? mnt_resolve_path(sp, cache) : sp;
+
+ if (!dev || !is_associated_fs(dev, fs))
+ fs = NULL;
+ }
+ if (!fs) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount %s: mtab disagrees with fstab",
+ tgt));
+ goto eperm;
+ }
+ }
+
+ /*
+ * User mounting and unmounting is allowed only if fstab contains one
+ * of the options `user', `users' or `owner' or `group'.
+ *
+ * The option `users' allows arbitrary users to mount and unmount -
+ * this may be a security risk.
+ *
+ * The options `user', `owner' and `group' only allow unmounting by the
+ * user that mounted (visible in mtab).
+ */
+ optstr = mnt_fs_get_user_options(fs); /* FSTAB mount options! */
+ if (!optstr)
+ goto eperm;
+
+ if (mnt_optstr_get_flags(optstr, &u_flags,
+ mnt_get_builtin_optmap(MNT_USERSPACE_MAP)))
+ goto eperm;
+
+ if (u_flags & MNT_MS_USERS) {
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: promiscuous setting ('users') in fstab"));
+ return 0;
+ }
+ /*
+ * Check user=<username> setting from mtab if there is a user, owner or
+ * group option in /etc/fstab
+ */
+ if (u_flags & (MNT_MS_USER | MNT_MS_OWNER | MNT_MS_GROUP)) {
+
+ char *curr_user;
+ char *mtab_user = NULL;
+ size_t sz;
+ struct libmnt_ns *ns_old;
+
+ DBG(CXT, ul_debugobj(cxt,
+ "umount: checking user=<username> from mtab"));
+
+ ns_old = mnt_context_switch_origin_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ curr_user = mnt_get_username(getuid());
+
+ if (!mnt_context_switch_ns(cxt, ns_old)) {
+ free(curr_user);
+ return -MNT_ERR_NAMESPACE;
+ }
+ if (!curr_user) {
+ DBG(CXT, ul_debugobj(cxt, "umount %s: cannot "
+ "convert %d to username", tgt, getuid()));
+ goto eperm;
+ }
+
+ /* get options from mtab */
+ optstr = mnt_fs_get_user_options(cxt->fs);
+ if (optstr && !mnt_optstr_get_option(optstr,
+ "user", &mtab_user, &sz) && sz)
+ ok = !strncmp(curr_user, mtab_user, sz);
+
+ free(curr_user);
+ }
+
+ if (ok) {
+ DBG(CXT, ul_debugobj(cxt, "umount %s is allowed", tgt));
+ return 0;
+ }
+eperm:
+ DBG(CXT, ul_debugobj(cxt, "umount is not allowed for you"));
+ return -EPERM;
+}
+
+static int exec_helper(struct libmnt_context *cxt)
+{
+ char *namespace = NULL;
+ struct libmnt_ns *ns_tgt = mnt_context_get_target_ns(cxt);
+ int rc;
+ pid_t pid;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+ assert(cxt->helper_exec_status == 1);
+
+ if (mnt_context_is_fake(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "fake mode: does not execute helper"));
+ cxt->helper_exec_status = rc = 0;
+ return rc;
+ }
+
+ if (ns_tgt->fd != -1
+ && asprintf(&namespace, "/proc/%i/fd/%i",
+ getpid(), ns_tgt->fd) == -1) {
+ return -ENOMEM;
+ }
+
+ DBG_FLUSH;
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ {
+ const char *args[12], *type;
+ int i = 0;
+
+ if (setgid(getgid()) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (setuid(getuid()) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (!mnt_context_switch_origin_ns(cxt))
+ _exit(EXIT_FAILURE);
+
+ type = mnt_fs_get_fstype(cxt->fs);
+
+ args[i++] = cxt->helper; /* 1 */
+ args[i++] = mnt_fs_get_target(cxt->fs); /* 2 */
+
+ if (mnt_context_is_nomtab(cxt))
+ args[i++] = "-n"; /* 3 */
+ if (mnt_context_is_lazy(cxt))
+ args[i++] = "-l"; /* 4 */
+ if (mnt_context_is_force(cxt))
+ args[i++] = "-f"; /* 5 */
+ if (mnt_context_is_verbose(cxt))
+ args[i++] = "-v"; /* 6 */
+ if (mnt_context_is_rdonly_umount(cxt))
+ args[i++] = "-r"; /* 7 */
+ if (type
+ && strchr(type, '.')
+ && !endswith(cxt->helper, type)) {
+ args[i++] = "-t"; /* 8 */
+ args[i++] = type; /* 9 */
+ }
+ if (namespace) {
+ args[i++] = "-N"; /* 10 */
+ args[i++] = namespace; /* 11 */
+ }
+
+ args[i] = NULL; /* 12 */
+ for (i = 0; args[i]; i++)
+ DBG(CXT, ul_debugobj(cxt, "argv[%d] = \"%s\"",
+ i, args[i]));
+ DBG_FLUSH;
+ execv(cxt->helper, (char * const *) args);
+ _exit(EXIT_FAILURE);
+ }
+ default:
+ {
+ int st;
+
+ if (waitpid(pid, &st, 0) == (pid_t) -1) {
+ cxt->helper_status = -1;
+ rc = -errno;
+ } else {
+ cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1;
+ cxt->helper_exec_status = rc = 0;
+ }
+ DBG(CXT, ul_debugobj(cxt, "%s executed [status=%d, rc=%d%s]",
+ cxt->helper,
+ cxt->helper_status, rc,
+ rc ? " waitpid failed" : ""));
+ break;
+ }
+
+ case -1:
+ cxt->helper_exec_status = rc = -errno;
+ DBG(CXT, ul_debugobj(cxt, "fork() failed"));
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * mnt_context_helper_setopt() backend.
+ *
+ * This function applies umount.type command line option (for example parsed
+ * by getopt() or getopt_long()) to @cxt. All unknown options are ignored and
+ * then 1 is returned.
+ *
+ * Returns: negative number on error, 1 if @c is unknown option, 0 on success.
+ */
+int mnt_context_umount_setopt(struct libmnt_context *cxt, int c, char *arg)
+{
+ int rc = -EINVAL;
+
+ assert(cxt);
+ assert(cxt->action == MNT_ACT_UMOUNT);
+
+ switch(c) {
+ case 'n':
+ rc = mnt_context_disable_mtab(cxt, TRUE);
+ break;
+ case 'l':
+ rc = mnt_context_enable_lazy(cxt, TRUE);
+ break;
+ case 'f':
+ rc = mnt_context_enable_force(cxt, TRUE);
+ break;
+ case 'v':
+ rc = mnt_context_enable_verbose(cxt, TRUE);
+ break;
+ case 'r':
+ rc = mnt_context_enable_rdonly_umount(cxt, TRUE);
+ break;
+ case 't':
+ if (arg)
+ rc = mnt_context_set_fstype(cxt, arg);
+ break;
+ case 'N':
+ if (arg)
+ rc = mnt_context_set_target_ns(cxt, arg);
+ break;
+ default:
+ return 1;
+ }
+
+ return rc;
+}
+
+/* Check whether the kernel supports the UMOUNT_NOFOLLOW flag */
+static int umount_nofollow_support(void)
+{
+ int res = umount2("", UMOUNT_UNUSED);
+ if (res != -1 || errno != EINVAL)
+ return 0;
+
+ res = umount2("", UMOUNT_NOFOLLOW);
+ if (res != -1 || errno != ENOENT)
+ return 0;
+
+ return 1;
+}
+
+static int do_umount(struct libmnt_context *cxt)
+{
+ int rc = 0, flags = 0;
+ const char *src, *target;
+ char *tgtbuf = NULL;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+ assert(cxt->syscall_status == 1);
+
+ if (cxt->helper)
+ return exec_helper(cxt);
+
+ src = mnt_fs_get_srcpath(cxt->fs);
+ target = mnt_fs_get_target(cxt->fs);
+
+ if (!target)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "do umount"));
+
+ if (mnt_context_is_restricted(cxt) && !mnt_context_is_fake(cxt)) {
+ /*
+ * extra paranoia for non-root users
+ * -- chdir to the parent of the mountpoint and use NOFOLLOW
+ * flag to avoid races and symlink attacks.
+ */
+ if (umount_nofollow_support())
+ flags |= UMOUNT_NOFOLLOW;
+
+ rc = mnt_chdir_to_parent(target, &tgtbuf);
+ if (rc)
+ return rc;
+ target = tgtbuf;
+ }
+
+ if (mnt_context_is_lazy(cxt))
+ flags |= MNT_DETACH;
+
+ if (mnt_context_is_force(cxt))
+ flags |= MNT_FORCE;
+
+ DBG(CXT, ul_debugobj(cxt, "umount(2) [target='%s', flags=0x%08x]%s",
+ target, flags,
+ mnt_context_is_fake(cxt) ? " (FAKE)" : ""));
+
+ if (mnt_context_is_fake(cxt))
+ rc = 0;
+ else {
+ rc = flags ? umount2(target, flags) : umount(target);
+ if (rc < 0)
+ cxt->syscall_status = -errno;
+ free(tgtbuf);
+ }
+
+ /*
+ * try remount read-only
+ */
+ if (rc < 0
+ && cxt->syscall_status == -EBUSY
+ && mnt_context_is_rdonly_umount(cxt)
+ && src) {
+
+ mnt_context_set_mflags(cxt, (cxt->mountflags |
+ MS_REMOUNT | MS_RDONLY));
+ mnt_context_enable_loopdel(cxt, FALSE);
+
+ DBG(CXT, ul_debugobj(cxt,
+ "umount(2) failed [errno=%d] -- trying to remount read-only",
+ -cxt->syscall_status));
+
+ rc = mount(src, mnt_fs_get_target(cxt->fs), NULL,
+ MS_REMOUNT | MS_RDONLY, NULL);
+ if (rc < 0) {
+ cxt->syscall_status = -errno;
+ DBG(CXT, ul_debugobj(cxt,
+ "read-only re-mount(2) failed [errno=%d]",
+ -cxt->syscall_status));
+
+ return -cxt->syscall_status;
+ }
+ cxt->syscall_status = 0;
+ DBG(CXT, ul_debugobj(cxt, "read-only re-mount(2) success"));
+ return 0;
+ }
+
+ if (rc < 0) {
+ DBG(CXT, ul_debugobj(cxt, "umount(2) failed [errno=%d]",
+ -cxt->syscall_status));
+ return -cxt->syscall_status;
+ }
+
+ cxt->syscall_status = 0;
+ DBG(CXT, ul_debugobj(cxt, "umount(2) success"));
+ return 0;
+}
+
+/**
+ * mnt_context_prepare_umount:
+ * @cxt: mount context
+ *
+ * Prepare context for umounting, unnecessary for mnt_context_umount().
+ *
+ * Returns: 0 on success, and negative number in case of error.
+ */
+int mnt_context_prepare_umount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ if (!cxt || !cxt->fs || mnt_fs_is_swaparea(cxt->fs))
+ return -EINVAL;
+ if (!mnt_context_get_source(cxt) && !mnt_context_get_target(cxt))
+ return -EINVAL;
+ if (cxt->flags & MNT_FL_PREPARED)
+ return 0;
+
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+
+ free(cxt->helper); /* be paranoid */
+ cxt->helper = NULL;
+ cxt->action = MNT_ACT_UMOUNT;
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = lookup_umount_fs(cxt);
+ if (!rc)
+ rc = mnt_context_merge_mflags(cxt);
+ if (!rc)
+ rc = evaluate_permissions(cxt);
+
+ if (!rc && !cxt->helper) {
+
+ if (cxt->user_mountflags & MNT_MS_HELPER)
+ /* on helper= mount option based helper */
+ rc = prepare_helper_from_options(cxt, "helper");
+
+ if (!rc && !cxt->helper)
+ /* on fstype based helper */
+ rc = mnt_context_prepare_helper(cxt, "umount", NULL);
+ }
+
+ if (!rc && (cxt->user_mountflags & MNT_MS_LOOP))
+ /* loop option explicitly specified in mtab, detach this loop */
+ mnt_context_enable_loopdel(cxt, TRUE);
+
+ if (!rc && mnt_context_is_loopdel(cxt) && cxt->fs) {
+ const char *src = mnt_fs_get_srcpath(cxt->fs);
+
+ if (src && (!is_loopdev(src) || loopdev_is_autoclear(src)))
+ mnt_context_enable_loopdel(cxt, FALSE);
+ }
+
+ if (rc) {
+ DBG(CXT, ul_debugobj(cxt, "umount: preparing failed"));
+ return rc;
+ }
+ cxt->flags |= MNT_FL_PREPARED;
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/**
+ * mnt_context_do_umount:
+ * @cxt: mount context
+ *
+ * Umount filesystem by umount(2) or fork()+exec(/sbin/umount.type).
+ * Unnecessary for mnt_context_umount().
+ *
+ * See also mnt_context_disable_helpers().
+ *
+ * WARNING: non-zero return code does not mean that umount(2) syscall or
+ * umount.type helper wasn't successfully called.
+ *
+ * Check mnt_context_get_status() after error!
+*
+ * Returns: 0 on success;
+ * >0 in case of umount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+int mnt_context_do_umount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+ assert((cxt->flags & MNT_FL_PREPARED));
+ assert((cxt->action == MNT_ACT_UMOUNT));
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = do_umount(cxt);
+ if (rc)
+ goto end;
+
+ if (mnt_context_get_status(cxt) && !mnt_context_is_fake(cxt)) {
+ /*
+ * Umounted, do some post-umount operations
+ * - remove loopdev
+ * - refresh in-memory mtab stuff if remount rather than
+ * umount has been performed
+ */
+ if (mnt_context_is_loopdel(cxt)
+ && !(cxt->mountflags & MS_REMOUNT))
+ rc = mnt_context_delete_loopdev(cxt);
+
+ if (!mnt_context_is_nomtab(cxt)
+ && mnt_context_get_status(cxt)
+ && !cxt->helper
+ && mnt_context_is_rdonly_umount(cxt)
+ && (cxt->mountflags & MS_REMOUNT)) {
+
+ /* use "remount" instead of "umount" in /etc/mtab */
+ if (!rc && cxt->update && mnt_context_mtab_writable(cxt))
+ rc = mnt_update_set_fs(cxt->update,
+ cxt->mountflags, NULL, cxt->fs);
+ }
+ }
+end:
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+/**
+ * mnt_context_finalize_umount:
+ * @cxt: context
+ *
+ * Mtab update, etc. Unnecessary for mnt_context_umount(), but should be called
+ * after mnt_context_do_umount(). See also mnt_context_set_syscall_status().
+ *
+ * Returns: negative number on error, 0 on success.
+ */
+int mnt_context_finalize_umount(struct libmnt_context *cxt)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert((cxt->flags & MNT_FL_PREPARED));
+ assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
+
+ rc = mnt_context_prepare_update(cxt);
+ if (!rc)
+ rc = mnt_context_update_tabs(cxt);
+ return rc;
+}
+
+
+/**
+ * mnt_context_umount:
+ * @cxt: umount context
+ *
+ * High-level, umounts filesystem by umount(2) or fork()+exec(/sbin/umount.type).
+ *
+ * This is similar to:
+ *
+ * mnt_context_prepare_umount(cxt);
+ * mnt_context_do_umount(cxt);
+ * mnt_context_finalize_umount(cxt);
+ *
+ * See also mnt_context_disable_helpers().
+ *
+ * WARNING: non-zero return code does not mean that umount(2) syscall or
+ * umount.type helper wasn't successfully called.
+ *
+ * Check mnt_context_get_status() after error!
+ *
+ * Returns: 0 on success;
+ * >0 in case of umount(2) error (returns syscall errno),
+ * <0 in case of other errors.
+ */
+int mnt_context_umount(struct libmnt_context *cxt)
+{
+ int rc;
+ struct libmnt_ns *ns_old;
+
+ assert(cxt);
+ assert(cxt->fs);
+ assert(cxt->helper_exec_status == 1);
+ assert(cxt->syscall_status == 1);
+
+ DBG(CXT, ul_debugobj(cxt, "umount: %s", mnt_context_get_target(cxt)));
+
+ ns_old = mnt_context_switch_target_ns(cxt);
+ if (!ns_old)
+ return -MNT_ERR_NAMESPACE;
+
+ rc = mnt_context_prepare_umount(cxt);
+ if (!rc)
+ rc = mnt_context_prepare_update(cxt);
+ if (!rc)
+ rc = mnt_context_do_umount(cxt);
+ if (!rc)
+ rc = mnt_context_update_tabs(cxt);
+
+ if (!mnt_context_switch_ns(cxt, ns_old))
+ return -MNT_ERR_NAMESPACE;
+
+ return rc;
+}
+
+
+/**
+ * mnt_context_next_umount:
+ * @cxt: context
+ * @itr: iterator
+ * @fs: returns the current filesystem
+ * @mntrc: returns the return code from mnt_context_umount()
+ * @ignored: returns 1 for not matching
+ *
+ * This function tries to umount the next filesystem from mtab (as returned by
+ * mnt_context_get_mtab()).
+ *
+ * You can filter out filesystems by:
+ * mnt_context_set_options_pattern() to simulate umount -a -O pattern
+ * mnt_context_set_fstype_pattern() to simulate umount -a -t pattern
+ *
+ * If the filesystem is not mounted or does not match the defined criteria,
+ * then the function mnt_context_next_umount() returns zero, but the @ignored is
+ * non-zero. Note that the root filesystem is always ignored.
+ *
+ * If umount(2) syscall or umount.type helper failed, then the
+ * mnt_context_next_umount() function returns zero, but the @mntrc is non-zero.
+ * Use also mnt_context_get_status() to check if the filesystem was
+ * successfully umounted.
+ *
+ * Returns: 0 on success,
+ * <0 in case of error (!= umount(2) errors)
+ * 1 at the end of the list.
+ */
+int mnt_context_next_umount(struct libmnt_context *cxt,
+ struct libmnt_iter *itr,
+ struct libmnt_fs **fs,
+ int *mntrc,
+ int *ignored)
+{
+ struct libmnt_table *mtab;
+ const char *tgt;
+ int rc;
+
+ if (ignored)
+ *ignored = 0;
+ if (mntrc)
+ *mntrc = 0;
+
+ if (!cxt || !fs || !itr)
+ return -EINVAL;
+
+ rc = mnt_context_get_mtab(cxt, &mtab);
+ cxt->mtab = NULL; /* do not reset mtab */
+ mnt_reset_context(cxt);
+
+ if (rc)
+ return rc;
+
+ cxt->mtab = mtab;
+
+ do {
+ rc = mnt_table_next_fs(mtab, itr, fs);
+ if (rc != 0)
+ return rc; /* no more filesystems (or error) */
+
+ tgt = mnt_fs_get_target(*fs);
+ } while (!tgt);
+
+ DBG(CXT, ul_debugobj(cxt, "next-umount: trying %s [fstype: %s, t-pattern: %s, options: %s, O-pattern: %s]", tgt,
+ mnt_fs_get_fstype(*fs), cxt->fstype_pattern, mnt_fs_get_options(*fs), cxt->optstr_pattern));
+
+ /* ignore filesystems which don't match options patterns */
+ if ((cxt->fstype_pattern && !mnt_fs_match_fstype(*fs,
+ cxt->fstype_pattern)) ||
+
+ /* ignore filesystems which don't match type patterns */
+ (cxt->optstr_pattern && !mnt_fs_match_options(*fs,
+ cxt->optstr_pattern))) {
+ if (ignored)
+ *ignored = 1;
+
+ DBG(CXT, ul_debugobj(cxt, "next-umount: not-match"));
+ return 0;
+ }
+
+ rc = mnt_context_set_fs(cxt, *fs);
+ if (rc)
+ return rc;
+ rc = mnt_context_umount(cxt);
+ if (mntrc)
+ *mntrc = rc;
+ return 0;
+}
+
+
+int mnt_context_get_umount_excode(
+ struct libmnt_context *cxt,
+ int rc,
+ char *buf,
+ size_t bufsz)
+{
+ if (mnt_context_helper_executed(cxt))
+ /*
+ * /sbin/umount.<type> called, return status
+ */
+ return mnt_context_get_helper_status(cxt);
+
+ if (rc == 0 && mnt_context_get_status(cxt) == 1)
+ /*
+ * Libmount success && syscall success.
+ */
+ return MNT_EX_SUCCESS;
+
+ if (!mnt_context_syscall_called(cxt)) {
+ /*
+ * libmount errors (extra library checks)
+ */
+ if (rc == -EPERM && !mnt_context_tab_applied(cxt)) {
+ /* failed to evaluate permissions because not found
+ * relevant entry in mtab */
+ if (buf)
+ snprintf(buf, bufsz, _("not mounted"));
+ return MNT_EX_USAGE;
+ }
+
+ if (rc == -MNT_ERR_LOCK) {
+ if (buf)
+ snprintf(buf, bufsz, _("locking failed"));
+ return MNT_EX_FILEIO;
+ }
+
+ if (rc == -MNT_ERR_NAMESPACE) {
+ if (buf)
+ snprintf(buf, bufsz, _("failed to switch namespace"));
+ return MNT_EX_SYSERR;
+ }
+ return mnt_context_get_generic_excode(rc, buf, bufsz,
+ _("umount failed: %m"));
+
+ } if (mnt_context_get_syscall_errno(cxt) == 0) {
+ /*
+ * umount(2) syscall success, but something else failed
+ * (probably error in mtab processing).
+ */
+ if (rc == -MNT_ERR_LOCK) {
+ if (buf)
+ snprintf(buf, bufsz, _("filesystem was unmounted, but failed to update userspace mount table"));
+ return MNT_EX_FILEIO;
+ }
+
+ if (rc == -MNT_ERR_NAMESPACE) {
+ if (buf)
+ snprintf(buf, bufsz, _("filesystem was unmounted, but failed to switch namespace back"));
+ return MNT_EX_SYSERR;
+
+ }
+
+ if (rc < 0)
+ return mnt_context_get_generic_excode(rc, buf, bufsz,
+ _("filesystem was unmounted, but any subsequent operation failed: %m"));
+
+ return MNT_EX_SOFTWARE; /* internal error */
+ }
+
+ /*
+ * umount(2) errors
+ */
+ if (buf) {
+ int syserr = mnt_context_get_syscall_errno(cxt);
+
+ switch (syserr) {
+ case ENXIO:
+ snprintf(buf, bufsz, _("invalid block device")); /* ??? */
+ break;
+ case EINVAL:
+ snprintf(buf, bufsz, _("not mounted"));
+ break;
+ case EIO:
+ snprintf(buf, bufsz, _("can't write superblock"));
+ break;
+ case EBUSY:
+ snprintf(buf, bufsz, _("target is busy"));
+ break;
+ case ENOENT:
+ snprintf(buf, bufsz, _("no mount point specified"));
+ break;
+ case EPERM:
+ snprintf(buf, bufsz, _("must be superuser to unmount"));
+ break;
+ case EACCES:
+ snprintf(buf, bufsz, _("block devices are not permitted on filesystem"));
+ break;
+ default:
+ return mnt_context_get_generic_excode(syserr, buf, bufsz,_("umount(2) system call failed: %m"));
+ }
+ }
+ return MNT_EX_FAIL;
+}