diff options
Diffstat (limited to 'libmount/src/context_umount.c')
-rw-r--r-- | libmount/src/context_umount.c | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c new file mode 100644 index 000000000..e10af52f2 --- /dev/null +++ b/libmount/src/context_umount.c @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <sys/wait.h> +#include <sys/mount.h> + +#include "c.h" +#include "pathnames.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; + 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; + } + fs = mnt_table_find_target(mtab, tgt, MNT_ITER_BACKWARD); + if (!fs) { + /* 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) { + DBG(CXT, mnt_debug_h(cxt, "umount: cannot found %s in mtab", tgt)); + return 0; + } + + /* 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 + * + * TODO : move this to loopdev.c + */ +static int mnt_loopdev_associated_fs(const char *devname, struct libmnt_fs *fs) +{ + uintmax_t offset = 0; + const char *src; + char *val, *optstr; + size_t valsz; + + /* 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)) { + int rc; + + val = strndup(val, valsz); + if (!val) + return 0; + rc = strtosize(val, &offset); + free(val); + if (rc) + return 0; + } + + /* TODO: + * if (mnt_loopdev_associated_file(devname, src, offset)) + * return 1; + */ + return 0; +} + +static int prepare_helper_from_options(struct libmnt_context *cxt, + const char *name) +{ + char *suffix = NULL; + const char *opts; + size_t valsz; + + if (cxt->flags & MNT_FL_NOHELPERS) + return 0; + + opts = mnt_fs_get_user_options(cxt->fs); + if (!opts) + return 0; + + if (mnt_optstr_get_option((char *) 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 found %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 || !mnt_loopdev_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) || (u_flags & MNT_MS_OWNER) || + (u_flags & 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((char *) 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 (cxt->flags & MNT_FL_NOMTAB) + args[i++] = "-n"; /* 3 */ + if (cxt->flags & MNT_FL_LAZY) + args[i++] = "-l"; /* 4 */ + if (cxt->flags & MNT_FL_FORCE) + args[i++] = "-f"; /* 5 */ + if (cxt->flags & MNT_FL_VERBOSE) + args[i++] = "-v"; /* 6 */ + if (cxt->flags & MNT_FL_RDONLY_UMOUNT) + 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 + i = 0; + 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_fake(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; + + if (cxt->flags & MNT_FL_FAKE) + return 0; + + DBG(CXT, mnt_debug_h(cxt, "do umount")); + + if (cxt->restricted) { + /* + * 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 (cxt->flags & MNT_FL_LAZY) + flags |= MNT_DETACH; + + else if (cxt->flags & MNT_FL_FORCE) + flags |= MNT_FORCE; + + DBG(CXT, mnt_debug_h(cxt, "umount(2) [target='%s', flags=0x%08x]", + target, flags)); + + 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 && + (cxt->flags & MNT_FL_RDONLY_UMOUNT) && src) { + + cxt->mountflags |= MS_REMOUNT | MS_RDONLY; + cxt->flags &= ~MNT_FL_LOOPDEL; + DBG(CXT, mnt_debug_h(cxt, + "umount(2) failed [errno=%d] -- tring 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 || (cxt->fs->flags & MNT_FS_SWAP)) + 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; + + 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) + rc = mnt_context_prepare_target(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); + } + +/* TODO + if ((cxt->flags & MNT_FL_LOOPDEL) && + (!mnt_is_loopdev(src) || mnt_loopdev_is_autoclear(src))) + cxt->flags &= ~MNT_FL_LOOPDEL; +*/ + 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 sucessfully 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; +/* TODO + if (cxt->flags & MNT_FL_LOOPDEL) + rc = mnt_loopdev_clean(mnt_fs_get_source(cxt->fs)); +*/ + if (cxt->flags & MNT_FL_NOMTAB) + return rc; + + if ((cxt->flags & MNT_FL_RDONLY_UMOUNT) && + (cxt->mountflags & (MS_RDONLY | MS_REMOUNT))) { + /* + * fix options, remount --> read-only mount + */ + const char *o = mnt_fs_get_options(cxt->fs); + char *n = o ? strdup(o) : NULL; + + DBG(CXT, mnt_debug_h(cxt, "fix remount-on-umount update")); + + if (n) + mnt_optstr_remove_option(&n, "rw"); + rc = mnt_optstr_prepend_option(&n, "ro", NULL); + if (!rc) + rc = mnt_fs_set_options(cxt->fs, n); + + /* 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 sucessfully 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); + + 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; +} |