diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 13:14:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 13:14:44 +0000 |
commit | 30ff6afe596eddafacf22b1a5b2d1a3d6254ea15 (patch) | |
tree | 9b788335f92174baf7ee18f03ca8330b8c19ce2b /libmount/src/context_umount.c | |
parent | Initial commit. (diff) | |
download | util-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.c | 1333 |
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; +} |