diff options
Diffstat (limited to 'libc/misc/locale/locale.c')
-rw-r--r-- | libc/misc/locale/locale.c | 255 |
1 files changed, 166 insertions, 89 deletions
diff --git a/libc/misc/locale/locale.c b/libc/misc/locale/locale.c index f3c23f9fb..801258c94 100644 --- a/libc/misc/locale/locale.c +++ b/libc/misc/locale/locale.c @@ -15,6 +15,16 @@ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* Nov. 1, 2002 + * + * Reworked setlocale() return values and locale arg processing to + * be more like glibc. Applications expecting to be able to + * query locale settings should now work... at the cost of almost + * doubling the size of the setlocale object code. + * Fixed a bug in the internal fixed-size-string locale specifier code. + */ + + /* TODO: * Implement the shared mmap code so non-mmu platforms can use this. * Add some basic collate functionality similar to what the previous @@ -36,9 +46,6 @@ #undef CODESET_LIST #define CODESET_LIST (__locale_mmap->codeset_list) -/* TODO: Optional... See below. */ -#define __LOCALE_STRICTER_SETLOCALE - #endif /* __LOCALE_C_ONLY */ /**********************************************************************/ @@ -67,15 +74,90 @@ char *setlocale(int category, register const char *locale) #error locales enabled, but not data other than for C locale! #endif -static unsigned char setlocale_buf[LOCALE_STRING_SIZE]; - - #define LOCALE_NAMES (__locale_mmap->locale_names5) #define LOCALES (__locale_mmap->locales) #define LOCALE_AT_MODIFIERS (__locale_mmap->locale_at_modifiers) #define CATEGORY_NAMES (__locale_mmap->lc_names) static const char posix[] = "POSIX"; +static const char utf8[] = "UTF-8"; + +#ifdef __UCLIBC_MJN3_ONLY__ +#warning REMINDER: redo the MAX_LOCALE_STR stuff... +#endif +#define MAX_LOCALE_STR 256 /* TODO: Only sufficient for current case. */ + +static char hr_locale[MAX_LOCALE_STR]; + +static __inline char *human_readable_locale(int category, const unsigned char *s) +{ + const unsigned char *loc; + char *n; + int i; + + ++s; + + if (category == LC_ALL) { + for (i = 0 ; i < LC_ALL-1 ; i += 2) { + if ((s[i] != s[i+2]) || (s[i+1] != s[i+3])) { + goto SKIP; + } + } + /* All categories the same, so simplify string by using a single + * category. */ + category = LC_CTYPE; + } + + SKIP: + i = (category == LC_ALL) ? 0 : category; + n = hr_locale; + + do { + if ((*s != 0xff) || (s[1] != 0xff)) { + loc = LOCALES + WIDTH_LOCALES * ((((int)(*s & 0x7f)) << 7) + (s[1] & 0x7f)); + if (category == LC_ALL) { + n = stpcpy(n, CATEGORY_NAMES + (int) CATEGORY_NAMES[i]); + *n++ = '='; + } + if (*loc == 0) { + *n++ = 'C'; + *n = 0; + } else { + char at = 0; + memcpy(n, LOCALE_NAMES + 5*((*loc)-1), 5); + if (n[2] != '_') { + at = n[2]; + n[2] = '_'; + } + n += 5; + *n++ = '.'; + if (loc[2] == 2) { + n = stpcpy(n, utf8); + } else if (loc[2] >= 3) { + n = stpcpy(n, CODESET_LIST + (int)(CODESET_LIST[loc[2] - 3])); + } + if (at) { + const char *q; + *n++ = '@'; + q = LOCALE_AT_MODIFIERS; + do { + if (q[1] == at) { + n = stpcpy(n, q+2); + break; + } + q += 2 + *q; + } while (*q); + } + } + *n++ = ';'; + } + s += 2; + } while (++i < category); + + *--n = 0; /* Remove trailing ';' and nul-terminate. */ + assert(n-hr_locale < MAX_LOCALE_STR); + return hr_locale; +} static int find_locale(int category, const char *p, unsigned char *new_locale) { @@ -86,6 +168,10 @@ static int find_locale(int category, const char *p, unsigned char *new_locale) #if defined(LOCALE_AT_MODIFIERS_LENGTH) && 1 /* Support standard locale handling for @-modifiers. */ + +#ifdef __UCLIBC_MJN3_ONLY__ +#warning REMINDER: fix buf size in find_locale +#endif char buf[18]; /* TODO: 7+{max codeset name length} */ const char *q; @@ -104,6 +190,7 @@ static int find_locale(int category, const char *p, unsigned char *new_locale) if (!*s) { return 0; } + assert(q - p < sizeof(buf)); memcpy(buf, p, q-p); buf[q-p] = 0; buf[2] = s[1]; @@ -116,11 +203,11 @@ static int find_locale(int category, const char *p, unsigned char *new_locale) goto FIND_LOCALE; } - if (p[5] == '.') { /* Codeset specified in locale name? */ + if ((strlen(p) > 5) && (p[5] == '.')) { /* Codeset in locale name? */ /* TODO: maybe CODESET_LIST + *s ??? */ /* 7bit is 1, UTF-8 is 2, 8-bit is >= 3 */ codeset = 2; - if (strcmp("UTF-8",p+6) != 0) {/* TODO - fix! */ + if (strcmp(utf8,p+6) != 0) {/* TODO - fix! */ s = CODESET_LIST; do { ++codeset; /* Increment codeset first. */ @@ -146,15 +233,15 @@ static int find_locale(int category, const char *p, unsigned char *new_locale) FIND_LOCALE: /* Find locale row matching name and codeset */ s = LOCALES; - n = 1; + n = 0; do { /* TODO -- do a binary search? */ if ((lang_cult == *s) && ((codeset == s[1]) || (codeset == s[2]))) { i = ((category == LC_ALL) ? 0 : category); s = new_locale + 2*i; do { /* Encode current locale row number. */ - *((unsigned char *) ++s) = (n >> 8) | 0x80; - *((unsigned char *) ++s) = n & 0xff; + *((unsigned char *) ++s) = (n >> 7) | 0x80; + *((unsigned char *) ++s) = (n & 0x7f) | 0x80; } while (++i < category); return i; /* Return non-zero */ @@ -166,12 +253,47 @@ static int find_locale(int category, const char *p, unsigned char *new_locale) return 0; /* Unsupported locale. */ } +static unsigned char *composite_locale(int category, const char *locale, unsigned char *new_locale) +{ + char buf[MAX_LOCALE_STR]; + char *t; + char *e; + int c; + + if (!strchr(locale,'=')) { + if (!find_locale(category, locale, new_locale)) { + return NULL; + } + return new_locale; + } + + if (strlen(locale) >= sizeof(buf)) { + return NULL; + } + stpcpy(buf, locale); + + t = strtok_r(buf, "=", &e); /* This can't fail because of strchr test above. */ + do { + for (c = 0 ; c < LC_ALL ; c++) { /* Find the category... */ + if (!strcmp(CATEGORY_NAMES + (int) CATEGORY_NAMES[c], t)) { + break; + } + } + t = strtok_r(NULL, ";", &e); + if ((category == LC_ALL) || (c == category)) { + if (!t || !find_locale(c, t, new_locale)) { + return NULL; + } + } + } while ((t = strtok_r(NULL, "=", &e)) != NULL); + + return new_locale; +} + char *setlocale(int category, const char *locale) { const unsigned char *p; - unsigned char *s; int i; - unsigned lc_mask; unsigned char new_locale[LOCALE_STRING_SIZE]; if (((unsigned int)(category)) > LC_ALL) { @@ -179,85 +301,41 @@ char *setlocale(int category, const char *locale) return NULL; /* Illegal/unsupported category. */ } - lc_mask = 1 << category; - if (category == LC_ALL) { - --lc_mask; - } + if (locale != NULL) { /* Not just a query... */ + stpcpy(new_locale, CUR_LOCALE_SPEC); /* Start with current. */ - if (!locale) { /* Request for locale category string... */ - DONE: - strcpy(setlocale_buf, CUR_LOCALE_SPEC); -#ifdef __LOCALE_STRICTER_SETLOCALE - /* The standard says you can only use the string returned to restore - * the category (categories) requested. This could be optional. - * See below as well. */ - s = setlocale_buf + 1; - lc_mask |= (1 << LC_ALL); - do { - if (!(lc_mask & 1)) { - /* Encode non-selected locale flag. */ - s[1] = *s = 0xff; - } - s += 2; - } while ((lc_mask >>= 1) > 1); -#endif /* __LOCALE_STRICTER_SETLOCALE */ - return (char *) setlocale_buf; - } - - strcpy(new_locale, CUR_LOCALE_SPEC); /* Start with current. */ - - if (!*locale) { /* locale == "", so check environment. */ - i = ((category == LC_ALL) ? 0 : category); - do { - /* Note: SUSv3 doesn't define a fallback mechanism here. So, - * if LC_ALL is invalid, we do _not_ continue trying the other - * environment vars. */ - if (!(p = getenv("LC_ALL"))) { - if (!(p = getenv(CATEGORY_NAMES + CATEGORY_NAMES[i]))) { - if (!(p = getenv("LANG"))) { - p = posix; + if (!*locale) { /* locale == "", so check environment. */ + i = ((category == LC_ALL) ? 0 : category); + do { + /* Note: SUSv3 doesn't define a fallback mechanism here. So, + * if LC_ALL is invalid, we do _not_ continue trying the other + * environment vars. */ + if (!(p = getenv("LC_ALL"))) { + if (!(p = getenv(CATEGORY_NAMES + CATEGORY_NAMES[i]))) { + if (!(p = getenv("LANG"))) { + p = posix; + } } } - } - /* The user set something... is it valid? */ - /* Note: Since we don't support user-supplied locales and - * alternate paths, we don't need to worry about special - * handling for suid/sgid apps. */ - if (!find_locale(i, p, new_locale)) { - return NULL; - } - } while (++i < category); - } else if (*locale == '#') { /* Previsouly returned value. */ - assert(strlen(locale) == LOCALE_STRING_SIZE - 1); + /* The user set something... is it valid? */ + /* Note: Since we don't support user-supplied locales and + * alternate paths, we don't need to worry about special + * handling for suid/sgid apps. */ + if (!find_locale(i, p, new_locale)) { + return NULL; + } + } while (++i < category); + } else if (!composite_locale(category, locale, new_locale)) { + return NULL; + } - i = ((category == LC_ALL) ? 0 : category); - p = locale + 2*i; - s = new_locale + 2*i; - do { -#ifdef __LOCALE_STRICTER_SETLOCALE - /* Only set categories that were selected in the previous - * return value. Could be optional. See above as well. - * NOTE: This still isn't quite right for non-LC_ALL - * as it only checks the category selected to set. */ - if ((*p == 0xff) && (p[1] == 0xff)) { - return NULL; - } -#endif /* __LOCALE_STRICTER_SETLOCALE */ - /* Note: Validate settings below. */ - *++s = *++p; - *++s = *++p; - } while (++i < category); - } else if (!find_locale(category, locale, new_locale)) { - return NULL; + /* TODO: Ok, everything checks out, so install the new locale. */ + _locale_set(new_locale); } - - /* TODO: Ok, everything checks out, so install the new locale. */ - _locale_set(new_locale); - - /* Everything ok, so make a copy in setlocale_buf and return. */ - goto DONE; + /* Either a query or a successful set, so return current locale string. */ + return human_readable_locale(category, CUR_LOCALE_SPEC); } #endif /* __LOCALE_C_ONLY */ @@ -329,7 +407,7 @@ struct lconv *localeconv(void) #ifndef __LOCALE_C_ONLY -#define C_LOCALE_SELECTOR "\x23\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01" +#define C_LOCALE_SELECTOR "\x23\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80" #define LOCALE_INIT_FAILED "locale init failed!\n" #define CUR_LOCALE_SPEC (__global_locale.cur_locale) @@ -404,10 +482,9 @@ void _locale_set(const unsigned char *p) ++p; do { if ((*p != *s) || (p[1] != s[1])) { - row = (((int)(*p & 0x7f)) << 8) + p[1] - 1; -#ifndef NDEBUG + row = (((int)(*p & 0x7f)) << 7) + (p[1] & 0x7f); assert(row < NUM_LOCALES); -#endif + *s = *p; s[1] = p[1]; |