Print this page
5360 Race condition in devfs upgrades reader to writer incidentally and causes panic

@@ -18,10 +18,11 @@
  *
  * CDDL HEADER END
  */
 
 /*
+ * Copyright 2014 Coraid, Inc.
  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 /*
  * This file implements /dev filesystem operations for non-global

@@ -653,55 +654,89 @@
                     name, name);
         }
 }
 
 /*
- * Build directory vnodes based on the profile and the global
- * dev instance.
+ * Return True if directory cache is out of date and should be updated.
  */
-void
-prof_filldir(struct sdev_node *ddv)
+static boolean_t
+prof_dev_needupdate(sdev_node_t *ddv)
 {
-        int firsttime = 1;
-        struct sdev_node *gdir = ddv->sdev_origin;
+        sdev_node_t *gdir = ddv->sdev_origin;
 
-        ASSERT(RW_READ_HELD(&ddv->sdev_contents));
+        /*
+         * Caller can have either reader or writer lock
+         */
+        ASSERT(RW_LOCK_HELD(&ddv->sdev_contents));
 
         /*
          * We need to rebuild the directory content if
-         * - SDEV_BUILD is set
-         * - The device tree generation number has changed
+         * - ddv is not in a SDEV_ZOMBIE state
+         * - SDEV_BUILD is set OR
+         * - The device tree generation number has changed OR
          * - The corresponding /dev namespace has been updated
          */
-check_build:
-        if ((ddv->sdev_flags & SDEV_BUILD) == 0 &&
-            ddv->sdev_devtree_gen == devtree_gen &&
-            (gdir == NULL || ddv->sdev_ldir_gen
-            == gdir->sdev_gdir_gen))
-                return;         /* already up to date */
+        return ((ddv->sdev_state != SDEV_ZOMBIE) &&
+            (((ddv->sdev_flags & SDEV_BUILD) != 0) ||
+            (ddv->sdev_devtree_gen != devtree_gen) ||
+            ((gdir != NULL) &&
+            (ddv->sdev_ldir_gen != gdir->sdev_gdir_gen))));
+}
 
-        /* We may have become a zombie (across a try) */
-        if (ddv->sdev_state == SDEV_ZOMBIE)
-                return;
+/*
+ * Build directory vnodes based on the profile and the global
+ * dev instance.
+ */
+void
+prof_filldir(sdev_node_t *ddv)
+{
+        sdev_node_t *gdir;
 
-        if (firsttime && rw_tryupgrade(&ddv->sdev_contents) == 0) {
+        ASSERT(RW_READ_HELD(&ddv->sdev_contents));
+
+        if (!prof_dev_needupdate(ddv)) {
+                ASSERT(RW_READ_HELD(&ddv->sdev_contents));
+                return;
+        }
+        /*
+         * Upgrade to writer lock
+         */
+        if (rw_tryupgrade(&ddv->sdev_contents) == 0) {
+                /*
+                 * We need to drop the read lock and re-acquire it as a
+                 * write lock. While we do this the condition may change so we
+                 * need to re-check condition.
+                 * NOTE: if device becomes a zombie, prof_dev_needupdate returns
+                 * false, so nothing we will just return in this case.
+                 */
                 rw_exit(&ddv->sdev_contents);
-                firsttime = 0;
                 rw_enter(&ddv->sdev_contents, RW_WRITER);
-                goto check_build;
+                if (!prof_dev_needupdate(ddv)) {
+                        /* Downgrade back to the read lock before returning */
+                        rw_downgrade(&ddv->sdev_contents);
+                        return;
         }
+        }
+        /* At this point we should have a write lock */
+        ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));
+
         sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n",
             ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen));
-        if (gdir)
+
+        gdir = ddv->sdev_origin;
+
+        if (gdir != NULL)
                 sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n",
                     ddv->sdev_path, ddv->sdev_ldir_gen,
                     gdir->sdev_gdir_gen));
 
         /* update flags and generation number so next filldir is quick */
+        if ((ddv->sdev_flags & SDEV_BUILD) == SDEV_BUILD) {
         ddv->sdev_flags &= ~SDEV_BUILD;
+        }
         ddv->sdev_devtree_gen = devtree_gen;
-        if (gdir)
+        if (gdir != NULL)
                 ddv->sdev_ldir_gen = gdir->sdev_gdir_gen;
 
         prof_make_symlinks(ddv);
         prof_make_maps(ddv);
         prof_make_names(ddv);