/* * umount(8) -- mount a filesystem * * Copyright (C) 2011 Red Hat, Inc. All rights reserved. * Written by Karel Zak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it would be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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. */ #include #include #include #include #include #include #include #include #include "nls.h" #include "c.h" #include "env.h" #include "closestream.h" #include "pathnames.h" #include "canonicalize.h" #define XALLOC_EXIT_CODE MNT_EX_SYSERR #include "xalloc.h" #define OPTUTILS_EXIT_CODE MNT_EX_USAGE #include "optutils.h" static int quiet; static int table_parser_errcb(struct libmnt_table *tb __attribute__((__unused__)), const char *filename, int line) { if (filename) warnx(_("%s: parse error at line %d -- ignored"), filename, line); return 1; } static void __attribute__((__noreturn__)) umount_print_version(void) { const char *ver = NULL; const char **features = NULL, **p; mnt_get_library_version(&ver); mnt_get_library_features(&features); printf(_("%s from %s (libmount %s"), program_invocation_short_name, PACKAGE_STRING, ver); p = features; while (p && *p) { fputs(p == features ? ": " : ", ", stdout); fputs(*p++, stdout); } fputs(")\n", stdout); exit(MNT_EX_SUCCESS); } static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _( " %1$s [-hV]\n" " %1$s -a [options]\n" " %1$s [options] | \n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Unmount filesystems.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -a, --all unmount all filesystems\n"), out); fputs(_(" -A, --all-targets unmount all mountpoints for the given device in the\n" " current namespace\n"), out); fputs(_(" -c, --no-canonicalize don't canonicalize paths\n"), out); fputs(_(" -d, --detach-loop if mounted loop device, also free this loop device\n"), out); fputs(_(" --fake dry run; skip the umount(2) syscall\n"), out); fputs(_(" -f, --force force unmount (in case of an unreachable NFS system)\n"), out); fputs(_(" -i, --internal-only don't call the umount. helpers\n"), out); fputs(_(" -n, --no-mtab don't write to /etc/mtab\n"), out); fputs(_(" -l, --lazy detach the filesystem now, clean up things later\n"), out); fputs(_(" -O, --test-opts limit the set of filesystems (use with -a)\n"), out); fputs(_(" -R, --recursive recursively unmount a target with all its children\n"), out); fputs(_(" -r, --read-only in case unmounting fails, try to remount read-only\n"), out); fputs(_(" -t, --types limit the set of filesystem types\n"), out); fputs(_(" -v, --verbose say what is being done\n"), out); fputs(_(" -q, --quiet suppress 'not mounted' error messages\n"), out); fputs(_(" -N, --namespace perform umount in another namespace\n"), out); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(25)); printf(USAGE_MAN_TAIL("umount(8)")); exit(MNT_EX_SUCCESS); } static void suid_drop(struct libmnt_context *cxt) { const uid_t ruid = getuid(); const uid_t euid = geteuid(); if (ruid != 0 && euid == 0) { if (setgid(getgid()) < 0) err(MNT_EX_FAIL, _("setgid() failed")); if (setuid(getuid()) < 0) err(MNT_EX_FAIL, _("setuid() failed")); } /* be paranoid and check it, setuid(0) has to fail */ if (ruid != 0 && setuid(0) == 0) errx(MNT_EX_FAIL, _("drop permissions failed.")); mnt_context_force_unrestricted(cxt); } static void success_message(struct libmnt_context *cxt) { const char *tgt, *src; if (mnt_context_helper_executed(cxt) || mnt_context_get_status(cxt) != 1) return; tgt = mnt_context_get_target(cxt); if (!tgt) return; src = mnt_context_get_source(cxt); if (src) warnx(_("%s (%s) unmounted"), tgt, src); else warnx(_("%s unmounted"), tgt); } static int mk_exit_code(struct libmnt_context *cxt, int rc) { char buf[BUFSIZ] = { 0 }; rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf)); /* suppress "not mounted" error message */ if (quiet && rc == MNT_EX_FAIL && mnt_context_syscall_called(cxt) && mnt_context_get_syscall_errno(cxt) == EINVAL) return rc; /* print errors/warnings */ if (*buf) { const char *spec = mnt_context_get_target(cxt); if (!spec) spec = mnt_context_get_source(cxt); if (!spec) spec = "???"; warnx("%s: %s.", spec, buf); } return rc; } static int umount_all(struct libmnt_context *cxt) { struct libmnt_iter *itr; struct libmnt_fs *fs; int mntrc, ignored, rc = 0; itr = mnt_new_iter(MNT_ITER_BACKWARD); if (!itr) { warn(_("failed to initialize libmount iterator")); return MNT_EX_SYSERR; } while (mnt_context_next_umount(cxt, itr, &fs, &mntrc, &ignored) == 0) { const char *tgt = mnt_fs_get_target(fs); if (ignored) { if (mnt_context_is_verbose(cxt)) printf(_("%-25s: ignored\n"), tgt); } else { int xrc = mk_exit_code(cxt, mntrc); if (xrc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt)) printf("%-25s: successfully unmounted\n", tgt); rc |= xrc; } } mnt_free_iter(itr); return rc; } static int umount_one(struct libmnt_context *cxt, const char *spec) { int rc; if (!spec) return MNT_EX_SOFTWARE; if (mnt_context_set_target(cxt, spec)) err(MNT_EX_SYSERR, _("failed to set umount target")); rc = mnt_context_umount(cxt); if (rc == -EPERM && mnt_context_is_restricted(cxt) && mnt_context_tab_applied(cxt) && !mnt_context_syscall_called(cxt)) { /* Mountpoint exists, but failed something else in libmount, * drop perms and try it again */ suid_drop(cxt); rc = mnt_context_umount(cxt); } rc = mk_exit_code(cxt, rc); if (rc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt)) success_message(cxt); mnt_reset_context(cxt); return rc; } static struct libmnt_table *new_mountinfo(struct libmnt_context *cxt) { struct libmnt_table *tb; struct libmnt_ns *ns_old = mnt_context_switch_target_ns(cxt); if (!ns_old) err(MNT_EX_SYSERR, _("failed to switch namespace")); tb = mnt_new_table(); if (!tb) err(MNT_EX_SYSERR, _("libmount table allocation failed")); mnt_table_set_parser_errcb(tb, table_parser_errcb); mnt_table_set_cache(tb, mnt_context_get_cache(cxt)); if (mnt_table_parse_file(tb, _PATH_PROC_MOUNTINFO)) { warn(_("failed to parse %s"), _PATH_PROC_MOUNTINFO); mnt_unref_table(tb); tb = NULL; } if (!mnt_context_switch_ns(cxt, ns_old)) err(MNT_EX_SYSERR, _("failed to switch namespace")); return tb; } /* * like umount_one() but does not return error is @spec not mounted */ static int umount_one_if_mounted(struct libmnt_context *cxt, const char *spec) { int rc; struct libmnt_fs *fs; rc = mnt_context_find_umount_fs(cxt, spec, &fs); if (rc == 1) { rc = MNT_EX_SUCCESS; /* already unmounted */ mnt_reset_context(cxt); } else if (rc < 0) { rc = mk_exit_code(cxt, rc); /* error */ mnt_reset_context(cxt); } else rc = umount_one(cxt, mnt_fs_get_target(fs)); return rc; } static int umount_do_recurse(struct libmnt_context *cxt, struct libmnt_table *tb, struct libmnt_fs *fs) { struct libmnt_fs *child; struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD); int rc; if (!itr) err(MNT_EX_SYSERR, _("libmount iterator allocation failed")); /* umount all children */ for (;;) { rc = mnt_table_next_child_fs(tb, itr, fs, &child); if (rc < 0) { warnx(_("failed to get child fs of %s"), mnt_fs_get_target(fs)); rc = MNT_EX_SOFTWARE; goto done; } else if (rc == 1) break; /* no more children */ rc = umount_do_recurse(cxt, tb, child); if (rc != MNT_EX_SUCCESS) goto done; } rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs)); done: mnt_free_iter(itr); return rc; } static int umount_recursive(struct libmnt_context *cxt, const char *spec) { struct libmnt_table *tb; struct libmnt_fs *fs; int rc; tb = new_mountinfo(cxt); if (!tb) return MNT_EX_SOFTWARE; /* it's always real mountpoint, don't assume that the target maybe a device */ mnt_context_disable_swapmatch(cxt, 1); fs = mnt_table_find_target(tb, spec, MNT_ITER_BACKWARD); if (fs) rc = umount_do_recurse(cxt, tb, fs); else { rc = MNT_EX_USAGE; if (!quiet) warnx(access(spec, F_OK) == 0 ? _("%s: not mounted") : _("%s: not found"), spec); } mnt_unref_table(tb); return rc; } static int umount_alltargets(struct libmnt_context *cxt, const char *spec, int rec) { struct libmnt_fs *fs; struct libmnt_table *tb; struct libmnt_iter *itr = NULL; dev_t devno = 0; int rc; /* Convert @spec to device name, Use the same logic like regular * "umount ". */ rc = mnt_context_find_umount_fs(cxt, spec, &fs); if (rc == 1) { rc = MNT_EX_USAGE; if (!quiet) warnx(access(spec, F_OK) == 0 ? _("%s: not mounted") : _("%s: not found"), spec); return rc; } if (rc < 0) return mk_exit_code(cxt, rc); /* error */ if (!mnt_fs_get_srcpath(fs) || !mnt_fs_get_devno(fs)) errx(MNT_EX_USAGE, _("%s: failed to determine source " "(--all-targets is unsupported on systems with " "regular mtab file)."), spec); itr = mnt_new_iter(MNT_ITER_BACKWARD); if (!itr) err(MNT_EX_SYSERR, _("libmount iterator allocation failed")); /* get on @cxt independent mountinfo */ tb = new_mountinfo(cxt); if (!tb) { rc = MNT_EX_SOFTWARE; goto done; } /* Note that @fs is from mount context and the context will be reset * after each umount() call */ devno = mnt_fs_get_devno(fs); fs = NULL; mnt_reset_context(cxt); while (mnt_table_next_fs(tb, itr, &fs) == 0) { if (mnt_fs_get_devno(fs) != devno) continue; mnt_context_disable_swapmatch(cxt, 1); if (rec) rc = umount_do_recurse(cxt, tb, fs); else rc = umount_one_if_mounted(cxt, mnt_fs_get_target(fs)); if (rc != MNT_EX_SUCCESS) break; } done: mnt_free_iter(itr); mnt_unref_table(tb); return rc; } /* * Check path -- non-root user should not be able to resolve path which is * unreadable for him. */ static char *sanitize_path(const char *path) { char *p; if (!path) return NULL; p = canonicalize_path_restricted(path); if (!p) err(MNT_EX_USAGE, "%s", path); return p; } static pid_t parse_pid(const char *str) { char *end; pid_t ret; errno = 0; ret = strtoul(str, &end, 10); if (ret < 0 || errno || end == str || (end && *end)) return 0; return ret; } int main(int argc, char **argv) { int c, rc = 0, all = 0, recursive = 0, alltargets = 0; struct libmnt_context *cxt; char *types = NULL; enum { UMOUNT_OPT_FAKE = CHAR_MAX + 1, }; static const struct option longopts[] = { { "all", no_argument, NULL, 'a' }, { "all-targets", no_argument, NULL, 'A' }, { "detach-loop", no_argument, NULL, 'd' }, { "fake", no_argument, NULL, UMOUNT_OPT_FAKE }, { "force", no_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "internal-only", no_argument, NULL, 'i' }, { "lazy", no_argument, NULL, 'l' }, { "no-canonicalize", no_argument, NULL, 'c' }, { "no-mtab", no_argument, NULL, 'n' }, { "quiet", no_argument, NULL, 'q' }, { "read-only", no_argument, NULL, 'r' }, { "recursive", no_argument, NULL, 'R' }, { "test-opts", required_argument, NULL, 'O' }, { "types", required_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, { "namespace", required_argument, NULL, 'N' }, { NULL, 0, NULL, 0 } }; static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ { 'A','a' }, /* all-targets,all */ { 'R','a' }, /* recursive,all */ { 'O','R','t'}, /* options,recursive,types */ { 'R','r' }, /* recursive,read-only */ { 0 } }; int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; sanitize_env(); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); mnt_init_debug(0); cxt = mnt_new_context(); if (!cxt) err(MNT_EX_SYSERR, _("libmount context allocation failed")); mnt_context_set_tables_errcb(cxt, table_parser_errcb); while ((c = getopt_long(argc, argv, "aAcdfhilnqRrO:t:vVN:", longopts, NULL)) != -1) { /* only few options are allowed for non-root users */ if (mnt_context_is_restricted(cxt) && !strchr("hdilqVv", c)) suid_drop(cxt); err_exclusive_options(c, longopts, excl, excl_st); switch(c) { case 'a': all = 1; break; case 'A': alltargets = 1; break; case 'c': mnt_context_disable_canonicalize(cxt, TRUE); break; case 'd': mnt_context_enable_loopdel(cxt, TRUE); break; case UMOUNT_OPT_FAKE: mnt_context_enable_fake(cxt, TRUE); break; case 'f': mnt_context_enable_force(cxt, TRUE); break; case 'i': mnt_context_disable_helpers(cxt, TRUE); break; case 'l': mnt_context_enable_lazy(cxt, TRUE); break; case 'n': mnt_context_disable_mtab(cxt, TRUE); break; case 'q': quiet = 1; break; case 'r': mnt_context_enable_rdonly_umount(cxt, TRUE); break; case 'R': recursive = TRUE; break; case 'O': if (mnt_context_set_options_pattern(cxt, optarg)) err(MNT_EX_SYSERR, _("failed to set options pattern")); break; case 't': types = optarg; break; case 'v': mnt_context_enable_verbose(cxt, TRUE); break; case 'N': { char path[PATH_MAX]; pid_t pid = parse_pid(optarg); if (pid) snprintf(path, sizeof(path), "/proc/%i/ns/mnt", pid); if (mnt_context_set_target_ns(cxt, pid ? path : optarg)) err(MNT_EX_SYSERR, _("failed to set target namespace to %s"), pid ? path : optarg); break; } case 'h': mnt_free_context(cxt); usage(); case 'V': mnt_free_context(cxt); umount_print_version(); default: errtryhelp(MNT_EX_USAGE); } } argc -= optind; argv += optind; if (all) { if (argc) { warnx(_("unexpected number of arguments")); errtryhelp(MNT_EX_USAGE); } if (!types) types = "noproc,nodevfs,nodevpts,nosysfs,norpc_pipefs,nonfsd,noselinuxfs"; mnt_context_set_fstype_pattern(cxt, types); rc = umount_all(cxt); } else if (argc < 1) { warnx(_("bad usage")); errtryhelp(MNT_EX_USAGE); } else if (alltargets) { while (argc--) rc += umount_alltargets(cxt, *argv++, recursive); } else if (recursive) { while (argc--) rc += umount_recursive(cxt, *argv++); } else { while (argc--) { char *path = *argv; if (mnt_context_is_restricted(cxt) && !mnt_tag_is_valid(path)) path = sanitize_path(path); rc += umount_one(cxt, path); if (path != *argv) free(path); argv++; } } mnt_free_context(cxt); return (rc < 256) ? rc : 255; }