Print this page
NEX-9755 Race in libshare can cause zfs set sharenfs to fail
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>

@@ -51,12 +51,16 @@
 
 struct sa_proto_plugin *sap_proto_list;
 
 static struct sa_proto_handle sa_proto_handle;
 
+extern mutex_t sa_global_lock;
+
 void proto_plugin_fini();
 
+static void proto_plugin_fini_impl(boolean_t);
+
 /*
  * Returns true if name is "." or "..", otherwise returns false.
  */
 static boolean_t
 proto_is_dot_or_dotdot(const char *name)

@@ -93,16 +97,22 @@
         struct dirent *dent;
         int ret = SA_OK;
         struct stat st;
         char isa[MAXISALEN];
 
+        assert(MUTEX_HELD(&sa_global_lock));
+
 #if defined(_LP64)
         if (sysinfo(SI_ARCHITECTURE_64, isa, MAXISALEN) == -1)
                 isa[0] = '\0';
 #else
         isa[0] = '\0';
 #endif
+        if (sap_proto_list != NULL && sa_proto_handle.sa_proto != NULL) {
+                sa_proto_handle.sa_ref_count++;
+                return (SA_OK);
+        }
 
         if ((dir = opendir(SA_LIB_DIR)) == NULL)
                 return (SA_OK);
 
         while ((dent = readdir(dir)) != NULL) {

@@ -153,11 +163,11 @@
                 sap_proto_list = proto;
         }
 
         (void) closedir(dir);
 
-        if (num_protos != 0) {
+        if (num_protos != 0 && sa_proto_handle.sa_proto == NULL) {
                 sa_proto_handle.sa_proto =
                     (char **)calloc(num_protos, sizeof (char *));
                 sa_proto_handle.sa_ops =
                     (struct sa_plugin_ops **)calloc(num_protos,
                     sizeof (struct sa_plugin_ops *));

@@ -192,12 +202,16 @@
         }
 
         /*
          * There was an error, so cleanup prior to return of failure.
          */
-        if (ret != SA_OK)
-                proto_plugin_fini();
+        if (ret != SA_OK) {
+                proto_plugin_fini_impl(B_TRUE);
+        } else {
+                assert(sa_proto_handle.sa_ref_count >= 0);
+                sa_proto_handle.sa_ref_count++;
+        }
 
         return (ret);
 }
 
 /*

@@ -207,13 +221,35 @@
  */
 
 void
 proto_plugin_fini()
 {
+        assert(MUTEX_HELD(&sa_global_lock));
+
+        proto_plugin_fini_impl(B_FALSE);
+}
+
+static void
+proto_plugin_fini_impl(boolean_t forcefini)
+{
         struct sa_proto_plugin *p;
 
+        assert(MUTEX_HELD(&sa_global_lock));
+
+        sa_proto_handle.sa_ref_count--;
         /*
+         * If another thread has a reference to the proto list,
+         * we don't want to clear it out while they're using it
+         * or find_protocol() could fail.
+         */
+        if (!forcefini && sa_proto_handle.sa_ref_count > 0) {
+                return;
+        } else {
+                sa_proto_handle.sa_ref_count = 0;
+        }
+
+        /*
          * Protocols may call this framework during _fini
          * (the smbfs plugin is known to do this) so do
          * two passes: 1st call _fini; 2nd free, dlclose.
          */
         for (p = sap_proto_list; p != NULL; p = p->plugin_next)

@@ -224,10 +260,12 @@
 
                 if (p->plugin_handle != NULL)
                         (void) dlclose(p->plugin_handle);
                 free(p);
         }
+        if (sap_proto_list != NULL)
+                sap_proto_list = NULL;
         if (sa_proto_handle.sa_ops != NULL) {
                 free(sa_proto_handle.sa_ops);
                 sa_proto_handle.sa_ops = NULL;
         }
         if (sa_proto_handle.sa_proto != NULL) {

@@ -247,11 +285,10 @@
 static struct sa_plugin_ops *
 find_protocol(char *proto)
 {
         int i;
         struct sa_plugin_ops *ops = NULL;
-        extern mutex_t sa_global_lock;
 
         (void) mutex_lock(&sa_global_lock);
         if (proto != NULL) {
                 for (i = 0; i < sa_proto_handle.sa_num_proto; i++) {
                         if (strcmp(proto, sa_proto_handle.sa_proto[i]) == 0) {