]> granicus.if.org Git - musl/commitdiff
add locale framework
authorRich Felker <dalias@aerifal.cx>
Wed, 2 Jul 2014 23:33:19 +0000 (19:33 -0400)
committerRich Felker <dalias@aerifal.cx>
Wed, 2 Jul 2014 23:33:19 +0000 (19:33 -0400)
this commit adds non-stub implementations of setlocale, duplocale,
newlocale, and uselocale, along with the data structures and minimal
code needed for representing the active locale on a per-thread basis
and optimizing the common case where thread-local locale settings are
not in use.

at this point, the data structures only contain what is necessary to
represent LC_CTYPE (a single flag) and LC_MESSAGES (a name for use in
finding message translation files). representation for the other
categories will be added later; the expectation is that a single
pointer will suffice for each.

for LC_CTYPE, the strings "C" and "POSIX" are treated as special; any
other string is accepted and treated as "C.UTF-8". for other
categories, any string is accepted after being truncated to a maximum
supported length (currently 15 bytes). for LC_MESSAGES, the name is
kept regardless of whether libc itself can use such a message
translation locale, since applications using catgets or gettext should
be able to use message locales libc is not aware of. for other
categories, names which are not successfully loaded as locales (which,
at present, means all names) are treated as aliases for "C". setlocale
never fails.

locale settings are not yet used anywhere, so this commit should have
no visible effects except for the contents of the string returned by
setlocale.

src/env/__init_tls.c
src/internal/libc.h
src/internal/locale_impl.h
src/locale/__setlocalecat.c [new file with mode: 0644]
src/locale/duplocale.c
src/locale/newlocale.c
src/locale/setlocale.c
src/locale/uselocale.c
src/thread/pthread_create.c

index 13cf2eeae939e03627aac000e153bb7a82e15cb1..efa072842999c6cbb959cc33a4c3c7587578c821 100644 (file)
@@ -16,6 +16,7 @@ int __init_tp(void *p)
        if (!r) libc.can_do_threads = 1;
        libc.has_thread_pointer = 1;
        td->tid = td->pid = __syscall(SYS_set_tid_address, &td->tid);
+       td->locale = &libc.global_locale;
        return 0;
 }
 
index fb4d9bc0849e6828e47aa6a4229b4f654f8e6c00..037d16b6e2cddc92a5ae86b101c84cffebca23a1 100644 (file)
@@ -5,6 +5,11 @@
 #include <stdio.h>
 #include <limits.h>
 
