After forced unmount of a file system that has other mount points
authorbluhm <bluhm@openbsd.org>
Sat, 15 Apr 2017 13:56:43 +0000 (13:56 +0000)
committerbluhm <bluhm@openbsd.org>
Sat, 15 Apr 2017 13:56:43 +0000 (13:56 +0000)
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
sys/sys/mount.h

index 25d53b4..b41c4a2 100644 (file)
@@ -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;
index 4c5d626..90ce21e 100644 (file)
@@ -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 */