summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorAlexander Bluhm <bluhm@cvs.openbsd.org>2017-04-15 13:56:44 +0000
committerAlexander Bluhm <bluhm@cvs.openbsd.org>2017-04-15 13:56:44 +0000
commit214297b8eb7dc08618ae717483f5deb3ad1574cd (patch)
tree0f63280400f3eecaee536bc002c58e00debeb0c1 /sys
parenta2d1d04709f2495086f98f1bc669129c23c97ca6 (diff)
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@
Diffstat (limited to 'sys')
-rw-r--r--sys/kern/vfs_syscalls.c85
-rw-r--r--sys/sys/mount.h3
2 files changed, 86 insertions, 2 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;
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 */