--- /dev/null	Tue May 20 07:58:48 2014
+++ new/usr/src/lib/libc/port/locale/localeimpl.c	Tue May 20 07:58:48 2014
@@ -0,0 +1,470 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * This file implements the 2008 newlocale and friends handling.
+ */
+
+#ifndef	_LCONV_C99
+#define	_LCONV_C99
+#endif
+
+#include "lint.h"
+#include <atomic.h>
+#include <locale.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <string.h>
+#include "libc.h"
+#include "mtlib.h"
+#include "tsd.h"
+#include "localeimpl.h"
+#include "lctype.h"
+
+/*
+ * Big Theory of Locales:
+ *
+ * (It is recommended that readers familiarize themselves with the POSIX
+ * 2008 (XPG Issue 7) specifications for locales, first.)
+ *
+ * Historically, we had a bunch of global variables that stored locale
+ * data.  While this worked well, it limited applications to a single locale
+ * at a time.  This doesn't work well in certain server applications.
+ *
+ * Issue 7, X/Open introduced the concept of a locale_t object, along with
+ * versions of functions that can take this object as a parameter, along
+ * with functions to clone and manipulate these locale objects.  The new
+ * functions are named with a _l() suffix.
+ *
+ * Additionally uselocale() is introduced which can change the locale of
+ * of a single thread.  However, setlocale() can still be used to change
+ * the global locale.
+ *
+ * In our implementation, we use libc's TSD to store the locale data that
+ * was previously global.  We still have global data because some applications
+ * have had those global objects compiled into them.  (Such applications will
+ * be unable to benefit from uselocale(), btw.)  The legacy routines are
+ * reimplemented as wrappers that use the appropriate locale object by
+ * calling uselocale().  uselocale() when passed a NULL pointer returns the
+ * thread-specific locale object if one is present, or the global locale
+ * object otherwise.  Note that once the TSD data is set, the only way
+ * to revert to the global locale is to pass the global locale LC_GLOBAL_LOCALE
+ * to uselocale().
+ *
+ * We are careful to minimize performance impact of multiple calls to
+ * uselocale() or setlocale() by using a cache of locale data whenever possible.
+ * 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.)
+ */
+
+/*
+ * 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] = {
+	__lc_ctype_load,
+	__lc_numeric_load,
+	__lc_time_load,
+	__lc_collate_load,
+	__lc_monetary_load,
+	__lc_messages_load,
+};
+
+extern struct lc_monetary lc_monetary_posix;
+extern struct lc_numeric lc_numeric_posix;
+extern struct lc_messages lc_messages_posix;
+extern struct lc_time lc_time_posix;
+extern struct lc_ctype lc_ctype_posix;
+extern struct lc_collate lc_collate_posix;
+
+static struct locale posix_locale = {
+	/* locdata */
+	.locdata = {
+		&__posix_ctype_locdata,
+		&__posix_numeric_locdata,
+		&__posix_time_locdata,
+		&__posix_collate_locdata,
+		&__posix_monetary_locdata,
+		&__posix_messages_locdata,
+	},
+	.ctype = &lc_ctype_posix,
+	.numeric = &lc_numeric_posix,
+	.collate = &lc_collate_posix,
+	.monetary = &lc_monetary_posix,
+	.messages = &lc_messages_posix,
+	.time = &lc_time_posix,
+	.runelocale = &_DefaultRuneLocale,
+};
+
+locale_t __global_locale = &posix_locale;
+
+/*
+ * Category names for getenv()  Note that this was modified
+ * for Solaris.  See <iso/locale_iso.h>.
+ */
+#define	NUM_CATS	7
+static char *categories[7] = {
+	"LC_CTYPE",
+	"LC_NUMERIC",
+	"LC_TIME",
+	"LC_COLLATE",
+	"LC_MONETARY",
+	"LC_MESSAGES",
+	"LC_ALL",
+};
+
+/*
+ * Prototypes.
+ */
+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)
+{
+	struct locdata *ldata;
+
+	if ((ldata = calloc(1, sizeof (*ldata))) == NULL) {
+		return (NULL);
+	}
+	if ((ldata->l_data[0] = calloc(1, memsz)) == NULL) {
+		free(ldata);
+		errno = ENOMEM;
+		return (NULL);
+	}
+	(void) strlcpy(ldata->l_lname, name, sizeof (ldata->l_lname));
+	ldata->l_refcnt = 1;
+
+	return (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.
+ */
+
+static struct locdata *cache_data[LC_ALL];
+static mutex_t cache_lock = DEFAULTMUTEX;
+
+/*
+ * Returns the cached data if the locale name is the same.  If not,
+ * returns NULL (cache miss).  The locdata is returned with a hold on
+ * it, taken on behalf of the caller.  The caller should drop the hold
+ * when it is finished.
+ */
+static struct locdata *
+locdata_get_cache(int category, const char *locname)
+{
+	struct locdata *loc;
+
+	if (category < 0 || category >= LC_ALL)
+		return (NULL);
+
+	lmutex_lock(&cache_lock);
+	if ((loc = cache_data[category]) != NULL) {
+		if (strcmp(locname, loc->l_lname) == 0) {
+			loc = __locdata_hold(loc);
+		} else {
+			loc = NULL;
+		}
+	}
+	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;
+
+	if (category < 0 || category >= LC_ALL)
+		return;
+
+	lmutex_lock(&cache_lock);
+	old = cache_data[category];
+	cache_data[category] = __locdata_hold(loc);
+	lmutex_unlock(&cache_lock);
+
+	/* drop our reference on the old data */
+	if (old)
+		__locdata_release(old);
+}
+
+/*
+ * Routine to get the locdata for a given category and locale.
+ * This includes retrieving it from cache, retrieving it from
+ * a file, etc.
+ */
+static struct locdata *
+locdata_get(int category, const char *locname)
+{
+	struct locdata *ldata;
+	char scratch[ENCODING_LEN + 1];
+	char *slash;
+	int cnt;
+	int len;
+
+	if (locname == NULL || *locname == 0) {
+		locname = get_locale_env(category);
+	}
+
+	/*
+	 * Extract the locale name for the category if it is a composite
+	 * locale.
+	 */
+	if ((slash = strchr(locname, '/')) != NULL) {
+		for (cnt = category; cnt && slash != NULL; cnt--) {
+			locname = slash + 1;
+			slash = strchr(locname, '/');
+		}
+		if (slash) {
+			len = slash - locname;
+			if (len >= sizeof (scratch)) {
+				len = sizeof (scratch);
+			}
+		} else {
+			len = sizeof (scratch);
+		}
+		(void) strlcpy(scratch, locname, len);
+		locname = scratch;
+	}
+
+	if ((strcmp(locname, "C") == 0) || (strcmp(locname, "POSIX") == 0))
+		return (__locdata_hold(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);
+}
+
+/* tsd destructor */
+static void
+freelocptr(void *arg)
+{
+	locale_t *locptr = arg;
+	if (*locptr != NULL)
+		freelocale(*locptr);
+}
+
+static const char *
+get_locale_env(int category)
+{
+	const char *env;
+
+	/* 1. check LC_ALL. */
+	env = getenv(categories[LC_ALL]);
+
+	/* 2. check LC_* */
+	if (env == NULL || *env == '\0')
+		env = getenv(categories[category]);
+
+	/* 3. check LANG */
+	if (env == NULL || *env == '\0')
+		env = getenv("LANG");
+
+	/* 4. if none is set, fall to "C" */
+	if (env == NULL || *env == '\0')
+		env = "C";
+
+	return (env);
+}
+
+
+/*
+ * This routine is exposed via the MB_CUR_MAX macro.  Note that legacy
+ * code will continue to use _ctype[520], but we prefer this function as
+ * it is the only way to get thread-specific information.
+ */
+unsigned char
+__mb_cur_max_l(locale_t loc)
+{
+	return (loc->ctype->lc_max_mblen);
+}
+
+unsigned char
+__mb_cur_max(void)
+{
+	return (__mb_cur_max_l(uselocale(NULL)));
+}
+
+/*
+ * Public interfaces.
+ */
+
+locale_t
+duplocale(locale_t src)
+{
+	locale_t	loc;
+	int		i;
+
+	loc = calloc(1, sizeof (*loc));
+	if (loc == NULL) {
+		return (NULL);
+	}
+	for (i = 0; i < LC_ALL; i++) {
+		loc->locdata[i] = __locdata_hold(src->locdata[i]);
+		loc->loaded[i] = 0;
+	}
+	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];
+	loc->messages = loc->locdata[LC_MESSAGES]->l_data[0];
+	loc->monetary = loc->locdata[LC_MONETARY]->l_data[0];
+	loc->numeric = loc->locdata[LC_NUMERIC]->l_data[0];
+	loc->time = loc->locdata[LC_TIME]->l_data[0];
+	return (loc);
+}
+
+void
+freelocale(locale_t loc)
+{
+	int i;
+	for (i = 0; i < LC_ALL; i++)
+		__locdata_release(loc->locdata[i]);
+	if (loc != &posix_locale)
+		free(loc);
+}
+
+locale_t
+newlocale(int catmask, const char *locname, locale_t base)
+{
+	locale_t loc;
+	int i, e;
+
+	if (catmask & ~(LC_ALL_MASK)) {
+		errno = EINVAL;
+		return (NULL);
+	}
+	loc = duplocale(base != NULL ? base : __global_locale);
+	if (loc == NULL) {
+		return (NULL);
+	}
+
+	for (i = 0; i < LC_ALL; i++) {
+		struct locdata *ldata;
+		loc->loaded[i] = 0;
+		if (((1 << i) & catmask) == 0) {
+			/* Default to base locale if not overriding */
+			continue;
+		}
+		ldata = locdata_get(i, locname);
+		if (ldata == NULL) {
+			e = errno;
+			freelocale(loc);
+			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];
+	loc->messages = loc->locdata[LC_MESSAGES]->l_data[0];
+	loc->monetary = loc->locdata[LC_MONETARY]->l_data[0];
+	loc->numeric = loc->locdata[LC_NUMERIC]->l_data[0];
+	loc->time = loc->locdata[LC_TIME]->l_data[0];
+	return (loc);
+}
+
+locale_t
+uselocale(locale_t loc)
+{
+	locale_t lastloc = __global_locale;
+	locale_t *locptr;
+
+	locptr = tsdalloc(_T_SETLOCALE, sizeof (locale_t), freelocptr);
+	/* Should never occur */
+	if (locptr == NULL) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	if (*locptr != NULL)
+		lastloc = *locptr;
+
+	/* Argument loc is NULL if we are just querying. */
+	if (loc != NULL) {
+		/*
+		 * Set it to LC_GLOBAL_LOCAL to return to using
+		 * the global locale (setlocale).
+		 */
+		if (loc == __global_locale) {
+			*locptr = NULL;
+		} else {
+			/* No validation of the provided locale at present */
+			*locptr = loc;
+		}
+	}
+
+	/*
+	 * The caller is responsible for freeing, of course it would be
+	 * gross error to call freelocale() on a locale object that is still
+	 * in use.
+	 */
+	return (lastloc);
+}
