diff options
Diffstat (limited to 'sys/kern/vfs_syscalls.c')
-rw-r--r-- | sys/kern/vfs_syscalls.c | 85 |
1 files changed, 84 insertions, 1 deletions
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); @@ -374,6 +393,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; int hadsyncer = 0; |