| // Internal functions to obtain the current locales. |
| |
| #include "third_party/glibc_locales/library/current_locale.h" |
| |
| #include <locale.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "third_party/glibc_locales/common/langinfo.h" |
| #include "third_party/glibc_locales/common/localeinfo.h" |
| #include "third_party/glibc_locales/glibc_locales.h" |
| #include "third_party/glibc_locales/locale_archive.h" |
| |
| // We store the current locale values in thread-local static variables. This |
| // assumes that users never rely on setlocale() to change the locale that is |
| // seen by a thread that already exists at the time of the call. Doing so |
| // would require explicit synchronization to avoid data races (glibc is not |
| // quite thread-safe for a locale-aware function being called at the same time |
| // as setlocale()), and supporting it here would add substantial complexity. |
| __thread static struct google_locale_struct global_locale; |
| #define global_ctype_data (global_locale.category_data[GOOGLE_LC_CTYPE]) |
| #define global_collate_data (global_locale.category_data[GOOGLE_LC_COLLATE]) |
| |
| // Pointer to the locale currently in use. This will be the global_locale |
| // object unless google_uselocale() has been called to set a thread-local |
| // locale, in which case this will point to that (user-owned) locale. |
| __thread static google_locale_t current_locale = NULL; |
| |
| // If we have a locale passed in through uselocale(). we stash it here. |
| void google_set_current_locales(google_locale_t locale) { |
| // If we've been given a locale, use it. |
| if (locale != NULL) { |
| current_locale = locale; |
| return; |
| } |
| |
| // Return if we've been called from setlocale() and this is a no-op |
| // because we have a thread-local locale overriding the setlocale state. |
| if (uselocale(NULL) != LC_GLOBAL_LOCALE) { |
| return; |
| } |
| |
| // If we got here, we need to set our locales to match glibc's global locale. |
| // Get the current names from glibc. |
| char *ctype_name = setlocale(LC_CTYPE, NULL); |
| char *collate_name = setlocale(LC_COLLATE, NULL); |
| |
| // Adjust the current pointer to use the global locale. |
| current_locale = &global_locale; |
| |
| // Replace the locale components in global_locale, if needed. |
| const char *archive_data = 0; |
| struct locrecent *locale_data = 0; |
| |
| if (global_ctype_data == NULL || |
| strcmp(global_ctype_data->name, ctype_name)) { |
| archive_data = locale_archive_create()->data; |
| locale_data = google_find_locale_data(archive_data, ctype_name); |
| |
| if (locale_data == NULL) return; |
| |
| const void *addr = |
| archive_data + locale_data->record[GOOGLE_LC_CTYPE].offset; |
| size_t len = locale_data->record[GOOGLE_LC_CTYPE].len; |
| |
| if (global_ctype_data) free(global_ctype_data); |
| global_ctype_data = |
| google_load_locale_data(ctype_name, GOOGLE_LC_CTYPE, addr, len); |
| |
| /* Update the special members. */ |
| if (global_locale.category_data[GOOGLE_LC_CTYPE] != NULL) { |
| union locale_data_value *ctypes = |
| global_locale.category_data[GOOGLE_LC_CTYPE]->values; |
| global_locale.ctype_b = |
| (const unsigned short int *) |
| ctypes[GOOGLE_NL_ITEM_INDEX(GOOGLE_NL_CTYPE_CLASS)] |
| .string + |
| 128; |
| global_locale.ctype_tolower = |
| (const int *)ctypes[GOOGLE_NL_ITEM_INDEX(GOOGLE_NL_CTYPE_TOLOWER)] |
| .string + |
| 128; |
| global_locale.ctype_toupper = |
| (const int *)ctypes[GOOGLE_NL_ITEM_INDEX(GOOGLE_NL_CTYPE_TOUPPER)] |
| .string + |
| 128; |
| } else { |
| global_locale.ctype_b = NULL; |
| global_locale.ctype_tolower = NULL; |
| global_locale.ctype_toupper = NULL; |
| } |
| } |
| |
| if (global_collate_data == NULL || |
| strcmp(global_collate_data->name, collate_name)) { |
| // If we've already found the relevant data for the CTYPE component, |
| // re-use that rather than searching for it again. |
| if (!archive_data) archive_data = locale_archive_create()->data; |
| if (!locale_data || strcmp(ctype_name, collate_name)) |
| locale_data = google_find_locale_data(archive_data, collate_name); |
| |
| if (locale_data == NULL) return; |
| |
| const void *addr = |
| archive_data + locale_data->record[GOOGLE_LC_COLLATE].offset; |
| size_t len = locale_data->record[GOOGLE_LC_COLLATE].len; |
| |
| if (global_collate_data) free(global_collate_data); |
| global_collate_data = |
| google_load_locale_data(collate_name, GOOGLE_LC_COLLATE, addr, len); |
| } |
| } |
| |
| google_locale_t google_get_current_locale() { |
| if (global_ctype_data == NULL || global_collate_data == NULL) |
| google_set_current_locales(NULL); |
| |
| return current_locale; |
| } |