From 618f8d6031ed95bae4ab122cc6c98d99cdd96d6f Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Mon, 13 Jul 2026 07:53:18 -0400 Subject: [PATCH] libc: report complete locale information --- apps/browser-demos/test/locale-info.spec.ts | 58 +++ docs/posix-status.md | 7 + examples/locale_info_test.c | 329 ++++++++++++++++++ host/test/global-setup.ts | 1 + host/test/locale-info.test.ts | 21 ++ .../src/internal/locale_name_impl.h | 15 + libc/musl-overlay/src/locale/freelocale.c | 21 ++ .../musl-overlay/src/locale/getlocalename_l.c | 35 +- libc/musl-overlay/src/locale/langinfo.c | 81 +++++ libc/musl-overlay/src/locale/locale_name.c | 80 +++++ libc/musl-overlay/src/locale/newlocale.c | 72 ++++ libc/musl-overlay/src/locale/setlocale.c | 79 +++++ 12 files changed, 788 insertions(+), 11 deletions(-) create mode 100644 apps/browser-demos/test/locale-info.spec.ts create mode 100644 examples/locale_info_test.c create mode 100644 host/test/locale-info.test.ts create mode 100644 libc/musl-overlay/src/internal/locale_name_impl.h create mode 100644 libc/musl-overlay/src/locale/freelocale.c create mode 100644 libc/musl-overlay/src/locale/langinfo.c create mode 100644 libc/musl-overlay/src/locale/locale_name.c create mode 100644 libc/musl-overlay/src/locale/newlocale.c create mode 100644 libc/musl-overlay/src/locale/setlocale.c diff --git a/apps/browser-demos/test/locale-info.spec.ts b/apps/browser-demos/test/locale-info.spec.ts new file mode 100644 index 0000000000..d232092873 --- /dev/null +++ b/apps/browser-demos/test/locale-info.spec.ts @@ -0,0 +1,58 @@ +import { expect, test } from "@playwright/test"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const programPath = resolve( + __dirname, + "../../../examples/locale_info_test.wasm", +); + +test("locale object names and alternate months work in Chromium", async ({ + page, + baseURL, + browserName, +}) => { + test.skip( + browserName !== "chromium", + "the aggregate browser gate uses Chromium", + ); + expect(baseURL).toBeTruthy(); + + const runtimeErrors: string[] = []; + page.on("console", (message) => { + if (message.type() === "error") { + runtimeErrors.push(`console: ${message.text()}`); + } + }); + page.on("pageerror", (error) => { + runtimeErrors.push(`pageerror: ${error.message}`); + }); + await page.route("**/favicon.ico", (route) => + route.fulfill({ status: 204 }), + ); + + await page.goto(new URL("/pages/test-runner/", baseURL).href); + await page.waitForFunction(() => (window as any).__testRunnerReady === true); + + const programUrl = new URL(`/@fs/${programPath}`, baseURL).href; + const result = await page.evaluate( + async ({ programUrl }) => { + const response = await fetch(programUrl); + if (!response.ok) { + throw new Error(`program fetch failed: ${response.status}`); + } + return (window as any).__runTest( + await response.arrayBuffer(), + ["locale-info-test"], + 60_000, + ); + }, + { programUrl }, + ); + + expect(result.exitCode, result.stderr).toBe(0); + expect(result.stdout).toBe("locale-info-ok\n"); + expect(result.stderr).toBe(""); + expect(runtimeErrors).toEqual([]); +}); diff --git a/docs/posix-status.md b/docs/posix-status.md index dbec136eef..93c31fd2c5 100644 --- a/docs/posix-status.md +++ b/docs/posix-status.md @@ -357,6 +357,13 @@ All virtual devices return synthetic `stat()` with `S_IFCHR | 0666`, determinist | `setenv()` / `unsetenv()` | Full | Kernel-managed. setenv supports overwrite flag. Rejects empty name or name containing '='. | | `environ` | Partial | Stored as Vec of KEY=VALUE entries in Process. No C-style char** environ pointer yet. | +## Locale + +| Function | Status | Notes | +|----------|--------|-------| +| `getlocalename_l()` | Full | Returns the real per-category name from local and global locale objects. LC_ALL returns a `setlocale()`-compatible name, including mixed-category locales. | +| `nl_langinfo()` / `nl_langinfo_l()` | Partial | Implements musl's locale data plus POSIX.1-2024 `ALTMON_1` through `ALTMON_12` and `ABALTMON_1` through `ABALTMON_12`. Musl catalogs do not encode a distinct alternate grammatical month form, so those items fall back to the corresponding full or abbreviated month name. | + ## System Information | Function | Status | Notes | diff --git a/examples/locale_info_test.c b/examples/locale_info_test.c new file mode 100644 index 0000000000..a8ef9f44ee --- /dev/null +++ b/examples/locale_info_test.c @@ -0,0 +1,329 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +static int failures; + +static void expect_string(const char *label, const char *actual, + const char *expected) +{ + if (!actual || strcmp(actual, expected)) { + fprintf(stderr, "%s: expected '%s', got '%s'\n", label, + expected, actual ? actual : "(null)"); + failures++; + } +} + +static void expect_null(const char *label, const char *actual) +{ + if (actual) { + fprintf(stderr, "%s: expected NULL, got '%s'\n", label, actual); + failures++; + } +} + +static locale_t make_composite_locale(const char *const names[LC_ALL]) +{ + locale_t locale = newlocale(LC_ALL_MASK, names[0], 0); + if (!locale) return 0; + + for (int category = LC_CTYPE; category < LC_ALL; category++) { + locale_t updated = newlocale(1 << category, names[category], locale); + if (!updated) { + freelocale(locale); + return 0; + } + locale = updated; + } + return locale; +} + +static void expect_invalid_composite(const char *label, const char *name, + const char *unchanged) +{ + if (setlocale(LC_ALL, name)) { + fprintf(stderr, "%s: malformed composite was accepted\n", label); + failures++; + } + expect_string(label, getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE), unchanged); +} + +static void check_semicolon_round_trip(void) +{ + static const char *const names[LC_ALL] = { + "ct;ype", "num;eric", "ti;me", + "col;late", "mon;etary", "mes;sages", + }; + static const char encoded[] = + "ct;ype/num;eric/ti;me/col;late/mon;etary/mes;sages"; + locale_t locale = make_composite_locale(names); + if (!locale) { + fputs("semicolon composite locale creation failed\n", stderr); + failures++; + return; + } + + const char *name = getlocalename_l(LC_ALL, locale); + expect_string("semicolon composite encoding", name, encoded); + if (!name) { + freelocale(locale); + return; + } + if (!setlocale(LC_ALL, name)) { + fputs("semicolon composite setlocale failed\n", stderr); + failures++; + } else { + for (int category = LC_CTYPE; category < LC_ALL; category++) + expect_string("semicolon category round trip", + getlocalename_l(category, LC_GLOBAL_LOCALE), names[category]); + expect_string("semicolon LC_ALL round trip", + setlocale(LC_ALL, 0), encoded); + } + freelocale(locale); + + if (!setlocale(LC_ALL, "one;locale")) { + fputs("uniform semicolon setlocale failed\n", stderr); + failures++; + return; + } + for (int category = LC_CTYPE; category < LC_ALL; category++) + expect_string("uniform semicolon category", + getlocalename_l(category, LC_GLOBAL_LOCALE), "one;locale"); + expect_string("uniform semicolon LC_ALL", + getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE), "one;locale"); + expect_invalid_composite("missing composite field", + "bad-0/bad-1/bad-2/bad-3/bad-4", "one;locale"); + expect_invalid_composite("extra composite field", + "bad-0/bad-1/bad-2/bad-3/bad-4/bad-5/bad-6", "one;locale"); + expect_invalid_composite("empty composite field", + "bad-0/bad-1//bad-3/bad-4/bad-5", "one;locale"); +} + +static void *replace_global_locale(void *arg) +{ + const char *encoded = arg; + if (!setlocale(LC_ALL, encoded)) { + fputs("worker setlocale failed\n", stderr); + failures++; + return 0; + } + expect_string("worker global locale", + getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE), encoded); + return 0; +} + +static void check_global_thread_buffer(void) +{ + static const char main_name[] = "main-0/main-1/main-2/main-3/main-4/main-5"; + static const char worker_name[] = + "worker-0/worker-1/worker-2/worker-3/worker-4/worker-5"; + pthread_t thread; + + if (!setlocale(LC_ALL, main_name)) { + fputs("main thread setlocale failed\n", stderr); + failures++; + return; + } + const char *saved = getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE); + expect_string("main global locale", saved, main_name); + if (pthread_create(&thread, 0, replace_global_locale, (void *)worker_name)) { + fputs("pthread_create failed\n", stderr); + failures++; + return; + } + if (pthread_join(thread, 0)) { + fputs("pthread_join failed\n", stderr); + failures++; + return; + } + expect_string("main global buffer after worker query", saved, main_name); +} + +static void select_cache_names(unsigned value, + const char *selected[LC_ALL]) +{ + static const char *const names[] = { + "cache-0", "cache-1", "cache-2", + "cache-3", "cache-4", "cache-5", + }; + + for (int category = LC_CTYPE; category < LC_ALL; category++) { + selected[category] = names[value % 6]; + value /= 6; + } +} + +static void check_cache_lifecycle(void) +{ + const char *selected[LC_ALL]; + char expected[128]; + locale_t locale; + + select_cache_names(0, selected); + locale = make_composite_locale(selected); + if (!locale) { + fputs("cache stress locale creation failed\n", stderr); + failures++; + return; + } + + /* Exercise every combination of six retained locale maps. Each successful + * in-place newlocale call invalidates the prior composite-name pointer. */ + for (unsigned value = 0; value < 46656; value++) { + select_cache_names(value, selected); + for (int category = LC_CTYPE; category < LC_ALL; category++) { + locale_t updated = newlocale(1 << category, + selected[category], locale); + if (!updated) { + fputs("cache stress newlocale failed\n", stderr); + failures++; + freelocale(locale); + return; + } + locale = updated; + } + int uniform = 1; + for (int category = LC_NUMERIC; category < LC_ALL; category++) + if (strcmp(selected[category], selected[LC_CTYPE])) uniform = 0; + if (uniform) { + snprintf(expected, sizeof expected, "%s", selected[LC_CTYPE]); + } else { + snprintf(expected, sizeof expected, "%s/%s/%s/%s/%s/%s", + selected[0], selected[1], selected[2], selected[3], + selected[4], selected[5]); + } + const char *actual = getlocalename_l(LC_ALL, locale); + if (!actual || strcmp(actual, expected)) { + fprintf(stderr, "cache stress %u: expected '%s', got '%s'\n", + value, expected, actual ? actual : "(null)"); + failures++; + break; + } + } + freelocale(locale); + + /* Repeatedly free a locale while it owns a cached composite name. */ + for (unsigned value = 0; value < 256; value++) { + select_cache_names(value, selected); + locale = make_composite_locale(selected); + if (!locale || !getlocalename_l(LC_ALL, locale)) { + fputs("cache free stress failed\n", stderr); + failures++; + if (locale) freelocale(locale); + return; + } + freelocale(locale); + } +} + +static void check_alternative_months(locale_t locale) +{ + static const nl_item full_items[] = { + ALTMON_1, ALTMON_2, ALTMON_3, ALTMON_4, ALTMON_5, ALTMON_6, + ALTMON_7, ALTMON_8, ALTMON_9, ALTMON_10, ALTMON_11, ALTMON_12, + }; + static const nl_item abbreviated_items[] = { + ABALTMON_1, ABALTMON_2, ABALTMON_3, ABALTMON_4, ABALTMON_5, + ABALTMON_6, ABALTMON_7, ABALTMON_8, ABALTMON_9, ABALTMON_10, + ABALTMON_11, ABALTMON_12, + }; + static const char *const full_names[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", + }; + static const char *const abbreviated_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + char label[32]; + + for (size_t i = 0; i < 12; i++) { + snprintf(label, sizeof label, "ALTMON_%zu", i + 1); + expect_string(label, nl_langinfo_l(full_items[i], locale), full_names[i]); + snprintf(label, sizeof label, "ABALTMON_%zu", i + 1); + expect_string(label, nl_langinfo_l(abbreviated_items[i], locale), + abbreviated_names[i]); + } +} + +int main(void) +{ + locale_t locale; + locale_t copy; + + if (!setlocale(LC_ALL, "C")) { + fputs("setlocale(C) failed\n", stderr); + return 1; + } + expect_string("global C category", + getlocalename_l(LC_TIME, LC_GLOBAL_LOCALE), "C"); + expect_string("global C LC_ALL", + getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE), "C"); + + locale = newlocale(LC_ALL_MASK, "en_US.UTF-8", 0); + if (!locale) { + fputs("newlocale(en_US.UTF-8) failed\n", stderr); + return 1; + } + for (int category = LC_CTYPE; category < LC_ALL; category++) + expect_string("named locale category", + getlocalename_l(category, locale), "en_US.UTF-8"); + expect_string("named locale LC_ALL", + getlocalename_l(LC_ALL, locale), "en_US.UTF-8"); + freelocale(locale); + + locale = newlocale(LC_ALL_MASK, "C", 0); + locale = newlocale(LC_TIME_MASK, "fr_FR.UTF-8", locale); + if (!locale) { + fputs("newlocale(composite) failed\n", stderr); + return 1; + } + expect_string("composite CTYPE", getlocalename_l(LC_CTYPE, locale), "C"); + expect_string("composite TIME", + getlocalename_l(LC_TIME, locale), "fr_FR.UTF-8"); + expect_string("composite LC_ALL", getlocalename_l(LC_ALL, locale), + "C/C/fr_FR.UTF-8/C/C/C"); + + copy = duplocale(locale); + if (!copy) { + fputs("duplocale failed\n", stderr); + return 1; + } + expect_string("duplicated TIME", + getlocalename_l(LC_TIME, copy), "fr_FR.UTF-8"); + expect_string("duplicated LC_ALL", getlocalename_l(LC_ALL, copy), + "C/C/fr_FR.UTF-8/C/C/C"); + freelocale(copy); + freelocale(locale); + + if (!setlocale(LC_TIME, "de_DE.UTF-8")) { + fputs("setlocale(de_DE.UTF-8) failed\n", stderr); + return 1; + } + expect_string("global named TIME", + getlocalename_l(LC_TIME, LC_GLOBAL_LOCALE), "de_DE.UTF-8"); + expect_string("global composite LC_ALL", + getlocalename_l(LC_ALL, LC_GLOBAL_LOCALE), + "C/C/de_DE.UTF-8/C/C/C"); + expect_null("invalid category", getlocalename_l(LC_ALL + 1, LC_GLOBAL_LOCALE)); + check_semicolon_round_trip(); + check_global_thread_buffer(); + check_cache_lifecycle(); + + locale = newlocale(LC_ALL_MASK, "C", 0); + if (!locale) { + fputs("newlocale(C) failed\n", stderr); + return 1; + } + check_alternative_months(locale); + freelocale(locale); + setlocale(LC_ALL, "C"); + + if (failures) return 1; + puts("locale-info-ok"); + return 0; +} diff --git a/host/test/global-setup.ts b/host/test/global-setup.ts index b46f1d1c62..83f787853c 100644 --- a/host/test/global-setup.ts +++ b/host/test/global-setup.ts @@ -39,6 +39,7 @@ const TEST_PROGRAMS = [ "spawn-pause.c", "mount_probe_test.c", "getpwent_smoke.c", + "locale_info_test.c", "thread-exit-group.c", ]; diff --git a/host/test/locale-info.test.ts b/host/test/locale-info.test.ts new file mode 100644 index 0000000000..8ad889207c --- /dev/null +++ b/host/test/locale-info.test.ts @@ -0,0 +1,21 @@ +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { runCentralizedProgram } from "./centralized-test-helper"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const programPath = join(__dirname, "../../examples/locale_info_test.wasm"); + +describe("locale information", () => { + it("reports locale object names and alternate month data", async () => { + const result = await runCentralizedProgram({ + programPath, + timeout: 60_000, + useDefaultRootfs: false, + }); + + expect(result.exitCode, result.stderr).toBe(0); + expect(result.stdout).toBe("locale-info-ok\n"); + expect(result.stderr).toBe(""); + }); +}); diff --git a/libc/musl-overlay/src/internal/locale_name_impl.h b/libc/musl-overlay/src/internal/locale_name_impl.h new file mode 100644 index 0000000000..f838d481c5 --- /dev/null +++ b/libc/musl-overlay/src/internal/locale_name_impl.h @@ -0,0 +1,15 @@ +#ifndef LOCALE_NAME_IMPL_H +#define LOCALE_NAME_IMPL_H + +#include "locale_impl.h" + +/* musl rejects '/' in locale names, so it unambiguously separates categories. */ +#define LOCALE_NAME_SEPARATOR '/' +#define LOCALE_NAME_BUFSIZE (LC_ALL * (LOCALE_NAME_MAX + 1)) + +/* Callers hold __locale_lock while using these helpers. */ +hidden const char *__locale_name_locked(locale_t, char[LOCALE_NAME_BUFSIZE]); +hidden const char *__locale_name_cached_locked(locale_t); +hidden void __locale_name_cache_remove_locked(locale_t); + +#endif diff --git a/libc/musl-overlay/src/locale/freelocale.c b/libc/musl-overlay/src/locale/freelocale.c new file mode 100644 index 0000000000..c0fe32a82e --- /dev/null +++ b/libc/musl-overlay/src/locale/freelocale.c @@ -0,0 +1,21 @@ +#include +#include "locale_impl.h" +#include "locale_name_impl.h" +#include "lock.h" + +#define malloc undef +#define calloc undef +#define realloc undef +#define free __libc_free + +void freelocale(locale_t locale) +{ + if (!__loc_is_allocated(locale)) return; + + LOCK(__locale_lock); + __locale_name_cache_remove_locked(locale); + free(locale); + UNLOCK(__locale_lock); +} + +weak_alias(freelocale, __freelocale); diff --git a/libc/musl-overlay/src/locale/getlocalename_l.c b/libc/musl-overlay/src/locale/getlocalename_l.c index e9f437eb43..5130ed7c96 100644 --- a/libc/musl-overlay/src/locale/getlocalename_l.c +++ b/libc/musl-overlay/src/locale/getlocalename_l.c @@ -1,16 +1,29 @@ #include +#include "locale_impl.h" +#include "locale_name_impl.h" +#include "libc.h" +#include "lock.h" + +static _Thread_local char global_name[LOCALE_NAME_BUFSIZE]; -/* - * POSIX.1-2024 getlocalename_l — return the name of a locale category. - * - * musl only supports the "C"/"POSIX" locale, so this always returns "C". - * For LC_GLOBAL_LOCALE, we return whatever setlocale() would return. - */ const char *getlocalename_l(int category, locale_t locale) { - if (category < LC_CTYPE || category > LC_MESSAGES) - return NULL; - /* Both LC_GLOBAL_LOCALE and newlocale-created locales - * always use "C" in musl's single-locale implementation. */ - return "C"; + const char *name; + int global; + + if ((unsigned)category > LC_ALL || !locale) return 0; + + LOCK(__locale_lock); + global = locale == LC_GLOBAL_LOCALE; + if (global) locale = &libc.global_locale; + if (category == LC_ALL) { + name = global + ? __locale_name_locked(locale, global_name) + : __locale_name_cached_locked(locale); + } else { + const struct __locale_map *map = locale->cat[category]; + name = map ? map->name : "C"; + } + UNLOCK(__locale_lock); + return name; } diff --git a/libc/musl-overlay/src/locale/langinfo.c b/libc/musl-overlay/src/locale/langinfo.c new file mode 100644 index 0000000000..b0e338e87d --- /dev/null +++ b/libc/musl-overlay/src/locale/langinfo.c @@ -0,0 +1,81 @@ +#include +#include +#include "locale_impl.h" + +static const char c_time[] = + "Sun\0" "Mon\0" "Tue\0" "Wed\0" "Thu\0" "Fri\0" "Sat\0" + "Sunday\0" "Monday\0" "Tuesday\0" "Wednesday\0" + "Thursday\0" "Friday\0" "Saturday\0" + "Jan\0" "Feb\0" "Mar\0" "Apr\0" "May\0" "Jun\0" + "Jul\0" "Aug\0" "Sep\0" "Oct\0" "Nov\0" "Dec\0" + "January\0" "February\0" "March\0" "April\0" + "May\0" "June\0" "July\0" "August\0" + "September\0" "October\0" "November\0" "December\0" + "AM\0" "PM\0" + "%a %b %e %T %Y\0" + "%m/%d/%y\0" + "%H:%M:%S\0" + "%I:%M:%S %p\0" + "\0" + "\0" + "%m/%d/%y\0" + "0123456789\0" + "%a %b %e %T %Y\0" + "%H:%M:%S\0" + /* musl catalogs have one full and one abbreviated name per month. + * Use those names as the POSIX fallback when a locale has no distinct + * alternative grammatical form. */ + "January\0" "February\0" "March\0" "April\0" + "May\0" "June\0" "July\0" "August\0" + "September\0" "October\0" "November\0" "December\0" + "Jan\0" "Feb\0" "Mar\0" "Apr\0" "May\0" "Jun\0" + "Jul\0" "Aug\0" "Sep\0" "Oct\0" "Nov\0" "Dec"; + +static const char c_messages[] = "^[yY]\0" "^[nN]\0" "yes\0" "no"; +static const char c_numeric[] = ".\0" ""; + +char *__nl_langinfo_l(nl_item item, locale_t loc) +{ + int cat = item >> 16; + int idx = item & 65535; + const char *str; + + if (item == CODESET) return loc->cat[LC_CTYPE] ? "UTF-8" : "ASCII"; + + /* _NL_LOCALE_NAME extension */ + if (idx == 65535 && cat < LC_ALL) + return loc->cat[cat] ? (char *)loc->cat[cat]->name : "C"; + + switch (cat) { + case LC_NUMERIC: + if (idx > 1) return ""; + str = c_numeric; + break; + case LC_TIME: + if (idx > 0x49) return ""; + str = c_time; + break; + case LC_MONETARY: + if (idx > 0) return ""; + str = ""; + break; + case LC_MESSAGES: + if (idx > 3) return ""; + str = c_messages; + break; + default: + return ""; + } + + for (; idx; idx--, str++) for (; *str; str++); + if (cat != LC_NUMERIC && *str) str = LCTRANS(str, cat, loc); + return (char *)str; +} + +char *__nl_langinfo(nl_item item) +{ + return __nl_langinfo_l(item, CURRENT_LOCALE); +} + +weak_alias(__nl_langinfo, nl_langinfo); +weak_alias(__nl_langinfo_l, nl_langinfo_l); diff --git a/libc/musl-overlay/src/locale/locale_name.c b/libc/musl-overlay/src/locale/locale_name.c new file mode 100644 index 0000000000..ae7feec15c --- /dev/null +++ b/libc/musl-overlay/src/locale/locale_name.c @@ -0,0 +1,80 @@ +#include +#include +#include "locale_name_impl.h" + +#define malloc __libc_malloc +#define free __libc_free + +struct locale_name_cache { + struct locale_name_cache *next; + locale_t locale; + char name[LOCALE_NAME_BUFSIZE]; +}; + +static struct locale_name_cache *locale_name_cache; + +/* Entries belong to locale objects and are removed by successful in-place + * newlocale calls and by freelocale. Builtin locale objects have fixed lifetime. */ + +static const char *category_name(locale_t locale, int category) +{ + const struct __locale_map *map = locale->cat[category]; + return map ? map->name : "C"; +} + +hidden const char *__locale_name_locked(locale_t locale, + char name[LOCALE_NAME_BUFSIZE]) +{ + char *p = name; + + for (int i = 1; i < LC_ALL; i++) + if (locale->cat[i] != locale->cat[0]) goto composite; + return category_name(locale, LC_CTYPE); + +composite: + for (int i = 0; i < LC_ALL; i++) { + const char *part = category_name(locale, i); + size_t len = strlen(part); + if (i) *p++ = LOCALE_NAME_SEPARATOR; + memcpy(p, part, len); + p += len; + } + *p = 0; + return name; +} + +hidden const char *__locale_name_cached_locked(locale_t locale) +{ + char name[LOCALE_NAME_BUFSIZE]; + const char *serialized; + + for (struct locale_name_cache *entry = locale_name_cache; + entry; entry = entry->next) + if (entry->locale == locale) return entry->name; + + serialized = __locale_name_locked(locale, name); + if (serialized != name) return serialized; + + struct locale_name_cache *entry = malloc(sizeof *entry); + if (!entry) return 0; + entry->next = locale_name_cache; + entry->locale = locale; + strcpy(entry->name, name); + locale_name_cache = entry; + return entry->name; +} + +hidden void __locale_name_cache_remove_locked(locale_t locale) +{ + struct locale_name_cache **link = &locale_name_cache; + + while (*link) { + struct locale_name_cache *entry = *link; + if (entry->locale != locale) { + link = &entry->next; + continue; + } + *link = entry->next; + free(entry); + } +} diff --git a/libc/musl-overlay/src/locale/newlocale.c b/libc/musl-overlay/src/locale/newlocale.c new file mode 100644 index 0000000000..5c90e590c6 --- /dev/null +++ b/libc/musl-overlay/src/locale/newlocale.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include "locale_impl.h" +#include "locale_name_impl.h" +#include "lock.h" + +#define malloc __libc_malloc +#define calloc undef +#define realloc undef +#define free undef + +static int default_locale_init_done; +static struct __locale_struct default_locale, default_ctype_locale; + +int __loc_is_allocated(locale_t loc) +{ + return loc && loc != C_LOCALE && loc != UTF8_LOCALE + && loc != &default_locale && loc != &default_ctype_locale; +} + +static locale_t do_newlocale(int mask, const char *name, locale_t loc) +{ + struct __locale_struct tmp; + + for (int i=0; icat[i] : + __get_locale(i, (mask & (1< +#include +#include +#include "locale_impl.h" +#include "locale_name_impl.h" +#include "libc.h" +#include "lock.h" + +static char buf[LOCALE_NAME_BUFSIZE]; + +static int load_composite(struct __locale_struct *locale, const char *name) +{ + const char *p = name; + char part[LOCALE_NAME_MAX + 1]; + + for (int i = 0; i < LC_ALL; i++) { + const char *end = __strchrnul(p, LOCALE_NAME_SEPARATOR); + size_t len = end - p; + if (!len || len > LOCALE_NAME_MAX + || (i < LC_ALL - 1 ? !*end : *end)) + return -1; + memcpy(part, p, len); + part[len] = 0; + locale->cat[i] = __get_locale(i, part); + if (locale->cat[i] == LOC_MAP_FAILED) return -1; + p = end + 1; + } + return 0; +} + +static int load_uniform(struct __locale_struct *locale, const char *name) +{ + for (int i = 0; i < LC_ALL; i++) { + locale->cat[i] = __get_locale(i, name); + if (locale->cat[i] == LOC_MAP_FAILED) return -1; + } + return 0; +} + +char *setlocale(int category, const char *name) +{ + const struct __locale_map *map; + + if ((unsigned)category > LC_ALL) return 0; + + LOCK(__locale_lock); + + if (category == LC_ALL) { + if (name) { + struct __locale_struct locale; + int failed = strchr(name, LOCALE_NAME_SEPARATOR) + ? load_composite(&locale, name) + : load_uniform(&locale, name); + if (failed) { + UNLOCK(__locale_lock); + return 0; + } + libc.global_locale = locale; + } + char *result = (char *)__locale_name_locked(&libc.global_locale, buf); + UNLOCK(__locale_lock); + return result; + } + + if (name) { + map = __get_locale(category, name); + if (map == LOC_MAP_FAILED) { + UNLOCK(__locale_lock); + return 0; + } + libc.global_locale.cat[category] = map; + } else { + map = libc.global_locale.cat[category]; + } + char *result = map ? (char *)map->name : "C"; + + UNLOCK(__locale_lock); + return result; +}