diff options
Diffstat (limited to 'mount-deprecated/umount.c')
-rw-r--r-- | mount-deprecated/umount.c | 867 |
1 files changed, 867 insertions, 0 deletions
diff --git a/mount-deprecated/umount.c b/mount-deprecated/umount.c new file mode 100644 index 0000000..3820c67 --- /dev/null +++ b/mount-deprecated/umount.c @@ -0,0 +1,867 @@ +/* + * umount(8) for Linux 0.99 - jrs, 1993 + */ + +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/mount.h> + +#include "mount_constants.h" +#include "sundries.h" +#include "getusername.h" +#include "pathnames.h" +#include "loopdev.h" +#include "fstab.h" +#include "env.h" +#include "nls.h" +#include "strutils.h" +#include "closestream.h" + +#if defined(MNT_FORCE) +/* Interesting ... it seems libc knows about MNT_FORCE and presumably + about umount2 as well -- need not do anything */ +#else /* MNT_FORCE */ + +/* Does the present kernel source know about umount2? */ +#include <linux/unistd.h> +#ifdef __NR_umount2 + +static int umount2(const char *path, int flags); + +_syscall2(int, umount2, const char *, path, int, flags); + +#else /* __NR_umount2 */ + +static int +umount2(const char *path, int flags) { + fprintf(stderr, _("umount: compiled without support for -f\n")); + errno = ENOSYS; + return -1; +} +#endif /* __NR_umount2 */ + +#ifndef MNT_FORCE +# define MNT_FORCE 0x00000001 /* Attempt to forcibily umount */ +#endif + +#endif /* MNT_FORCE */ + +#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 + + +/* True if we are allowed to call /sbin/umount.${FSTYPE} */ +int external_allowed = 1; + +/* Nonzero for force umount (-f). There is kernel support since 2.1.116. */ +int force = 0; + +/* Nonzero for lazy umount (-l). There is kernel support since 2.4.11. */ +int lazy = 0; + +/* When umount fails, attempt a read-only remount (-r). */ +int remount = 0; + +/* Don't write a entry in /etc/mtab (-n). */ +int nomtab = 0; + +/* Call losetup -d for each unmounted loop device. */ +int delloop = 0; + +/* True if (ruid != euid) or (0 != ruid), i.e. only "user" umounts permitted. */ +int restricted = 1; + +/* Last error message */ +int complained_err = 0; +char *complained_dev = NULL; + +/* True for fake umount (--fake). */ +static int fake = 0; + +/* + * check_special_umountprog() + * If there is a special umount program for this type, exec it. + * returns: 0: no exec was done, 1: exec was done, status has result + */ +static int +check_special_umountprog(const char *node, + const char *type, int *status) { + char search_path[] = FS_SEARCH_PATH; + char *path, umountprog[150]; + struct stat statbuf; + int res; + + if (!external_allowed) + return 0; + + if (type == NULL || strcmp(type, "none") == 0) + return 0; + + path = strtok(search_path, ":"); + while (path) { + int type_opt = 0; + + res = snprintf(umountprog, sizeof(umountprog), "%s/umount.%s", + path, type); + path = strtok(NULL, ":"); + if (res < 0 || (size_t) res >= sizeof(umountprog)) + continue; + + res = stat(umountprog, &statbuf); + if (res == -1 && errno == ENOENT && strchr(type, '.')) { + /* If type ends with ".subtype" try without it */ + *strrchr(umountprog, '.') = '\0'; + type_opt = 1; + res = stat(umountprog, &statbuf); + } + if (res == 0) { + res = fork(); + if (res == 0) { + char *umountargs[10]; + int i = 0; + + if(setgid(getgid()) < 0) + die(EX_FAIL, _("umount: cannot set group id: %m")); + + if(setuid(getuid()) < 0) + die(EX_FAIL, _("umount: cannot set user id: %m")); + + umountargs[i++] = umountprog; + umountargs[i++] = xstrdup(node); + if (nomtab) + umountargs[i++] = "-n"; + if (lazy) + umountargs[i++] = "-l"; + if (force) + umountargs[i++] = "-f"; + if (verbose) + umountargs[i++] = "-v"; + if (remount) + umountargs[i++] = "-r"; + if (type_opt) { + umountargs[i++] = "-t"; + umountargs[i++] = (char *) type; + } + umountargs[i] = NULL; + execv(umountprog, umountargs); + exit(1); /* exec failed */ + } else if (res != -1) { + int st; + wait(&st); + *status = (WIFEXITED(st) ? WEXITSTATUS(st) + : EX_SYSERR); + return 1; + } else { + int errsv = errno; + error(_("umount: cannot fork: %s"), + strerror(errsv)); + } + } + } + return 0; +} + +/* complain about a failed umount */ +static void complain(int err, const char *dev) { + + if (complained_err == err && complained_dev && dev && + strcmp(dev, complained_dev) == 0) + return; + + complained_err = err; + free(complained_dev); + complained_dev = xstrdup(dev); + + switch (err) { + case ENXIO: + error (_("umount: %s: invalid block device"), dev); break; + case EINVAL: + error (_("umount: %s: not mounted"), dev); break; + case EIO: + error (_("umount: %s: can't write superblock"), dev); break; + case EBUSY: + /* Let us hope fstab has a line "proc /proc ..." + and not "none /proc ..."*/ + error (_("umount: %s: device is busy.\n" + " (In some cases useful info about processes that use\n" + " the device is found by lsof(8) or fuser(1))"), dev); + break; + case ENOENT: + error (_("umount: %s: not found"), dev); break; + case EPERM: + error (_("umount: %s: must be superuser to umount"), dev); break; + case EACCES: + error (_("umount: %s: block devices not permitted on fs"), dev); break; + default: + error (_("umount: %s: %s"), dev, strerror (err)); break; + } +} + +/* 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 const char *chdir_to_parent(const char *node, char **resbuf) +{ + char *tmp, *res; + const char *parent; + char buf[65536]; + + *resbuf = xstrdup(node); + + tmp = strrchr(*resbuf, '/'); + if (!tmp) + die (2, _("umount: internal error: invalid abs path: %s"), node); + + if (tmp != *resbuf) { + *tmp = '\0'; + res = tmp + 1; + parent = *resbuf; + } else if (tmp[1] != '\0') { + res = tmp + 1; + parent = "/"; + } else { + res = "."; + parent = "/"; + } + + if (chdir(parent) == -1) + die (2, _("umount: failed to chdir to %s: %m"), parent); + + if (!getcwd(buf, sizeof(buf))) + die (2, _("umount: failed to obtain current directory: %m")); + + if (strcmp(buf, parent) != 0) + die (2, _("umount: mountpoint moved (%s -> %s)"), parent, buf); + + if (verbose) + printf(_("current directory moved to %s\n"), res); + + return res; +} + +/* Umount a single device. Return a status code, so don't exit + on a non-fatal error. We lock/unlock around each umount. */ +static int +umount_one (const char *spec, const char *node, const char *type, + struct mntentchn *mc) { + int umnt_err = 0; + int isroot; + int res = 0; + int status; + int extra_flags = 0; + const char *loopdev, *target = node; + char *targetbuf = NULL; + + /* Special case for root. As of 0.99pl10 we can (almost) unmount root; + the kernel will remount it readonly so that we can carry on running + afterwards. The readonly remount is illegal if any files are opened + for writing at the time, so we can't update mtab for an unmount of + root. As it is only really a remount, this doesn't matter too + much. [sct May 29, 1993] */ + isroot = (streq (node, "/") || streq (node, "root") + || streq (node, "rootfs")); + if (isroot) + nomtab++; + + /* + * Call umount.TYPE for types that require a separate umount program. + * All such special things must occur isolated in the types string. + */ + if (check_special_umountprog(node, type, &status)) + return status; + + block_signals(SIG_BLOCK); + + /* Skip the actual umounting for --fake */ + if (fake) + goto writemtab; + + if (restricted) { + if (umount_nofollow_support()) + extra_flags |= UMOUNT_NOFOLLOW; + + /* call umount(2) with relative path to avoid races */ + target = chdir_to_parent(node, &targetbuf); + } + + if (lazy) { + res = umount2 (target, MNT_DETACH | extra_flags); + if (res < 0) + umnt_err = errno; + goto writemtab; + } + + if (force) { /* only supported for NFS */ + res = umount2 (target, MNT_FORCE | extra_flags); + if (res == -1) { + int errsv = errno; + perror("umount2"); + errno = errsv; + if (errno == ENOSYS) { + if (verbose) + printf(_("no umount2, trying umount...\n")); + res = umount (target); + } + } + } else if (extra_flags) + res = umount2 (target, extra_flags); + else + res = umount (target); + + free(targetbuf); + + if (res < 0) + umnt_err = errno; + + if (res < 0 && remount && umnt_err == EBUSY) { + /* Umount failed - let us try a remount */ + res = mount(spec, node, NULL, + MS_MGC_VAL | MS_REMOUNT | MS_RDONLY, NULL); + if (res == 0) { + fprintf(stderr, + _("umount: %s busy - remounted read-only\n"), + spec); + if (mc && !nomtab) { + /* update mtab if the entry is there */ + struct my_mntent remnt; + remnt.mnt_fsname = mc->m.mnt_fsname; + remnt.mnt_dir = mc->m.mnt_dir; + remnt.mnt_type = mc->m.mnt_type; + remnt.mnt_opts = "ro"; + remnt.mnt_freq = 0; + remnt.mnt_passno = 0; + update_mtab(node, &remnt); + } + block_signals(SIG_UNBLOCK); + return 0; + } else if (errno != EBUSY) { /* hmm ... */ + perror("remount"); + fprintf(stderr, + _("umount: could not remount %s read-only\n"), + spec); + } + } + + loopdev = 0; + if (res >= 0) { + /* Umount succeeded */ + if (verbose) + printf (_("%s has been unmounted\n"), spec); + + /* Free any loop devices that we allocated ourselves */ + if (mc) { + char *optl; + + /* old style mtab line? */ + if (streq(mc->m.mnt_type, "loop")) { + loopdev = spec; + goto gotloop; + } + + /* new style mtab line? */ + optl = mc->m.mnt_opts ? xstrdup(mc->m.mnt_opts) : ""; + for (optl = strtok (optl, ","); optl; + optl = strtok (NULL, ",")) { + if (!strncmp(optl, "loop=", 5)) { + loopdev = optl+5; + goto gotloop; + } + } + } else { + /* + * If option "-o loop=spec" occurs in mtab, + * note the mount point, and delete mtab line. + */ + if ((mc = getmntoptfile (spec)) != NULL) + node = mc->m.mnt_dir; + } + + /* Also free loop devices when -d flag is given */ + if (delloop) + loopdev = spec; + } + gotloop: + if (loopdev) { + struct loopdev_cxt lc; + + loopcxt_init(&lc, 0); + loopcxt_set_device(&lc, loopdev); + + if (loopcxt_get_backing_file(&lc) + && loopcxt_is_autoclear(&lc) == 0) + loopcxt_delete_device(&lc); + loopcxt_deinit(&lc); + } + + writemtab: + if (!nomtab && + (umnt_err == 0 || umnt_err == EINVAL || umnt_err == ENOENT)) { + + update_mtab (node, NULL); + } + + block_signals(SIG_UNBLOCK); + + if (res >= 0) + return 0; + if (umnt_err) + complain(umnt_err, node); + return 1; +} + +/* + * umount_one_bw: unmount FILE that has last occurrence MC0 + * + * Why this loop? + * 1. People who boot a system with a bad fstab root entry + * will get an incorrect "/dev/foo on /" in mtab. + * If later /dev/foo is actually mounted elsewhere, + * it will occur twice in mtab. + * 2. With overmounting one can get the situation that + * the same filename is used as mount point twice. + * In both cases, it is best to try the last occurrence first. + */ +static int +umount_one_bw (const char *file, struct mntentchn *mc0) { + struct mntentchn *mc; + int res = 1; + + mc = mc0; + while (res && mc) { + res = umount_one(mc->m.mnt_fsname, mc->m.mnt_dir, + mc->m.mnt_type, mc); + mc = getmntdirbackward(file, mc); + } + mc = mc0; + while (res && mc) { + res = umount_one(mc->m.mnt_fsname, mc->m.mnt_dir, + mc->m.mnt_type, mc); + mc = getmntdevbackward(file, mc); + } + return res; +} + +/* Unmount all filesystems of type VFSTYPES found in mtab. Since we are + concurrently updating mtab after every successful umount, we have to + slurp in the entire file before we start. This isn't too bad, because + in any case it's important to umount mtab entries in reverse order + to mount, e.g. /usr/spool before /usr. */ +static int +umount_all (char *types, char *test_opts) { + struct mntentchn *mc, *hd; + int errors = 0; + + hd = mtab_head(); + if (!hd->prev) + die (2, _("umount: cannot find list of filesystems to unmount")); + for (mc = hd->prev; mc != hd; mc = mc->prev) { + if (matching_type (mc->m.mnt_type, types) + && matching_opts (mc->m.mnt_opts, test_opts)) { + errors |= umount_one (mc->m.mnt_fsname, mc->m.mnt_dir, + mc->m.mnt_type, mc); + } + } + + return errors; +} + +static struct option longopts[] = +{ + { "all", 0, 0, 'a' }, + { "force", 0, 0, 'f' }, + { "help", 0, 0, 'h' }, + { "no-mtab", 0, 0, 'n' }, + { "test-opts", 1, 0, 'O' }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { "read-only", 0, 0, 'r' }, + { "types", 1, 0, 't' }, + + { "no-canonicalize", 0, 0, 144 }, + { "fake", 0, 0, 145 }, + { NULL, 0, 0, 0 } +}; + +static void +usage (FILE *fp, int n) +{ + fprintf (fp, _("Usage: umount -h | -V\n" + " umount -a [-d] [-f] [-r] [-n] [-v] [-t vfstypes] [-O opts]\n" + " umount [-d] [-f] [-r] [-n] [-v] special | node...\n")); + exit (n); +} + +/* + * Look for an option in a comma-separated list + */ +static int +contains(const char *list, const char *s) { + int n = strlen(s); + + while (list && *list) { + if (strncmp(list, s, n) == 0 && + (list[n] == 0 || list[n] == ',')) + return 1; + while (*list && *list++ != ',') ; + } + return 0; +} + +/* check if @mc contains a loop device which is associated + * with the @file in fs + */ +static int +is_valid_loop(struct mntentchn *mc, struct mntentchn *fs) +{ + uintmax_t offset = 0; + char *p; + + /* check if it begins with /dev/loop */ + if (strncmp(mc->m.mnt_fsname, _PATH_DEV_LOOP, + sizeof(_PATH_DEV_LOOP) - 1)) + return 0; + + /* check for loop option in fstab */ + if (!contains(fs->m.mnt_opts, "loop")) + return 0; + + /* check for offset option in fstab */ + p = get_option_value(fs->m.mnt_opts, "offset="); + if (p && strtosize(p, &offset)) { + if (verbose > 1) + printf(_("failed to parse 'offset=%s' options\n"), p); + return 0; + } + + /* check association */ + if (loopdev_is_used((char *) mc->m.mnt_fsname, fs->m.mnt_fsname, + offset, LOOPDEV_FL_OFFSET) == 1) { + if (verbose > 1) + printf(_("device %s is associated with %s\n"), + mc->m.mnt_fsname, fs->m.mnt_fsname); + return 1; + } + + if (verbose > 1) + printf(_("device %s is not associated with %s\n"), + mc->m.mnt_fsname, fs->m.mnt_fsname); + return 0; +} + +/* + * umount helper call based on {u,p}helper= mount option + */ +static int check_helper_umountprog(const char *node, + const char *opts, const char *name, + int *status) +{ + char *helper; + + if (!external_allowed || !opts) + return 0; + + helper = get_option_value(opts, name); + if (helper) + return check_special_umountprog(node, helper, status); + + return 0; +} + +static int +umount_file (char *arg) { + struct mntentchn *mc, *fs; + const char *file, *options; + int fstab_has_user, fstab_has_users, fstab_has_owner, fstab_has_group; + int ok, status = 0; + struct stat statbuf; + char *loopdev = NULL; + + if (!*arg) { /* "" would be expanded to `pwd` */ + die(2, _("Cannot unmount \"\"\n")); + return 0; + } + + file = canonicalize(arg); /* mtab paths are canonicalized */ + +try_loopdev: + if (verbose > 1) + printf(_("Trying to unmount %s\n"), file); + + mc = getmntdirbackward(file, NULL); + if (!mc) { + mc = getmntdevbackward(file, NULL); + if (mc) { + struct mntentchn *mc1; + char *cn; + + mc1 = getmntdirbackward(mc->m.mnt_dir, NULL); + if (!mc1) + /* 'mc1' must exist, though not necessarily + equals to `mc'. Otherwise we go mad. */ + die(EX_SOFTWARE, + _("umount: confused when analyzing mtab")); + + cn = canonicalize(mc1->m.mnt_fsname); + if (cn && strcmp(file, cn)) { + /* Something was stacked over `file' on the + same mount point. */ + die(EX_FAIL, _("umount: cannot unmount %s -- %s is " + "mounted over it on the same point"), + file, mc1->m.mnt_fsname); + } + free(cn); + } + } + if (!mc && verbose) + printf(_("Could not find %s in mtab\n"), file); + + /* not found in mtab - check if it is associated with some loop device + * (only if it is a regular file) + */ + if (!mc && !loopdev && !stat(file, &statbuf) && S_ISREG(statbuf.st_mode)) { + int count = loopdev_count_by_backing_file(file, &loopdev); + + if (count == 1) { + if (verbose) + printf(_("%s is associated with %s\n"), + arg, loopdev); + file = loopdev; + goto try_loopdev; + + } else if (count > 1) + fprintf(stderr, _("umount: warning: %s is associated " + "with more than one loop device\n"), arg); + } + + if (mc) { + /* + * helper - umount helper (e.g. pam_mount) + */ + if (check_helper_umountprog(arg, mc->m.mnt_opts, + "helper=", &status)) + return status; + } + + if (restricted) { + char *mtab_user = NULL; + + if (!mc) + die(2, + _("umount: %s is not mounted (according to mtab)"), + file); + /* + * uhelper - unprivileged umount helper (e.g. HAL/udisks mounts) + */ + if (check_helper_umountprog(arg, mc->m.mnt_opts, + "uhelper=", &status)) + return status; + + /* The 2.4 kernel will generally refuse to mount the same + filesystem on the same mount point, but will accept NFS. + So, unmounting must be possible. */ + if (!is_mounted_once(file) && strcmp(mc->m.mnt_type,"nfs")) + die(2, + _("umount: it seems %s is mounted multiple times"), + file); + + /* 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 = getfs_by_devdir(mc->m.mnt_fsname, mc->m.mnt_dir); + if (!fs) { + fs = getfs_by_dir(file); + if (!fs && !getfs_by_spec(file)) + die (2, + _("umount: %s is not in the fstab " + "(and you are not root)"), + file); + + /* spec could be a file which is loop mounted */ + if (!fs || !is_valid_loop(mc, fs)) + die (2, _("umount: %s mount disagrees with " + "the fstab"), file); + } + + /* + * 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). + */ + + options = fs->m.mnt_opts; + if (!options) + options = ""; + fstab_has_user = contains(options, "user"); + fstab_has_users = contains(options, "users"); + fstab_has_owner = contains(options, "owner"); + fstab_has_group = contains(options, "group"); + ok = 0; + + if (fstab_has_users) + ok = 1; + + if (!ok && (fstab_has_user || fstab_has_owner || + fstab_has_group)) { + char *user = getusername(); + + options = mc->m.mnt_opts; + if (!options) + options = ""; + mtab_user = get_option_value(options, "user="); + + if (user && mtab_user && streq (user, mtab_user)) + ok = 1; + } + if (!ok) + die (2, _("umount: only %s can unmount %s from %s"), + mtab_user ? mtab_user : "root", + fs->m.mnt_fsname, fs->m.mnt_dir); + + } + + if (mc) + return umount_one_bw (file, mc); + else + return umount_one (arg, arg, arg, NULL); +} + +int +main (int argc, char *argv[]) { + int c; + int all = 0; + char *types = NULL, *test_opts = NULL, *p; + int result = 0; + + sanitize_env(); + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + progname = argv[0]; + if ((p = strrchr(progname, '/')) != NULL) + progname = p+1; + + umask(022); + + while ((c = getopt_long (argc, argv, "adfhlnrit:O:vV", + longopts, NULL)) != -1) + switch (c) { + case 'a': /* umount everything */ + ++all; + break; + /* fall through? */ + case 'd': /* do losetup -d for unmounted loop devices */ + ++delloop; + break; + case 'f': /* force umount */ + ++force; + break; + case 'h': /* help */ + usage (stdout, 0); + break; + case 'l': /* lazy umount */ + ++lazy; + break; + case 'n': /* do not write in /etc/mtab */ + ++nomtab; + break; + case 'O': /* specify file system options */ + test_opts = optarg; + break; + case 'r': /* remount read-only if umount fails */ + ++remount; + break; + case 'v': /* make noise */ + ++verbose; + break; + case 'V': /* version */ + printf ("umount (%s)\n", PACKAGE_STRING); + exit (0); + case 't': /* specify file system type */ + types = optarg; + break; + case 'i': + external_allowed = 0; + break; + case 144: + nocanonicalize = 1; + break; + case 145: + fake = 1; + break; + case 0: + break; + case '?': + default: + usage (stderr, 1); + } + + { + const uid_t ruid = getuid(); + const uid_t euid = geteuid(); + + /* if we're really root and aren't running setuid */ + if (((uid_t)0 == ruid) && (ruid == euid)) { + restricted = 0; + } + } + + if (restricted && + (all || types || nomtab || force || remount || nocanonicalize || + fake)) { + die (2, _("umount: only root can do that")); + } + + argc -= optind; + argv += optind; + + atexit(unlock_mtab); + + if (all) { + /* nodev stuff: sysfs, usbfs, oprofilefs, ... */ + if (types == NULL) + types = "noproc,nodevfs,nodevpts,nosysfs,norpc_pipefs,nonfsd"; + result = umount_all (types, test_opts); + } else if (argc < 1) { + usage (stderr, 2); + } else while (argc--) { + result += umount_file(*argv++); + } + exit (result); /* nonzero on at least one failure */ +} |