--- old/usr/src/lib/libc/port/locale/localeimpl.c	Sun Jun 29 11:51:31 2014
+++ new/usr/src/lib/libc/port/locale/localeimpl.c	Sun Jun 29 11:51:31 2014
@@ -69,14 +69,17 @@
  * As a consequence of this, applications that iterate over all possible
  * locales will burn through a lot of virtual memory, but we find such
  * applications rare.  (locale -a might be an exception, but it is short lived.)
+ *
+ * Category data is never released (although enclosing locale objects might be),
+ * in order to guarantee thread-safety.  Calling freelocale() on an object
+ * while it is in use by another thread is a programmer error (use-after-free)
+ * and we don't bother to note it further.
+ *
+ * Locale objects (global locales) established by setlocale() are also
+ * never freed (for MT safety), but we will save previous locale objects
+ * and reuse them when we can.
  */
 
-/*
- * NB: Each of the structures listed herein should have the refcnt
- * set to -1, to ensure that posix locale information is never ever freed,
- * even when changing the global locale.
- */
-
 typedef struct locdata *(*loadfn_t)(const char *);
 
 static const loadfn_t loaders[LC_ALL] = {
@@ -143,35 +146,11 @@
 static const char *get_locale_env(int);
 static struct locdata *locdata_get(int, const const char *);
 static struct locdata *locdata_get_cache(int, const char *);
-static void locdata_set_cache(int, struct locdata *);
 
 /*
  * Some utility routines.
  */
-struct locdata *
-__locdata_hold(struct locdata *ld)
-{
-	if (ld != NULL && ld->l_refcnt != (uint32_t)-1)
-		atomic_inc_32(&ld->l_refcnt);
-	return (ld);
-}
 
-void
-__locdata_release(struct locdata *ld)
-{
-	if (ld->l_refcnt == (uint32_t)-1)
-		return;
-
-	if (atomic_dec_32_nv(&ld->l_refcnt) == 0) {
-		for (int i = 0; i < NLOCDATA; i++)
-			free(ld->l_data[i]);
-		if (ld->l_map && ld->l_map_len) {
-			(void) munmap(ld->l_map, ld->l_map_len);
-		}
-		free(ld);
-	}
-}
-
 struct locdata *
 __locdata_alloc(const char *name, size_t memsz)
 {
@@ -186,12 +165,25 @@
 		return (NULL);
 	}
 	(void) strlcpy(ldata->l_lname, name, sizeof (ldata->l_lname));
-	ldata->l_refcnt = 1;
 
 	return (ldata);
 }
 
 /*
+ * Normally we never free locale data truly, but if we failed to load it
+ * for some reason, this routine is used to cleanup the partial mess.
+ */
+void
+__locdata_free(struct locdata *ldata)
+{
+	for (int i = 0; i < NLOCDATA; i++)
+		free(ldata->l_data[i]);
+	if (ldata->l_map != NULL && ldata->l_map_len)
+		(void) munmap(ldata->l_map, ldata->l_map_len);
+	free(ldata);
+}
+
+/*
  * It turns out that for performance reasons we would really like to
  * cache the most recently referenced locale data to avoid wasteful
  * loading from files.
@@ -198,6 +190,7 @@
  */
 
 static struct locdata *cache_data[LC_ALL];
+static struct locdata *cat_data[LC_ALL];
 static mutex_t cache_lock = DEFAULTMUTEX;
 
 /*
@@ -214,40 +207,66 @@
 	if (category < 0 || category >= LC_ALL)
 		return (NULL);
 
+	/* Try cache first. */
 	lmutex_lock(&cache_lock);
-	if ((loc = cache_data[category]) != NULL) {
-		if (strcmp(locname, loc->l_lname) == 0) {
-			loc = __locdata_hold(loc);
-		} else {
-			loc = NULL;
-		}
+	loc = cache_data[category];
+
+	if ((loc != NULL) && (strcmp(loc->l_lname, locname) == 0)) {
+		lmutex_unlock(&cache_lock);
+		return (loc);
 	}
-	lmutex_unlock(&cache_lock);
-	return (loc);
-}
 
