Print this page
Thread safety fixes.
*** 67,84 ****
* 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,
--- 67,87 ----
* 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.)
+ *
+ * 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.
*/
typedef struct locdata *(*loadfn_t)(const char *);
static const loadfn_t loaders[LC_ALL] = {
__lc_ctype_load,
__lc_numeric_load,
*** 141,179 ****
* 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;
--- 144,158 ----
*** 184,205 ****
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
--- 163,198 ----
free(ldata);
errno = ENOMEM;
return (NULL);
}
(void) strlcpy(ldata->l_lname, name, sizeof (ldata->l_lname));
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.
*/
static struct locdata *cache_data[LC_ALL];
+ static struct locdata *cat_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
*** 212,255 ****
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
--- 205,274 ----
struct locdata *loc;
if (category < 0 || category >= LC_ALL)
return (NULL);
+ /* Try cache first. */
lmutex_lock(&cache_lock);
! loc = cache_data[category];
!
! if ((loc != NULL) && (strcmp(loc->l_lname, locname) == 0)) {
! lmutex_unlock(&cache_lock);
! return (loc);
! }
!
! /*
! * 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;
}
}
+
+ /*
+ * 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));
! }
! /*
! * 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;
! }
! }
lmutex_unlock(&cache_lock);
! return (loc);
}
/*
* Routine to get the locdata for a given category and locale.
* This includes retrieving it from cache, retrieving it from
*** 256,266 ****
* 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;
--- 275,284 ----
*** 288,309 ****
(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)
--- 306,318 ----
(void) strlcpy(scratch, locname, len);
locname = scratch;
}
if ((strcmp(locname, "C") == 0) || (strcmp(locname, "POSIX") == 0))
! return (posix_locale.locdata[category]);
! return (locdata_get_cache(category, locname));
}
/* tsd destructor */
static void
freelocptr(void *arg)
*** 366,377 ****
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];
--- 375,390 ----
loc = calloc(1, sizeof (*loc));
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] = 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];
*** 383,396 ****
}
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)
--- 396,411 ----
}
void
freelocale(locale_t loc)
{
! /*
! * 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);
}
locale_t
newlocale(int catmask, const char *locname, locale_t base)
*** 400,410 ****
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++) {
--- 415,433 ----
if (catmask & ~(LC_ALL_MASK)) {
errno = EINVAL;
return (NULL);
}
! /*
! * 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);
}
for (i = 0; i < LC_ALL; i++) {
*** 419,434 ****
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];
--- 442,453 ----