kernel - Fix filesystem lookup error due to parent directory recyclement race
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 7 Dec 2012 22:44:26 +0000 (14:44 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 7 Dec 2012 22:44:26 +0000 (14:44 -0800)
* When looking up a path the parent ncp's vnode is needed to pass into
  the VFS code as the directory vnode (dvp) for the element being looked up.

* Fix a timing race whereby a system under extreme vnode pressure (such as
  when kern.maxvnodes is set to a very low value) can squeek in recyclement
  of this directory vnode when there are no children under it in the
  namecache.

  We fix the problem by holding the directory vnode during the nlookup() and
  cache_resolve().

sys/kern/vfs_nlookup.c

index d770503..7c1d378 100644 (file)
@@ -412,6 +412,7 @@ nlookup(struct nlookupdata *nd)
     struct nchandle par;
     struct nchandle nctmp;
     struct mount *mp;
+    struct vnode *hvp;         /* hold to prevent recyclement */
     int wasdotordotdot;
     char *ptr;
     char *xptr;
@@ -555,11 +556,21 @@ nlookup(struct nlookupdata *nd)
            wasdotordotdot = 2;
        } else {
            /*
-            * Must unlock nl_nch when traversing down the path.
+            * Must unlock nl_nch when traversing down the path.  However,
+            * the child ncp has not yet been found/created and the parent's
+            * child list might be empty.  Thus releasing the lock can
+            * allow a race whereby the parent ncp's vnode is recycled.
+            * This case can occur especially when maxvnodes is set very low.
+            *
+            * We need the parent's ncp to remain resolved for all normal
+            * filesystem activities, so we vhold() the vp during the lookup
+            * to prevent recyclement due to vnlru / maxvnodes.
             *
             * If we race an unlink or rename the ncp might be marked
             * DESTROYED after resolution, requiring a retry.
             */
+           if ((hvp = nd->nl_nch.ncp->nc_vp) != NULL)
+               vhold(hvp);
            cache_unlock(&nd->nl_nch);
            nd->nl_flags &= ~NLC_NCPISLOCKED;
            nch = cache_nlookup(&nd->nl_nch, &nlc);
@@ -572,6 +583,8 @@ nlookup(struct nlookupdata *nd)
                cache_put(&nch);
                nch = cache_nlookup(&nd->nl_nch, &nlc);
            }
+           if (hvp)
+               vdrop(hvp);
            wasdotordotdot = 0;
        }