kernel - Fix mount refs interactions and umount races
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 23 Mar 2012 04:37:25 +0000 (21:37 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Thu, 29 Mar 2012 23:06:15 +0000 (16:06 -0700)
* It is possible for a umount to race other operations on active mount
  point, causing one or the other to deadlock.

* vfs_busy()/vfs_unbusy() now incr/decr mp->mnt_refs.

* cache_findmount() now increments mp->mnt_refs, and add a new API
  function cache_dropmount() which decrements it.

sys/kern/vfs_cache.c
sys/kern/vfs_mount.c
sys/kern/vfs_nlookup.c
sys/kern/vfs_syscalls.c
sys/sys/namecache.h

index c764e77..169aeef 100644 (file)
@@ -2499,6 +2499,7 @@ cache_findmount_callback(struct mount *mp, void *data)
            mp->mnt_ncmounton.ncp == info->nch_ncp
        ) {
            info->result = mp;
+           atomic_add_int(&mp->mnt_refs, 1);
            return(-1);
        }
        return(0);
@@ -2517,6 +2518,12 @@ cache_findmount(struct nchandle *nch)
        return(info.result);
 }
 
+void
+cache_dropmount(struct mount *mp)
+{
+       atomic_add_int(&mp->mnt_refs, -1);
+}
+
 /*
  * Resolve an unresolved namecache entry, generally by looking it up.
  * The passed ncp must be locked and refd. 
index 18c83fe..c5cefa2 100644 (file)
@@ -251,9 +251,12 @@ vfs_busy(struct mount *mp, int flags)
 {
        int lkflags;
 
+       atomic_add_int(&mp->mnt_refs, 1);
        if (mp->mnt_kern_flag & MNTK_UNMOUNT) {
-               if (flags & LK_NOWAIT)
+               if (flags & LK_NOWAIT) {
+                       atomic_add_int(&mp->mnt_refs, -1);
                        return (ENOENT);
+               }
                /* XXX not MP safe */
                mp->mnt_kern_flag |= MNTK_MWAIT;
                /*
@@ -263,6 +266,7 @@ vfs_busy(struct mount *mp, int flags)
                 * exclusive lock at the end of dounmount.
                 */
                tsleep((caddr_t)mp, 0, "vfs_busy", 0);
+               atomic_add_int(&mp->mnt_refs, -1);
                return (ENOENT);
        }
        lkflags = LK_SHARED;
@@ -273,10 +277,14 @@ vfs_busy(struct mount *mp, int flags)
 
 /*
  * Free a busy filesystem.
+ *
+ * Decrement refs before releasing the lock so e.g. a pending umount
+ * doesn't give us an unexpected busy error.
  */
 void
 vfs_unbusy(struct mount *mp)
 {
+       atomic_add_int(&mp->mnt_refs, -1);
        lockmgr(&mp->mnt_lock, LK_RELEASE);
 }
 
index ab0a87e..e42d06a 100644 (file)
@@ -727,11 +727,14 @@ nlookup(struct nlookupdata *nd)
                    ;
                error = VFS_ROOT(mp, &tdp);
                vfs_unbusy(mp);
-               if (error)
+               if (error) {
+                   cache_dropmount(mp);
                    break;
+               }
                cache_setvp(&nch, tdp);
                vput(tdp);
            }
+           cache_dropmount(mp);
        }
        if (error) {
            cache_put(&nch);
index 11c4933..06ce36a 100644 (file)
@@ -181,10 +181,13 @@ sys_mount(struct mount_args *uap)
        cache_zero(&nd.nl_nch);
        nlookup_done(&nd);
 
-       if ((nch.ncp->nc_flag & NCF_ISMOUNTPT) && cache_findmount(&nch))
+       if ((nch.ncp->nc_flag & NCF_ISMOUNTPT) &&
+           (mp = cache_findmount(&nch)) != NULL) {
+               cache_dropmount(mp);
                hasmount = 1;
-       else
+       } else {
                hasmount = 0;
+       }
 
 
        /*
@@ -1544,6 +1547,7 @@ sys_fchdir(struct fchdir_args *uap)
                        cache_drop(&nch);
                        nch = tnch;
                }
+               cache_dropmount(mp);
        }
        if (error == 0) {
                ovp = fdp->fd_cdir;
index 55ef726..4078c5e 100644 (file)
@@ -192,6 +192,7 @@ struct nchandle cache_nlookup_nonblock(struct nchandle *nch,
                        struct nlcomponent *nlc);
 void   cache_allocroot(struct nchandle *nch, struct mount *mp, struct vnode *vp);
 struct mount *cache_findmount(struct nchandle *nch);
+void cache_dropmount(struct mount *mp);
 int    cache_inval(struct nchandle *nch, int flags);
 int    cache_inval_vp(struct vnode *vp, int flags);
 int    cache_inval_vp_nonblock(struct vnode *vp);