static int lookup_umount_fs(struct libmnt_context *cxt) { int rc, loopdev = 0; const char *tgt; struct libmnt_table *mtab = NULL; struct libmnt_fs *fs; struct libmnt_cache *cache = NULL; char *cn_tgt = NULL; 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; } /* * The mtab file maybe huge and on systems with utab we have to merge * userspace mount options into /proc/self/mountinfo. This all is * expensive. The mtab filter allows to filter out entries, then * mtab and utab are very tiny files. * * *but*... the filter uses mnt_fs_streq_{target,srcpath} functions * where LABEL, UUID or symlinks are to canonicalized. It means that * it's usable only for canonicalized stuff (e.g. kernel mountinfo). */ if (!cxt->mtab_writable && *tgt == '/' && !mnt_context_is_force(cxt) && !mnt_context_is_lazy(cxt)) { struct stat st; if (stat(tgt, &st) == 0 && S_ISDIR(st.st_mode)) { /* we'll canonicalized /proc/self/mountinfo */ cache = mnt_context_get_cache(cxt); cn_tgt = mnt_resolve_path(tgt, cache); if (cn_tgt) mnt_context_set_tabfilter(cxt, mtab_filter, cn_tgt); } } rc = mnt_context_get_mtab(cxt, &mtab); if (cn_tgt) { mnt_context_set_tabfilter(cxt, NULL, NULL); if (!cache) free(cn_tgt); } if (rc) { DBG(CXT, mnt_debug_h(cxt, "umount: failed to read mtab")); return rc; } 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, 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 && !loopdev && mnt_context_is_swapmatch(cxt)) { /* * Maybe the option is /path/file.img, try to convert to /dev/loopN */ struct stat st; if (stat(tgt, &st) == 0 && S_ISREG(st.st_mode)) { char *dev = NULL; int count = loopdev_count_by_backing_file(tgt, &dev); if (count == 1) { DBG(CXT, mnt_debug_h(cxt, "umount: %s --> %s (retry)", tgt, dev)); mnt_fs_set_source(cxt->fs, tgt); mnt_fs_set_target(cxt->fs, dev); free(dev); tgt = mnt_fs_get_target(cxt->fs); loopdev = 1; /* to avoid endless loop */ goto try_loopdev; } else if (count > 1) DBG(CXT, mnt_debug_h(cxt, "umount: warning: %s is associated " "with more than one loopdev", tgt)); } } if (!fs) { DBG(CXT, mnt_debug_h(cxt, "umount: cannot find %s in mtab", tgt)); return 0; } if (fs != cxt->fs) { /* 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; }
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, mnt_debug_h(cxt, "do umount")); if (cxt->restricted && !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; else if (mnt_context_is_force(cxt)) flags |= MNT_FORCE; DBG(CXT, mnt_debug_h(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, mnt_debug_h(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_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; }
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 (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 && !endswith(cxt->helper, type)) { args[i++] = "-t"; /* 8 */ args[i++] = (char *) type; /* 9 */ } args[i] = NULL; /* 10 */ #ifdef CONFIG_LIBMOUNT_DEBUG 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_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) { int rc; struct libmnt_table *mtab = NULL; struct libmnt_fs *fs; char *loopdev = NULL; 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 */ /* * 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 allows to filter out entries, then * a mount table and utab are very tiny files. * * *but*... the filter uses mnt_fs_streq_{target,srcpath} functions * where LABEL, UUID or symlinks are canonicalized. It means that * it's usable only for canonicalized stuff (e.g. kernel mountinfo). */ if (!mnt_context_mtab_writable(cxt) && *tgt == '/' && !mnt_context_is_force(cxt) && !mnt_context_is_lazy(cxt)) 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; } 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)); } } if (pfs) *pfs = fs; free(loopdev); DBG(CXT, ul_debugobj(cxt, "umount fs: %s", fs ? mnt_fs_get_target(fs) : "<not found>")); return fs ? 0 : 1; err: free(loopdev); return rc; }
/* this is umount replacement to mnt_context_apply_fstab(), use * mnt_context_tab_applied() to check result. */ static int lookup_umount_fs(struct libmnt_context *cxt) { const char *tgt; struct stat st; struct libmnt_fs *fs = NULL; int rc = 0; assert(cxt); assert(cxt->fs); tgt = mnt_fs_get_target(cxt->fs); if (!tgt) { DBG(CXT, ul_debugobj(cxt, "umount: undefined target")); return -EINVAL; } /* * 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_loopdel(cxt) && mnt_stat_mountpoint(tgt, &st) == 0 && S_ISDIR(st.st_mode) && !has_utab_entry(cxt, tgt)) { const char *type = mnt_fs_get_fstype(cxt->fs); /* !mnt_context_mtab_writable(cxt) && has_utab_entry() verified that there * is no stuff in utab, so disable all mtab/utab related actions */ mnt_context_disable_mtab(cxt, TRUE); if (!type) { struct statfs vfs; if (statfs(tgt, &vfs) == 0) type = mnt_statfs_get_fstype(&vfs); if (type) { rc = mnt_fs_set_fstype(cxt->fs, type); if (rc) return rc; } } if (type) { DBG(CXT, ul_debugobj(cxt, "umount: mountinfo unnecessary [type=%s]", type)); return 0; } } rc = mnt_context_find_umount_fs(cxt, tgt, &fs); if (rc < 0) return rc; if (rc == 1 || !fs) { DBG(CXT, ul_debugobj(cxt, "umount: cannot find '%s' in mtab", tgt)); return 0; /* this is correct! */ } if (fs != cxt->fs) { /* 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, ul_debugobj(cxt, "umount: failed to copy FS")); return -errno; } DBG(CXT, ul_debugobj(cxt, "umount: mtab applied")); } cxt->flags |= MNT_FL_TAB_APPLIED; return rc; }
static int umount_main(struct libmnt_context *cxt, int argc, char **argv) { int rc, c; char *spec = NULL, *opts = NULL; static const struct option longopts[] = { { "force", 0, 0, 'f' }, { "help", 0, 0, 'h' }, { "no-mtab", 0, 0, 'n' }, { "verbose", 0, 0, 'v' }, { "read-only", 0, 0, 'r' }, { "lazy", 0, 0, 'l' }, { "types", 1, 0, 't' }, { NULL, 0, 0, 0 } }; mnt_context_init_helper(cxt, MNT_ACT_UMOUNT, 0); while ((c = getopt_long (argc, argv, "fvnrlh", longopts, NULL)) != -1) { rc = mnt_context_helper_setopt(cxt, c, optarg); if (rc == 0) /* valid option */ continue; if (rc < 0) /* error (probably ENOMEM) */ goto err; /* rc==1 means unknow option */ umount_usage(); return EX_USAGE; } if (optind < argc) spec = argv[optind++]; if (!spec || (*spec != '/' && strchr(spec,':') == NULL)) { nfs_error(_("%s: no mount point provided"), progname); return EX_USAGE; } if (mnt_context_set_target(cxt, spec)) goto err; if (mnt_context_set_fstype_pattern(cxt, "nfs,nfs4")) /* restrict filesystems */ goto err; /* read mtab/fstab, evaluate permissions, etc. */ rc = mnt_context_prepare_umount(cxt); if (rc) { nfs_error(_("%s: failed to prepare umount: %s\n"), progname, strerror(-rc)); goto err; } opts = retrieve_mount_options(mnt_context_get_fs(cxt)); if (!mnt_context_is_lazy(cxt)) { if (opts) { /* we have full FS description (e.g. from mtab or /proc) */ switch (is_vers4(cxt)) { case 0: /* We ignore the error from nfs_umount23. * If the actual umount succeeds (in del_mtab), * we don't want to signal an error, as that * could cause /sbin/mount to retry! */ nfs_umount23(mnt_context_get_source(cxt), opts); break; case 1: /* unknown */ break; default: /* error */ goto err; } } else /* strange, no entry in mtab or /proc not mounted */ nfs_umount23(spec, "tcp,v3"); } rc = mnt_context_do_umount(cxt); /* call umount(2) syscall */ mnt_context_finalize_mount(cxt); /* mtab update */ if (rc && !mnt_context_get_status(cxt)) { /* mnt_context_do_umount() returns errno if umount(2) failed */ umount_error(rc, spec); goto err; } free(opts); return EX_SUCCESS; err: free(opts); return EX_FAIL; }