| /* Make the global locale minimally POSIX compliant. | |
| Copyright (C) 2025-2026 Free Software Foundation, Inc. | |
| This file is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation; either version 2.1 of the | |
| License, or (at your option) any later version. | |
| This file is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU Lesser General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include <config.h> | |
| /* Specification. */ | |
| #include "setlocale-fixes.h" | |
| #if defined _WIN32 && !defined __CYGWIN__ | |
| # include <stddef.h> | |
| # include <string.h> | |
| /* The system does not store an LC_MESSAGES locale category. Do it here. */ | |
| static char lc_messages_name[64] = "C"; | |
| const char * | |
| setlocale_messages (const char *name) | |
| { | |
| if (name != NULL) | |
| { | |
| lc_messages_name[sizeof (lc_messages_name) - 1] = '\0'; | |
| strncpy (lc_messages_name, name, sizeof (lc_messages_name) - 1); | |
| } | |
| return lc_messages_name; | |
| } | |
| const char * | |
| setlocale_messages_null (void) | |
| { | |
| /* This implementation is multithread-safe, assuming no other thread changes | |
| the LC_MESSAGES locale category. */ | |
| return lc_messages_name; | |
| } | |
| #endif | |
| #if defined __ANDROID__ | |
| # include <stddef.h> | |
| # include <stdlib.h> | |
| # include <locale.h> | |
| # include <string.h> | |
| /* Use the system's setlocale() function, not the gnulib override, here. */ | |
| # undef setlocale | |
| /* Storage for the name of each category of the global locale. | |
| Regarding the size, cf. SETLOCALE_NULL_MAX. */ | |
| static char lc_cat_name[12][256+1] = | |
| { "C", "C", "C", "C", "C", "C", "C", "C", "C", "C", "C", "C" }; | |
| /* Mapping from the valid categories to the indices 0..12-1. */ | |
| #define cat_to_index(cat) ((cat) - ((cat) >= LC_ALL)) | |
| /* Mapping from the indicess 0..12-1 to the valid categories. */ | |
| #define index_to_cat(i) ((i) + ((i) >= LC_ALL)) | |
| /* Storage for the name of the entire global locale. | |
| For mixed locales, use the syntax from glibc: | |
| LC_CTYPE=...;LC_NUMERIC=...;......;LC_IDENTIFICATION=... | |
| Regarding the size, cf. SETLOCALE_NULL_ALL_MAX. */ | |
| static char lc_all_name[148+12*256+1]; | |
| /* Category names. */ | |
| static const char cat_names[12][17+1] = | |
| { | |
| [cat_to_index (LC_CTYPE)] = "LC_CTYPE", | |
| [cat_to_index (LC_NUMERIC)] = "LC_NUMERIC", | |
| [cat_to_index (LC_TIME)] = "LC_TIME", | |
| [cat_to_index (LC_COLLATE)] = "LC_COLLATE", | |
| [cat_to_index (LC_MONETARY)] = "LC_MONETARY", | |
| [cat_to_index (LC_MESSAGES)] = "LC_MESSAGES", | |
| [cat_to_index (LC_PAPER)] = "LC_PAPER", | |
| [cat_to_index (LC_NAME)] = "LC_NAME", | |
| [cat_to_index (LC_ADDRESS)] = "LC_ADDRESS", | |
| [cat_to_index (LC_TELEPHONE)] = "LC_TELEPHONE", | |
| [cat_to_index (LC_MEASUREMENT)] = "LC_MEASUREMENT", | |
| [cat_to_index (LC_IDENTIFICATION)] = "LC_IDENTIFICATION", | |
| }; | |
| /* Combines the contents of all lc_cat_name[], filling lc_all_name. */ | |
| static void | |
| fill_lc_all_name (void) | |
| { | |
| bool all_same = true; | |
| for (size_t i = 1; i < 12; i++) | |
| if (!streq (lc_cat_name[i], lc_cat_name[0])) | |
| { | |
| all_same = false; | |
| break; | |
| } | |
| if (all_same) | |
| /* Produce a simple locale name. */ | |
| strcpy (lc_all_name, lc_cat_name[0]); | |
| else | |
| { | |
| /* Produce a mixed locale name. */ | |
| char *q = lc_all_name; | |
| for (size_t i = 0; i < 12; i++) | |
| { | |
| const char *p = cat_names[i]; | |
| size_t n = strlen (p); | |
| memcpy (q, p, n); q += n; | |
| *q++ = '='; | |
| p = lc_cat_name[i]; | |
| n = strlen (p); | |
| memcpy (q, p, n); q += n; | |
| *q++ = ';'; | |
| } | |
| *--q = '\0'; | |
| } | |
| } | |
| /* Extracts the name of a single category from NAME and stores it in | |
| SINGLE_NAME. CATEGORY must be a valid category name != LC_ALL. | |
| This is basically the opposite of fill_lc_all_name. */ | |
| static int | |
| extract_single_name (char single_name[256+1], int category, const char *name) | |
| { | |
| if (strchr (name, ';') != NULL) | |
| { | |
| /* A mixed locale name. */ | |
| const char *cat_name = cat_names[cat_to_index (category)]; | |
| size_t cat_name_len = strlen (cat_name); | |
| const char *p = name; | |
| for (;;) | |
| { | |
| const char *q = strchr (p, ';'); | |
| if (strncmp (p, cat_name, cat_name_len) == 0 | |
| && p[cat_name_len] == '=') | |
| { | |
| p += cat_name_len + 1; | |
| size_t n = (q == NULL ? strlen (p) : q - p); | |
| if (n >= 256+1) | |
| n = 256; | |
| memcpy (single_name, p, n); | |
| single_name[n] = '\0'; | |
| return 0; | |
| } | |
| if (q == NULL) | |
| break; | |
| p = q + 1; | |
| } | |
| return -1; | |
| } | |
| else | |
| { | |
| /* A simple locale name. */ | |
| size_t n = strlen (name); | |
| if (n >= 256+1) | |
| n = 256; | |
| memcpy (single_name, name, n); | |
| single_name[n] = '\0'; | |
| return 0; | |
| } | |
| } | |
| const char * | |
| setlocale_fixed (int category, const char *name) | |
| { | |
| if (category == LC_ALL) | |
| { | |
| if (name != NULL) | |
| { | |
| char single_name[256+1]; | |
| /* Test whether NAME is valid. */ | |
| for (int i = 12-1; i >= 0; i--) | |
| if (extract_single_name (single_name, index_to_cat (i), name) < 0) | |
| return NULL; | |
| /* Now single_name contains the one for the index 0, | |
| i.e. for LC_CTYPE. */ | |
| if (setlocale (LC_CTYPE, single_name) == NULL) | |
| return NULL; | |
| /* Fill lc_cat_name[]. */ | |
| for (int i = 12-1; i >= 0; i--) | |
| { | |
| if (extract_single_name (single_name, index_to_cat (i), name) < 0) | |
| abort (); | |
| strcpy (lc_cat_name[i], single_name); | |
| } | |
| /* Update lc_all_name. */ | |
| fill_lc_all_name (); | |
| } | |
| return lc_all_name; | |
| } | |
| if (category >= LC_CTYPE && category <= LC_IDENTIFICATION) | |
| { | |
| if (name != NULL) | |
| { | |
| char single_name[256+1]; | |
| /* Test whether NAME is valid. */ | |
| if (extract_single_name (single_name, category, name) < 0) | |
| return NULL; | |
| if (category == LC_CTYPE) | |
| if (setlocale (category, single_name) == NULL) | |
| return NULL; | |
| /* Fill lc_cat_name[]. */ | |
| strcpy (lc_cat_name[cat_to_index (category)], single_name); | |
| /* Update lc_all_name. */ | |
| fill_lc_all_name (); | |
| } | |
| return lc_cat_name[cat_to_index (category)]; | |
| } | |
| return NULL; | |
| } | |
| const char * | |
| setlocale_fixed_null (int category) | |
| { | |
| /* This implementation is multithread-safe, assuming no other thread changes | |
| any locale category. */ | |
| if (category == LC_ALL) | |
| return lc_all_name; | |
| if (category >= LC_CTYPE && category <= LC_IDENTIFICATION) | |
| return lc_cat_name[cat_to_index (category)]; | |
| return NULL; | |
| } | |
| #endif |