diff options
author | Bruno Haible <bruno@clisp.org> | 2018-10-14 17:03:01 +0200 |
---|---|---|
committer | Bruno Haible <bruno@clisp.org> | 2018-10-14 17:05:28 +0200 |
commit | 67c16dcba0e7a0107d39f9bff77f0c9c6d1b5a21 (patch) | |
tree | 81c6e9dcc9b314e810d616be1820c9a6b5358520 /lib/localename.c | |
parent | b159aa5da7e1aa7abeb2f77ba644aa164d25a46d (diff) | |
download | gnulib-67c16dcba0e7a0107d39f9bff77f0c9c6d1b5a21.tar.gz |
localename: Add support for per-thread locales on Solaris 11.4.
* lib/locale.in.h (newlocale, freelocale): New declarations.
(duplocale): Declare also when the 'localename' module requests it.
* lib/localename.c (struniq_hash_node): Renamed from hash_node.
(STRUNIQ_HASH_TABLE_SIZE): Renamed from HASH_TABLE_SIZE.
(struniq): Update.
(struct locale_categories_names, struct locale_hash_node): New types.
(LOCALE_HASH_TABLE_SIZE): New constant.
(locale_hash_table, locale_lock): New variables.
(pointer_hash, get_locale_t_name): New functions.
(newlocale, duplocale, freelocale): New overridden functions.
(gl_locale_name_thread_unsafe): Use get_locale_t_name.
* m4/intlsolaris.m4: New file.
* m4/localename.m4 (gl_LOCALENAME): Require gl_LOCALE_H_DEFAULTS. Invoke
gt_INTL_SOLARIS. Set HAVE_NEWLOCALE, HAVE_DUPLOCALE, HAVE_FREELOCALE,
REPLACE_NEWLOCALE, REPLACE_DUPLOCALE, REPLACE_FREELOCALE.
* m4/locale_h.m4 (gl_LOCALE_H): Test whether newlocale, freelocale are
declared.
(gl_LOCALE_H_DEFAULTS): Initialize GNULIB_LOCALENAME, HAVE_NEWLOCALE,
HAVE_FREELOCALE, REPLACE_NEWLOCALE, REPLACE_FREELOCALE.
* modules/locale (Makefile.am): Substitute GNULIB_LOCALENAME,
HAVE_NEWLOCALE, HAVE_FREELOCALE, REPLACE_NEWLOCALE, REPLACE_FREELOCALE.
* modules/localename (Files): Add intlsolaris.m4.
(Depends-on): Add 'locale'.
(configure.ac): Invoke gl_LOCALE_MODULE_INDICATOR.
* tests/test-locale-c++.cc (newlocale, freelocale): Prepare for checking
the signatures.
Diffstat (limited to 'lib/localename.c')
-rw-r--r-- | lib/localename.c | 441 |
1 files changed, 431 insertions, 10 deletions
diff --git a/lib/localename.c b/lib/localename.c index f9f9cc9d84..93fee9b920 100644 --- a/lib/localename.c +++ b/lib/localename.c @@ -50,6 +50,10 @@ /* Solaris >= 12. */ extern char * getlocalename_l(int, locale_t); # endif +# if HAVE_NAMELESS_LOCALES +# include <errno.h> +# include <stdint.h> +# endif #endif #if HAVE_CFLOCALECOPYCURRENT || HAVE_CFPREFERENCESCOPYAPPVALUE @@ -2615,7 +2619,7 @@ get_lcid (const char *locale_name) #endif -#if HAVE_USELOCALE /* glibc, Mac OS X, Solaris 11 OpenIndiana, or Solaris 12 */ +#if HAVE_USELOCALE /* glibc, Mac OS X, Solaris 11 OpenIndiana, or Solaris >= 11.4 */ /* Simple hash set of strings. We don't want to drag in lots of hash table code here. */ @@ -2641,14 +2645,14 @@ string_hash (const void *x) simultaneously, but only one thread can insert into it at the same time. */ /* A node in a hash bucket collision list. */ -struct hash_node +struct struniq_hash_node { - struct hash_node * volatile next; + struct struniq_hash_node * volatile next; char contents[FLEXIBLE_ARRAY_MEMBER]; }; -# define HASH_TABLE_SIZE 257 -static struct hash_node * volatile struniq_hash_table[HASH_TABLE_SIZE] +# define STRUNIQ_HASH_TABLE_SIZE 257 +static struct struniq_hash_node * volatile struniq_hash_table[STRUNIQ_HASH_TABLE_SIZE] /* = { NULL, ..., NULL } */; /* This lock protects the struniq_hash_table against multiple simultaneous @@ -2661,17 +2665,17 @@ static const char * struniq (const char *string) { size_t hashcode = string_hash (string); - size_t slot = hashcode % HASH_TABLE_SIZE; + size_t slot = hashcode % STRUNIQ_HASH_TABLE_SIZE; size_t size; - struct hash_node *new_node; - struct hash_node *p; + struct struniq_hash_node *new_node; + struct struniq_hash_node *p; for (p = struniq_hash_table[slot]; p != NULL; p = p->next) if (strcmp (p->contents, string) == 0) return p->contents; size = strlen (string) + 1; new_node = - (struct hash_node *) - malloc (FLEXSIZEOF (struct hash_node, contents, size)); + (struct struniq_hash_node *) + malloc (FLEXSIZEOF (struct struniq_hash_node, contents, size)); if (new_node == NULL) /* Out of memory. Return a statically allocated string. */ return "C"; @@ -2700,6 +2704,421 @@ struniq (const char *string) #endif +#if HAVE_USELOCALE && HAVE_NAMELESS_LOCALES /* Solaris >= 11.4 */ + +/* The 'locale_t' object does not contain the names of the locale categories. + We have to associate them with the object through a hash table. */ + +struct locale_categories_names + { + /* Locale category -> name (allocated with indefinite extent). */ + const char *category_name[6]; + }; + +/* A hash function for pointers. */ +static size_t _GL_ATTRIBUTE_CONST +pointer_hash (const void *x) +{ + uintptr_t p = (uintptr_t) x; + size_t h = ((p % 4177) << 12) + ((p % 79) << 6) + (p % 61); + return h; +} + +/* A hash table of fixed size. Multiple threads can access it read-only + simultaneously, but only one thread can insert into it or remove from it + at the same time. */ + +/* A node in a hash bucket collision list. */ +struct locale_hash_node + { + struct locale_hash_node *next; + locale_t locale; + struct locale_categories_names names; + }; + +# define LOCALE_HASH_TABLE_SIZE 101 +static struct locale_hash_node * locale_hash_table[LOCALE_HASH_TABLE_SIZE] + /* = { NULL, ..., NULL } */; + +/* This lock protects the locale_hash_table against multiple simultaneous + accesses (except that multiple simultaneous read accesses are allowed). */ + +gl_rwlock_define_initialized(static, locale_lock) + +/* Returns the name of a given locale category in a given locale_t object, + allocated as a string with indefinite extent. */ +static const char * +get_locale_t_name (int category, locale_t locale) +{ + if (locale == LC_GLOBAL_LOCALE) + { + /* Query the global locale. */ + const char *name = setlocale (category, NULL); + if (name != NULL) + return struniq (name); + else + /* Should normally not happen. */ + return ""; + } + else + { + /* Look up the names in the hash table. */ + size_t hashcode = pointer_hash (locale); + size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE; + /* If the locale was not found in the table, return "". This can + happen if the application uses the original newlocale()/duplocale() + functions instead of the overridden ones. */ + const char *name = ""; + struct locale_hash_node *p; + /* Lock while looking up the hash node. */ + gl_rwlock_rdlock (locale_lock); + for (p = locale_hash_table[slot]; p != NULL; p = p->next) + if (p->locale == locale) + { + name = p->names.category_name[category]; + break; + } + gl_rwlock_unlock (locale_lock); + return name; + } +} + +# if !(defined newlocale && defined duplocale && defined freelocale) +# error "newlocale, duplocale, freelocale not being replaced as expected!" +# endif + +/* newlocale() override. */ +locale_t +newlocale (int category_mask, const char *name, locale_t base) +#undef newlocale +{ + struct locale_categories_names names; + struct locale_hash_node *node; + locale_t result; + + /* Make sure name has indefinite extent. */ + if (((LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK + | LC_MONETARY_MASK | LC_MESSAGES_MASK) + & category_mask) != 0) + name = struniq (name); + + /* Determine the category names of the result. */ + if (((LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK + | LC_MONETARY_MASK | LC_MESSAGES_MASK) + & ~category_mask) == 0) + { + /* Use name, ignore base. */ + int category; + + name = struniq (name); + for (category = 0; category < 6; category++) + names.category_name[category] = name; + } + else + { + /* Use base, possibly also name. */ + if (base == NULL) + { + int category; + + for (category = 0; category < 6; category++) + { + int mask; + + switch (category) + { + case LC_CTYPE: + mask = LC_CTYPE_MASK; + break; + case LC_NUMERIC: + mask = LC_NUMERIC_MASK; + break; + case LC_TIME: + mask = LC_TIME_MASK; + break; + case LC_COLLATE: + mask = LC_COLLATE_MASK; + break; + case LC_MONETARY: + mask = LC_MONETARY_MASK; + break; + case LC_MESSAGES: + mask = LC_MESSAGES_MASK; + break; + default: + abort (); + } + names.category_name[category] = + ((mask & category_mask) != 0 ? name : "C"); + } + } + else if (base == LC_GLOBAL_LOCALE) + { + int category; + + for (category = 0; category < 6; category++) + { + int mask; + + switch (category) + { + case LC_CTYPE: + mask = LC_CTYPE_MASK; + break; + case LC_NUMERIC: + mask = LC_NUMERIC_MASK; + break; + case LC_TIME: + mask = LC_TIME_MASK; + break; + case LC_COLLATE: + mask = LC_COLLATE_MASK; + break; + case LC_MONETARY: + mask = LC_MONETARY_MASK; + break; + case LC_MESSAGES: + mask = LC_MESSAGES_MASK; + break; + default: + abort (); + } + names.category_name[category] = + ((mask & category_mask) != 0 + ? name + : get_locale_t_name (category, LC_GLOBAL_LOCALE)); + } + } + else + { + /* Look up the names of base in the hash table. Like multiple calls + of get_locale_t_name, but locking only once. */ + struct locale_hash_node *p; + int category; + + /* Lock while looking up the hash node. */ + gl_rwlock_rdlock (locale_lock); + for (p = locale_hash_table[pointer_hash (base) % LOCALE_HASH_TABLE_SIZE]; + p != NULL; + p = p->next) + if (p->locale == base) + break; + + for (category = 0; category < 6; category++) + { + int mask; + + switch (category) + { + case LC_CTYPE: + mask = LC_CTYPE_MASK; + break; + case LC_NUMERIC: + mask = LC_NUMERIC_MASK; + break; + case LC_TIME: + mask = LC_TIME_MASK; + break; + case LC_COLLATE: + mask = LC_COLLATE_MASK; + break; + case LC_MONETARY: + mask = LC_MONETARY_MASK; + break; + case LC_MESSAGES: + mask = LC_MESSAGES_MASK; + break; + default: + abort (); + } + names.category_name[category] = + ((mask & category_mask) != 0 + ? name + : (p != NULL ? p->names.category_name[category] : "")); + } + + gl_rwlock_unlock (locale_lock); + } + } + + node = (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node)); + if (node == NULL) + /* errno is set to ENOMEM. */ + return NULL; + + result = newlocale (category_mask, name, base); + if (result == NULL) + { + int saved_errno = errno; + free (node); + errno = saved_errno; + return NULL; + } + + /* Fill the hash node. */ + node->locale = result; + node->names = names; + + /* Insert it in the hash table. */ + { + size_t hashcode = pointer_hash (result); + size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE; + struct locale_hash_node *p; + + /* Lock while inserting the new node. */ + gl_rwlock_wrlock (locale_lock); + for (p = locale_hash_table[slot]; p != NULL; p = p->next) + if (p->locale == result) + { + /* This can happen if the application uses the original freelocale() + function instead of the overridden one. */ + p->names = node->names; + break; + } + if (p == NULL) + { + node->next = locale_hash_table[slot]; + locale_hash_table[slot] = node; + } + + gl_rwlock_unlock (locale_lock); + + if (p != NULL) + free (node); + } + + return result; +} + +/* duplocale() override. */ +locale_t +duplocale (locale_t locale) +#undef duplocale +{ + struct locale_hash_node *node; + locale_t result; + + if (locale == NULL) + /* Invalid argument. */ + abort (); + + node = (struct locale_hash_node *) malloc (sizeof (struct locale_hash_node)); + if (node == NULL) + /* errno is set to ENOMEM. */ + return NULL; + + result = duplocale (locale); + if (result == NULL) + { + int saved_errno = errno; + free (node); + errno = saved_errno; + return NULL; + } + + /* Fill the hash node. */ + node->locale = result; + if (locale == LC_GLOBAL_LOCALE) + { + int category; + + for (category = 0; category < 6; category++) + node->names.category_name[category] = + get_locale_t_name (category, LC_GLOBAL_LOCALE); + + /* Lock before inserting the new node. */ + gl_rwlock_wrlock (locale_lock); + } + else + { + struct locale_hash_node *p; + + /* Lock once, for the lookup and the insertion. */ + gl_rwlock_wrlock (locale_lock); + + for (p = locale_hash_table[pointer_hash (locale) % LOCALE_HASH_TABLE_SIZE]; + p != NULL; + p = p->next) + if (p->locale == locale) + break; + if (p != NULL) + node->names = p->names; + else + { + /* This can happen if the application uses the original + newlocale()/duplocale() functions instead of the overridden + ones. */ + int category; + + for (category = 0; category < 6; category++) + node->names.category_name[category] = ""; + } + } + + /* Insert it in the hash table. */ + { + size_t hashcode = pointer_hash (result); + size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE; + struct locale_hash_node *p; + + for (p = locale_hash_table[slot]; p != NULL; p = p->next) + if (p->locale == result) + { + /* This can happen if the application uses the original freelocale() + function instead of the overridden one. */ + p->names = node->names; + break; + } + if (p == NULL) + { + node->next = locale_hash_table[slot]; + locale_hash_table[slot] = node; + } + + gl_rwlock_unlock (locale_lock); + + if (p != NULL) + free (node); + } + + return result; +} + +/* freelocale() override. */ +void +freelocale (locale_t locale) +#undef freelocale +{ + if (locale == NULL || locale == LC_GLOBAL_LOCALE) + /* Invalid argument. */ + abort (); + + { + size_t hashcode = pointer_hash (locale); + size_t slot = hashcode % LOCALE_HASH_TABLE_SIZE; + struct locale_hash_node *found; + struct locale_hash_node **p; + + found = NULL; + /* Lock while removing the hash node. */ + gl_rwlock_wrlock (locale_lock); + for (p = &locale_hash_table[slot]; *p != NULL; p = &(*p)->next) + if ((*p)->locale == locale) + { + found = *p; + *p = (*p)->next; + break; + } + gl_rwlock_unlock (locale_lock); + free (found); + } + + freelocale (locale); +} + +#endif + + #if defined IN_LIBINTL || HAVE_USELOCALE /* Like gl_locale_name_thread, except that the result is not in storage of @@ -2761,6 +3180,8 @@ gl_locale_name_thread_unsafe (int category, const char *categoryname) # if HAVE_GETLOCALENAME_L /* Solaris >= 12. */ return getlocalename_l (category, thread_locale); +# elif HAVE_NAMELESS_LOCALES + return get_locale_t_name (category, thread_locale); # else /* Solaris 11 OpenIndiana. For the internal structure of locale objects, see |