| /* |
| * umount(8) -- mount a filesystem |
| * |
| * Copyright (C) 2011 Red Hat, Inc. All rights reserved. |
| * Written by Karel Zak <kzak@redhat.com> |
| * |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <getopt.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| |
| #include <libmount.h> |
| |
| #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 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__)) 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] <source> | <directory>\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.<type> 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 <list> 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 <list> limit the set of filesystem types\n"), out); |
| fputs(_(" -v, --verbose say what is being done\n"), out); |
| fputs(_(" -N, --namespace <ns> 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 __attribute__((__noreturn__)) exit_non_root(const char *option) |
| { |
| const uid_t ruid = getuid(); |
| const uid_t euid = geteuid(); |
| |
| if (ruid == 0 && euid != 0) { |
| /* user is root, but setuid to non-root */ |
| if (option) |
| errx(MNT_EX_USAGE, |
| _("only root can use \"--%s\" option " |
| "(effective UID is %u)"), |
| option, euid); |
| errx(MNT_EX_USAGE, _("only root can do that " |
| "(effective UID is %u)"), euid); |
| } |
| if (option) |
| errx(MNT_EX_USAGE, _("only root can use \"--%s\" option"), option); |
| errx(MNT_EX_USAGE, _("only root can do that")); |
| } |
| |
| 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)); |
| 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); |
| 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; |
| 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 <spec>". |
| */ |
| rc = mnt_context_find_umount_fs(cxt, spec, &fs); |
| if (rc == 1) { |
| rc = MNT_EX_USAGE; |
| 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' }, |
| { "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); |
| atexit(close_stdout); |
| |
| 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, "aAcdfhilnRrO:t:vVN:", |
| longopts, NULL)) != -1) { |
| |
| |
| /* only few options are allowed for non-root users */ |
| if (mnt_context_is_restricted(cxt) && !strchr("hdilVv", c)) |
| exit_non_root(option_to_longopt(c, longopts)); |
| |
| 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 'h': |
| usage(); |
| 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 '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 'V': |
| print_version(); |
| 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; |
| } |
| default: |
| errtryhelp(MNT_EX_USAGE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| |
| if (all) { |
| 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; |
| } |
| |