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


 638         }
 639 
 640         /* Walk nvlist and lookup corresponding device in global inst */
 641         while (nvp = nvlist_next_nvpair(nvl, nvp)) {
 642                 int type;
 643                 rv = nvpair_value_int32(nvp, &type);
 644                 if (rv != 0) {
 645                         cmn_err(CE_WARN, sdev_nvp_val_err,
 646                             rv, nvpair_name(nvp));
 647                         break;
 648                 }
 649                 if (type == PROFILE_TYPE_EXCLUDE)
 650                         continue;
 651                 name = nvpair_name(nvp);
 652                 (void) prof_lookup_globaldev(dir, dir->sdev_origin,
 653                     name, name);
 654         }
 655 }
 656 
 657 /*
 658  * Build directory vnodes based on the profile and the global
 659  * dev instance.
 660  */
 661 void
 662 prof_filldir(struct sdev_node *ddv)
 663 {
 664         int firsttime = 1;
 665         struct sdev_node *gdir = ddv->sdev_origin;
 666 
 667         ASSERT(RW_READ_HELD(&ddv->sdev_contents));



 668 
 669         /*
 670          * We need to rebuild the directory content if
 671          * - SDEV_BUILD is set
 672          * - The device tree generation number has changed

 673          * - The corresponding /dev namespace has been updated
 674          */
 675 check_build:
 676         if ((ddv->sdev_flags & SDEV_BUILD) == 0 &&
 677             ddv->sdev_devtree_gen == devtree_gen &&
 678             (gdir == NULL || ddv->sdev_ldir_gen
 679             == gdir->sdev_gdir_gen))
 680                 return;         /* already up to date */
 681 
 682         /* We may have become a zombie (across a try) */
 683         if (ddv->sdev_state == SDEV_ZOMBIE)
 684                 return;





 685 
 686         if (firsttime && rw_tryupgrade(&ddv->sdev_contents) == 0) {














 687                 rw_exit(&ddv->sdev_contents);
 688                 firsttime = 0;
 689                 rw_enter(&ddv->sdev_contents, RW_WRITER);
 690                 goto check_build;



 691         }




 692         sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n",
 693             ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen));
 694         if (gdir)



 695                 sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n",
 696                     ddv->sdev_path, ddv->sdev_ldir_gen,
 697                     gdir->sdev_gdir_gen));
 698 
 699         /* update flags and generation number so next filldir is quick */

 700         ddv->sdev_flags &= ~SDEV_BUILD;

 701         ddv->sdev_devtree_gen = devtree_gen;
 702         if (gdir)
 703                 ddv->sdev_ldir_gen = gdir->sdev_gdir_gen;
 704 
 705         prof_make_symlinks(ddv);
 706         prof_make_maps(ddv);
 707         prof_make_names(ddv);
 708         rw_downgrade(&ddv->sdev_contents);
 709 }
 710 
 711 /* apply include/exclude pattern to existing directory content */
 712 static void
 713 apply_dir_pattern(struct sdev_node *dir, char *expr, char *pathleft, int type)
 714 {
 715         struct sdev_node *dv;
 716 
 717         /* leaf pattern */
 718         if (pathleft == NULL) {
 719                 if (type == PROFILE_TYPE_INCLUDE)
 720                         return; /* nothing to do for include */
 721                 (void) sdev_cleandir(dir, expr, SDEV_ENFORCE);
 722                 return;




 638         }
 639 
 640         /* Walk nvlist and lookup corresponding device in global inst */
 641         while (nvp = nvlist_next_nvpair(nvl, nvp)) {
 642                 int type;
 643                 rv = nvpair_value_int32(nvp, &type);
 644                 if (rv != 0) {
 645                         cmn_err(CE_WARN, sdev_nvp_val_err,
 646                             rv, nvpair_name(nvp));
 647                         break;
 648                 }
 649                 if (type == PROFILE_TYPE_EXCLUDE)
 650                         continue;
 651                 name = nvpair_name(nvp);
 652                 (void) prof_lookup_globaldev(dir, dir->sdev_origin,
 653                     name, name);
 654         }
 655 }
 656 
 657 /*
 658  * Return True if directory cache is out of date and should be updated.

 659  */
 660 static boolean_t
 661 prof_dev_needupdate(sdev_node_t *ddv)
 662 {
 663         sdev_node_t *gdir = ddv->sdev_origin;

 664 
 665         /*
 666          * Caller can have either reader or writer lock
 667          */
 668         ASSERT(RW_LOCK_HELD(&ddv->sdev_contents));
 669 
 670         /*
 671          * We need to rebuild the directory content if
 672          * - ddv is not in a SDEV_ZOMBIE state
 673          * - SDEV_BUILD is set OR
 674          * - The device tree generation number has changed OR
 675          * - The corresponding /dev namespace has been updated
 676          */
 677         return ((ddv->sdev_state != SDEV_ZOMBIE) &&
 678                 (((ddv->sdev_flags & SDEV_BUILD) != 0) ||
 679                 (ddv->sdev_devtree_gen != devtree_gen) ||
 680                 ((gdir != NULL) &&
 681                 (ddv->sdev_ldir_gen != gdir->sdev_gdir_gen))));
 682 }
 683 
 684 /*
 685  * Build directory vnodes based on the profile and the global
 686  * dev instance.
 687  */
 688 void
 689 prof_filldir(sdev_node_t *ddv)
 690 {
 691         sdev_node_t *gdir;
 692 
 693         ASSERT(RW_READ_HELD(&ddv->sdev_contents));
 694 
 695         if (!prof_dev_needupdate(ddv)) {
 696                 ASSERT(RW_READ_HELD(&ddv->sdev_contents));
 697                 return;
 698         }
 699         /*
 700          * Upgrade to writer lock
 701          */
 702         if (rw_tryupgrade(&ddv->sdev_contents) == 0) {
 703                 /*
 704                  * We need to drop the read lock and re-acquire it as a
 705                  * write lock. While we do this the condition may change so we
 706                  * need to re-check condition
 707                  */
 708                 rw_exit(&ddv->sdev_contents);

 709                 rw_enter(&ddv->sdev_contents, RW_WRITER);
 710                 if (!prof_dev_needupdate(ddv)) {
 711                         /* Downgrade back to the read lock before returning */
 712                         rw_downgrade(&ddv->sdev_contents);
 713                         return;
 714                 }
 715         }
 716         /* At this point we should have a write lock */
 717         ASSERT(RW_WRITE_HELD(&ddv->sdev_contents));
 718 
 719         sdcmn_err10(("devtree_gen (%s): %ld -> %ld\n",
 720             ddv->sdev_path, ddv->sdev_devtree_gen, devtree_gen));
 721 
 722         gdir = ddv->sdev_origin;
 723 
 724         if (gdir != NULL)
 725                 sdcmn_err10(("sdev_dir_gen (%s): %ld -> %ld\n",
 726                     ddv->sdev_path, ddv->sdev_ldir_gen,
 727                     gdir->sdev_gdir_gen));
 728 
 729         /* update flags and generation number so next filldir is quick */
 730         if ((ddv->sdev_flags & SDEV_BUILD) == SDEV_BUILD) {
 731                 ddv->sdev_flags &= ~SDEV_BUILD;
 732         }
 733         ddv->sdev_devtree_gen = devtree_gen;
 734         if (gdir != NULL)
 735                 ddv->sdev_ldir_gen = gdir->sdev_gdir_gen;
 736 
 737         prof_make_symlinks(ddv);
 738         prof_make_maps(ddv);
 739         prof_make_names(ddv);
 740         rw_downgrade(&ddv->sdev_contents);
 741 }
 742 
 743 /* apply include/exclude pattern to existing directory content */
 744 static void
 745 apply_dir_pattern(struct sdev_node *dir, char *expr, char *pathleft, int type)
 746 {
 747         struct sdev_node *dv;
 748 
 749         /* leaf pattern */
 750         if (pathleft == NULL) {
 751                 if (type == PROFILE_TYPE_INCLUDE)
 752                         return; /* nothing to do for include */
 753                 (void) sdev_cleandir(dir, expr, SDEV_ENFORCE);
 754                 return;