From 214297b8eb7dc08618ae717483f5deb3ad1574cd Mon Sep 17 00:00:00 2001 From: Alexander Bluhm Date: Sat, 15 Apr 2017 13:56:44 +0000 Subject: After forced unmount of a file system that has other mount points in it, dangling mounts could remain. When unmounting check the hierarcy and unmount recursively. Also prevent that a new mount appears during the scan. Joint work with natano@; testing and OK krw@ --- sys/kern/vfs_syscalls.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++- sys/sys/mount.h | 3 +- 2 files changed, 86 insertions(+), 2 deletions(-) (limited to 'sys') diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c index 25d53b46afe..b41c4a24b96 100644 --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vfs_syscalls.c,v 1.271 2017/02/15 03:36:58 guenther Exp $ */ +/* $OpenBSD: vfs_syscalls.c,v 1.272 2017/04/15 13:56:43 bluhm Exp $ */ /* $NetBSD: vfs_syscalls.c,v 1.71 1996/04/23 10:29:02 mycroft Exp $ */ /* @@ -88,6 +88,7 @@ int domkdirat(struct proc *, int, const char *, mode_t); int doutimensat(struct proc *, int, const char *, struct timespec [2], int); int dovutimens(struct proc *, struct vnode *, struct timespec [2]); int dofutimens(struct proc *, int, struct timespec [2]); +int dounmount_leaf(struct mount *, int, struct proc *); /* * Virtual File System System Calls @@ -204,7 +205,22 @@ sys_mount(struct proc *p, void *v, register_t *retval) strncpy(mp->mnt_stat.f_fstypename, vfsp->vfc_name, MFSNAMELEN); mp->mnt_vnodecovered = vp; mp->mnt_stat.f_owner = p->p_ucred->cr_uid; + update: + /* Ensure that the parent mountpoint does not get unmounted. */ + error = vfs_busy(vp->v_mount, VB_READ|VB_NOWAIT); + if (error) { + if (mp->mnt_flag & MNT_UPDATE) { + mp->mnt_flag = mntflag; + vfs_unbusy(mp); + } else { + vfs_unbusy(mp); + free(mp, M_MOUNT, sizeof(*mp)); + } + vput(vp); + return (error); + } + /* * Set the mount level flags. */ @@ -226,6 +242,7 @@ update: mp->mnt_stat.f_ctime = time_second; } if (mp->mnt_flag & MNT_UPDATE) { + vfs_unbusy(vp->v_mount); vput(vp); if (mp->mnt_flag & MNT_WANTRDWR) mp->mnt_flag &= ~MNT_RDONLY; @@ -257,6 +274,7 @@ update: vfsp->vfc_refcount++; TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list); checkdirs(vp); + vfs_unbusy(vp->v_mount); VOP_UNLOCK(vp, p); if ((mp->mnt_flag & MNT_RDONLY) == 0) error = vfs_allocate_syncvnode(mp); @@ -268,6 +286,7 @@ update: mp->mnt_vnodecovered->v_mountedhere = NULL; vfs_unbusy(mp); free(mp, M_MOUNT, sizeof(*mp)); + vfs_unbusy(vp->v_mount); vput(vp); } return (error); @@ -373,6 +392,70 @@ sys_unmount(struct proc *p, void *v, register_t *retval) */ int dounmount(struct mount *mp, int flags, struct proc *p) +{ + SLIST_HEAD(, mount) mplist; + struct mount *nmp; + int error; + + SLIST_INIT(&mplist); + SLIST_INSERT_HEAD(&mplist, mp, mnt_dounmount); + + /* + * Collect nested mount points. This takes advantage of the mount list + * being ordered - nested mount points come after their parent. + */ + while ((mp = TAILQ_NEXT(mp, mnt_list)) != NULL) { + SLIST_FOREACH(nmp, &mplist, mnt_dounmount) { + if (mp->mnt_vnodecovered == NULLVP || + mp->mnt_vnodecovered->v_mount != nmp) + continue; + + if ((flags & MNT_FORCE) == 0) { + error = EBUSY; + goto err; + } + error = vfs_busy(mp, VB_WRITE|VB_WAIT); + if (error) { + if ((flags & MNT_DOOMED)) { + /* + * If the mount point was busy due to + * being unmounted, it has been removed + * from the mount list already. + * Restart the iteration from the last + * collected busy entry. + */ + mp = SLIST_FIRST(&mplist); + break; + } + goto err; + } + SLIST_INSERT_HEAD(&mplist, mp, mnt_dounmount); + break; + } + } + + /* + * Nested mount points cannot appear during this loop as mounting + * requires a read lock for the parent mount point. + */ + while ((mp = SLIST_FIRST(&mplist)) != NULL) { + SLIST_REMOVE(&mplist, mp, mount, mnt_dounmount); + error = dounmount_leaf(mp, flags, p); + if (error) + goto err; + } + return (0); + +err: + while ((mp = SLIST_FIRST(&mplist)) != NULL) { + SLIST_REMOVE(&mplist, mp, mount, mnt_dounmount); + vfs_unbusy(mp); + } + return (error); +} + +int +dounmount_leaf(struct mount *mp, int flags, struct proc *p) { struct vnode *coveredvp; int error; diff --git a/sys/sys/mount.h b/sys/sys/mount.h index 4c5d62658cf..90ce21e2117 100644 --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mount.h,v 1.128 2017/01/10 19:48:32 bluhm Exp $ */ +/* $OpenBSD: mount.h,v 1.129 2017/04/15 13:56:43 bluhm Exp $ */ /* $NetBSD: mount.h,v 1.48 1996/02/18 11:55:47 fvdl Exp $ */ /* @@ -347,6 +347,7 @@ LIST_HEAD(vnodelst, vnode); struct mount { TAILQ_ENTRY(mount) mnt_list; /* mount list */ + SLIST_ENTRY(mount) mnt_dounmount; /* unmount work queue */ const struct vfsops *mnt_op; /* operations on fs */ struct vfsconf *mnt_vfc; /* configuration info */ struct vnode *mnt_vnodecovered; /* vnode we mounted on */ -- cgit v1.2.3