+struct __locale_struct {
+       int ctype_utf8;
+       char *messages_name;
+};
+
 struct __libc {
        int has_thread_pointer;
        int can_do_threads;
@@ -16,6 +21,9 @@ struct __libc {
        int ofl_lock[2];
        size_t tls_size;
        size_t page_size;
+       volatile int uselocale_cnt;
+       volatile int bytelocale_cnt_minus_1;
+       struct __locale_struct global_locale;
 };
 
 extern size_t __hwcap;
index f41c6f24513c9573a16546dfe6cd40d51058625c..2747b85a4b483475cd62fb68f1eb538705fdbb21 100644 (file)
@@ -1,5 +1,17 @@
 #include <locale.h>
+#include <stdlib.h>
+#include "libc.h"
+#include "pthread_impl.h"
 
-struct __locale_struct {
-       int dummy;
-};
+#define LOCALE_NAME_MAX 15
+
+int __setlocalecat(locale_t, int, const char *);
+
+#define CURRENT_LOCALE \
+       (libc.uselocale_cnt ? __pthread_self()->locale : &libc.global_locale)
+
+#define CURRENT_UTF8 \
+       (libc.bytelocale_cnt_minus_1<0 || __pthread_self()->locale->ctype_utf8)
+
+#undef MB_CUR_MAX
+#define MB_CUR_MAX (CURRENT_UTF8 ? 4 : 1)
diff --git a/src/locale/__setlocalecat.c b/src/locale/__setlocalecat.c
new file mode 100644 (file)
index 0000000..f1e4bf0
--- /dev/null
@@ -0,0 +1,46 @@
+#include <locale.h>
+#include <string.h>
+#include "locale_impl.h"
+#include "libc.h"
+#include "atomic.h"
+
+static const char envvars[][12] = {
+       "LC_CTYPE",
+       "LC_NUMERIC",
+       "LC_TIME",
+       "LC_COLLATE",
+       "LC_MONETARY",
+       "LC_MESSAGES",
+};
+
+int __setlocalecat(locale_t loc, int cat, const char *val)
+{
+       if (!*val) {
+               (val = getenv("LC_ALL")) ||
+               (val = getenv(envvars[cat])) ||
+               (val = getenv("LANG")) ||
+               (val = "C.UTF-8");
+       }
+
+       size_t n = strnlen(val, LOCALE_NAME_MAX);
+       int builtin = (val[0]=='C' && !val[1])
+               || !strcmp(val, "C.UTF-8")
+               || !strcmp(val, "POSIX");
+
+       switch (cat) {
+       case LC_CTYPE:
+               a_store(&loc->ctype_utf8, !builtin || val[1]=='.');
+               break;
+       case LC_MESSAGES:
+               if (builtin) {
+                       loc->messages_name[0] = 0;
+               } else {
+                       memcpy(loc->messages_name, val, n);
+                       loc->messages_name[n] = 0;
+               }
+               /* fall through */
+       default:
+               break;
+       }
+       return 0;
+}
index f9fc1ffa75dac6e0c816984e94fe8754cc93edae..133687071df766c9884e814d88b616b54b7b25c2 100644 (file)
@@ -3,12 +3,19 @@
 #include "locale_impl.h"
 #include "libc.h"
 
-locale_t duplocale(locale_t old)
+locale_t __duplocale(locale_t old)
 {
-       locale_t new;
-       new = calloc(1, sizeof *new);
+       locale_t new = calloc(1, sizeof *new + LOCALE_NAME_MAX + 1);
+       if (!new) return 0;
+       new->messages_name = (void *)(new+1);
+
+       if (old == LC_GLOBAL_LOCALE) old = &libc.global_locale;
+       new->ctype_utf8 = old->ctype_utf8;
+       if (old->messages_name)
+               strcpy(new->messages_name, old->messages_name);
+
        if (new && old != LC_GLOBAL_LOCALE) memcpy(new, old, sizeof *new);
        return new;
 }
 
-weak_alias(duplocale, __duplocale);
+weak_alias(__duplocale, duplocale);
index 447c8fc20262420510135d7c26081acb4b86d638..39501d0c1b9a3305cbd2405e716dc06620d310c7 100644 (file)
@@ -3,12 +3,24 @@
 #include "locale_impl.h"
 #include "libc.h"
 
-locale_t newlocale(int mask, const char *name, locale_t base)
+locale_t __newlocale(int mask, const char *name, locale_t loc)
 {
-       if (*name && strcmp(name, "C") && strcmp(name, "POSIX"))
-               return 0;
-       if (!base) base = calloc(1, sizeof *base);
-       return base;
+       int i;
+
+       if (!loc) {
+               loc = calloc(1, sizeof *loc + LOCALE_NAME_MAX + 1);
+               if (!loc) return 0;
+               loc->messages_name = (void *)(loc+1);
+               for (i=0; i<LC_ALL; i++)
+                       if (!(mask & (1<<i)))
+                               __setlocalecat(loc, i, "");
+       }
+
+       for (i=0; i<LC_ALL; i++)
+               if (mask & (1<<i))
+                       __setlocalecat(loc, i, name);
+
+       return loc;
 }
 
-weak_alias(newlocale, __newlocale);
+weak_alias(__newlocale, newlocale);
index 28f29b80414618ff81f15e18789e869c03f3f1cf..cbc0b5517f04dcd6ce20e721a3d0bc13ba7c9293 100644 (file)
@@ -1,9 +1,67 @@
 #include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include "locale_impl.h"
+#include "libc.h"
+#include "atomic.h"
 
-char *setlocale(int category, const char *locale)
+static char buf[2+4*(LOCALE_NAME_MAX+1)];
+
+char *setlocale(int cat, const char *name)
 {
-       /* Note: plain "C" would be better, but puts some broken
-        * software into legacy 8-bit-codepage mode, ignoring
-        * the standard library's multibyte encoding */
-       return "C.UTF-8";
+       if (!libc.global_locale.messages_name) {
+               libc.global_locale.messages_name =
+                       buf + 2 + 3*(LOCALE_NAME_MAX+1);
+       }
+
+       if ((unsigned)cat > LC_ALL) return 0;
+
+       /* For LC_ALL, setlocale is required to return a string which
+        * encodes the current setting for all categories. The format of
+        * this string is unspecified, and only the following code, which
+        * performs both the serialization and deserialization, depends
+        * on the format, so it can easily be changed if needed. */
+       if (cat == LC_ALL) {
+               if (name) {
+                       char part[LOCALE_NAME_MAX+1];
+                       int i, j;
+                       if (name[0] && name[1]==';'
+                           && strlen(name) > 2 + 3*(LOCALE_NAME_MAX+1)) {
+                               part[0] = name[0];
+                               part[1] = 0;
+                               setlocale(LC_CTYPE, part);
+                               part[LOCALE_NAME_MAX] = 0;
+                               for (i=LC_TIME; i<LC_MESSAGES; i++) {
+                                       memcpy(part, name + 2 + (i-2)*(LOCALE_NAME_MAX+1), LOCALE_NAME_MAX);
+                                       for (j=LOCALE_NAME_MAX-1; j && part[j]==';'; j--)
+                                               part[j] = 0;
+                                       setlocale(i, part);
+                               }
+                               setlocale(LC_MESSAGES, name + 2 + 3*(LOCALE_NAME_MAX+1));
+                       } else {
+                               for (i=0; i<LC_ALL; i++)
+                                       setlocale(i, name);
+                       }
+               }
+               memset(buf, ';', 2 + 3*(LOCALE_NAME_MAX+1));
+               buf[0] = libc.global_locale.ctype_utf8 ? 'U' : 'C';
+               return buf;
+       }
+
+       if (name) {
+               int adj = libc.global_locale.ctype_utf8;
+               __setlocalecat(&libc.global_locale, cat, name);
+               adj -= libc.global_locale.ctype_utf8;
+               if (adj) a_fetch_add(&libc.bytelocale_cnt_minus_1, adj);
+       }
+
+       switch (cat) {
+       case LC_CTYPE:
+               return libc.global_locale.ctype_utf8 ? "C.UTF-8" : "C";
+       case LC_MESSAGES:
+               return libc.global_locale.messages_name[0]
+                       ? libc.global_locale.messages_name : "C";
+       default:
+               return "C";
+       }
 }
index 4fc5c64ed118245fdb910d9560524dae1defee00..510679575944dd1cfe04589d3e2b1b9f547c60ba 100644 (file)
@@ -2,12 +2,25 @@
 #include "pthread_impl.h"
 #include "libc.h"
 
-locale_t uselocale(locale_t l)
+locale_t __uselocale(locale_t new)
 {
        pthread_t self = __pthread_self();
        locale_t old = self->locale;
-       if (l) self->locale = l;
-       return old;
+       locale_t global = &libc.global_locale;
+
+       if (new == LC_GLOBAL_LOCALE) new = global;
+
+       if (new && new != old) {
+               int adj = 0;
+               if (new == global) a_dec(&libc.uselocale_cnt);
+               else if (!new->ctype_utf8) adj++;
+               if (old == global) a_inc(&libc.uselocale_cnt);
+               else if (!old->ctype_utf8) adj--;
+               a_fetch_add(&libc.bytelocale_cnt_minus_1, adj);
+               self->locale = new;
+       }
+
+       return old == global ? LC_GLOBAL_LOCALE : old;
 }
 
-weak_alias(uselocale, __uselocale);
+weak_alias(__uselocale, uselocale);
index e9c8160a4f982b0be2ea4c902810c2350a91902a..a7493c1041018e66209818c66ef30b319c89a74a 100644 (file)
@@ -57,6 +57,12 @@ _Noreturn void pthread_exit(void *result)
                exit(0);
        }
 
+       if (self->locale != &libc.global_locale) {
+               a_dec(&libc.uselocale_cnt);
+               if (self->locale->ctype_utf8)
+                       a_dec(&libc.bytelocale_cnt_minus_1);
+       }
+
        if (self->detached && self->map_base) {
                /* Detached threads must avoid the kernel clear_child_tid
                 * feature, since the virtual address will have been
@@ -205,6 +211,7 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp
        new->start_arg = arg;
        new->self = new;
        new->tsd = (void *)tsd;
+       new->locale = &libc.global_locale;
        if (attr._a_detach) {
                new->detached = 1;
                flags -= CLONE_CHILD_CLEARTID;