kernel - Fix deadlock when umount races an access on the underlying filesystem
authorMatthew Dillon <dillon@apollo.backplane.com>
Tue, 29 Jan 2013 23:04:05 +0000 (15:04 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Tue, 29 Jan 2013 23:04:05 +0000 (15:04 -0800)
* The nlookup code temporarily busies the target mount when diving a
  mount point in cases where the base of the mount is not resolved
  in the namecache.

* Fix a deadlock which can occur between the namecache structural lock
  and the vfs_busy() on the mount structure by reordering the lock.

* Generally only occured if an attempt was made to unmount a filesystem
  on which programs are still doing active operations (verses just
  passively holding references to the filesystem).  For example, a
  umount on the target filesystem occuring while a cpdup using an
  absolute path is running.

Reported-by: ftigeot
sys/kern/vfs_nlookup.c

index 7c1d378..1bc9d6b 100644 (file)
@@ -735,15 +735,29 @@ nlookup(struct nlookupdata *nd)
            (mp = cache_findmount(&nch)) != NULL
        ) {
            struct vnode *tdp;
+           int vfs_do_busy = 0;
 
+           /*
+            * VFS must be busied before the namecache entry is locked,
+            * but we don't want to waste time calling vfs_busy() if the
+            * mount point is already resolved.
+            */
+again:
            cache_put(&nch);
+           if (vfs_do_busy) {
+               while (vfs_busy(mp, 0))
+                   ;
+           }
            cache_get(&mp->mnt_ncmountpt, &nch);
 
            if (nch.ncp->nc_flag & NCF_UNRESOLVED) {
-               while (vfs_busy(mp, 0))
-                   ;
+               if (vfs_do_busy == 0) {
+                   vfs_do_busy = 1;
+                   goto again;
+               }
                error = VFS_ROOT(mp, &tdp);
                vfs_unbusy(mp);
+               vfs_do_busy = 0;
                if (error) {
                    cache_dropmount(mp);
                    break;
@@ -751,6 +765,8 @@ nlookup(struct nlookupdata *nd)
                cache_setvp(&nch, tdp);
                vput(tdp);
            }
+           if (vfs_do_busy)
+               vfs_unbusy(mp);
            cache_dropmount(mp);
        }
        if (error) {