-/*
- * Set the cache for the category to specific content.  An additional hold
- * is taken for the data while it is in the cache, so the caller may drop
- * its own hold once this is complete.  Also, releases the hold on any
- * previously cached data.
- */
-static void
-locdata_set_cache(int category, struct locdata *loc)
-{
-	struct locdata *old;
+	/*
+	 * Failing that try previously loaded locales (linear search) --
+	 * this could be optimized to a hash, but its unlikely that a single
+	 * application will ever need to work with more than a few locales.
+	 */
+		for (loc = cat_data[category]; loc != NULL; loc = loc->l_next) {
+			if (strcmp(locname, loc->l_lname) == 0) {
+				break;
+			}
+		}
 
-	if (category < 0 || category >= LC_ALL)
-		return;
+		/*
+		 * Finally, if we still don't have one, try loading the locale
+		 * data from the actual on-disk data.
+		 *
+		 * We drop the lock (libc wants to ensure no internal locks
+		 * are held when we call other routines required to read from
+		 * files, allocate memory, etc.)  There is a small race here,
+		 * but the consequences of the race are benign -- if multiple
+		 * threads hit this at precisely the same point, we could
+		 * wind up with duplicates of the locale data in the cache.
+		 *
+		 * This wastes the memory for an extra copy of the locale
+		 * data, but there is no further harm beyond that.  Its not
+		 * worth the effort to recode this to something "safe"
+		 * (which would require rescanning the list, etc.), given
+		 * that this race will probably never actually occur.
+		 */
+		if (loc == NULL) {
+			lmutex_unlock(&cache_lock);
+			loc = (*loaders[category])(locname);
+			lmutex_lock(&cache_lock);
+			(void) strlcpy(loc->l_lname, locname,
+			    sizeof (loc->l_lname));
+		}
 
-	lmutex_lock(&cache_lock);
-	old = cache_data[category];
-	cache_data[category] = __locdata_hold(loc);
-	lmutex_unlock(&cache_lock);
+	/*
+	 * Assuming we got one, update the cache, and stick us on the list
+	 * of loaded locale data.  We insert into the head (more recent
+	 * use is likely to win.)
+	 */
+	if (loc != NULL) {
+		cache_data[category] = loc;
+		if (loc->l_next == NULL) {
+			loc->l_next = cat_data[category];
+			cat_data[category] = loc;
+		}
+	}
 
-	/* drop our reference on the old data */
-	if (old)
-		__locdata_release(old);
+	lmutex_unlock(&cache_lock);
+	return (loc);
 }
 
 /*
@@ -258,7 +277,6 @@
 static struct locdata *
 locdata_get(int category, const char *locname)
 {
-	struct locdata *ldata;
 	char scratch[ENCODING_LEN + 1];
 	char *slash;
 	int cnt;
@@ -290,18 +308,9 @@
 	}
 
 	if ((strcmp(locname, "C") == 0) || (strcmp(locname, "POSIX") == 0))
-		return (__locdata_hold(posix_locale.locdata[category]));
+		return (posix_locale.locdata[category]);
 
-	ldata = locdata_get_cache(category, locname);
-	if (ldata != NULL)
-		return (ldata);
-
-	/* Otherwise load it */
-	ldata = (*loaders[category])(locname);
-	if (ldata != NULL) {
-		locdata_set_cache(category, ldata);
-	}
-	return (ldata);
+	return (locdata_get_cache(category, locname));
 }
 
 /* tsd destructor */
@@ -368,8 +377,12 @@
 	if (loc == NULL) {
 		return (NULL);
 	}
+	if (src == NULL) {
+		/* illumos extension: POSIX says LC_GLOBAL_LOCALE here */
+		src = ___global_locale;
+	}
 	for (i = 0; i < LC_ALL; i++) {
-		loc->locdata[i] = __locdata_hold(src->locdata[i]);
+		loc->locdata[i] = src->locdata[i];
 		loc->loaded[i] = 0;
 	}
 	loc->collate = loc->locdata[LC_COLLATE]->l_data[0];
@@ -385,10 +398,12 @@
 void
 freelocale(locale_t loc)
 {
-	int i;
-	for (i = 0; i < LC_ALL; i++)
-		__locdata_release(loc->locdata[i]);
-	if (loc != &posix_locale)
+	/*
+	 * We take extra care never to free a saved locale created by
+	 * setlocale().  This shouldn't be strictly necessary, but a little
+	 * extra safety doesn't hurt here.
+	 */
+	if ((loc != &posix_locale) && (loc->next == NULL))
 		free(loc);
 }
 
@@ -402,7 +417,15 @@
 		errno = EINVAL;
 		return (NULL);
 	}
-	loc = duplocale(base != NULL ? base : ___global_locale);
+	/*
+	 * Technically passing LC_GLOBAL_LOCALE here is illegal,
+	 * but we allow it.
+	 */
+	if (base == NULL || base == ___global_locale) {
+		loc = duplocale(___global_locale);
+	} else {
+		loc = base;
+	}
 	if (loc == NULL) {
 		return (NULL);
 	}
@@ -421,12 +444,8 @@
 			errno = e;
 			return (NULL);
 		}
-		__locdata_release(loc->locdata[i]);
 		loc->locdata[i] = ldata;
 	}
-	if (base && base != ___global_locale) {
-		freelocale(base);
-	}
 	loc->collate = loc->locdata[LC_COLLATE]->l_data[0];
 	loc->ctype = loc->locdata[LC_CTYPE]->l_data[0];
 	loc->runelocale = loc->locdata[LC_CTYPE]->l_data[1];
