about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/internal/libc.h3
-rw-r--r--src/internal/locale_impl.h7
-rw-r--r--src/locale/__setlocalecat.c58
-rw-r--r--src/locale/setlocale.c14
4 files changed, 80 insertions, 2 deletions
diff --git a/src/internal/libc.h b/src/internal/libc.h
index 037d16b6..2eef98e4 100644
--- a/src/internal/libc.h
+++ b/src/internal/libc.h
@@ -5,9 +5,12 @@
 #include <stdio.h>
 #include <limits.h>
 
+struct __locale_map;
+
 struct __locale_struct {
 	int ctype_utf8;
 	char *messages_name;
+	struct __locale_map *cat[4];
 };
 
 struct __libc {
diff --git a/src/internal/locale_impl.h b/src/internal/locale_impl.h
index 2747b85a..0ee72d3e 100644
--- a/src/internal/locale_impl.h
+++ b/src/internal/locale_impl.h
@@ -5,6 +5,13 @@
 
 #define LOCALE_NAME_MAX 15
 
+struct __locale_map {
+	const void *map;
+	size_t map_size;
+	char name[LOCALE_NAME_MAX+1];
+	struct __locale_map *next;
+};
+
 int __setlocalecat(locale_t, int, const char *);
 
 #define CURRENT_LOCALE \
diff --git a/src/locale/__setlocalecat.c b/src/locale/__setlocalecat.c
index a947dbff..bbecde41 100644
--- a/src/locale/__setlocalecat.c
+++ b/src/locale/__setlocalecat.c
@@ -4,6 +4,58 @@
 #include "libc.h"
 #include "atomic.h"
 
+const unsigned char *__map_file(const char *, size_t *);
+int __munmap(void *, size_t);
+char *__strchrnul(const char *, int);
+
+static struct __locale_map *findlocale(const char *name, size_t n)
+{
+	static void *loc_head;
+	struct __locale_map *p, *new, *old_head;
+	const char *path = 0, *z;
+	char buf[256];
+	size_t l;
+	const void *map;
+	size_t map_size;
+
+	for (p=loc_head; p; p=p->next)
+		if (!strcmp(name, p->name)) return p;
+
+	if (strchr(name, '/')) return 0;
+
+	if (!libc.secure) path = getenv("MUSL_LOCPATH");
+	/* FIXME: add a default path? */
+	if (!path) return 0;
+
+	for (; *path; path=z+!!*z) {
+		z = __strchrnul(path, ':');
+		l = z - path - !!*z;
+		if (l >= sizeof buf - n - 2) continue;
+		memcpy(buf, path, l);
+		buf[l] = '/';
+		memcpy(buf+l+1, name, n);
+		buf[l+1+n] = 0;
+		map = __map_file(buf, &map_size);
+		if (map) {
+			new = malloc(sizeof *new);
+			if (!new) {
+				__munmap((void *)map, map_size);
+				return 0;
+			}
+			new->map = map;
+			new->map_size = map_size;
+			memcpy(new->name, name, n);
+			new->name[n] = 0;
+			do {
+				old_head = loc_head;
+				new->next = old_head;
+			} while (a_cas_p(&loc_head, old_head, new) != old_head);
+			return new;
+		}
+	}
+	return 0;
+}
+
 static const char envvars[][12] = {
 	"LC_CTYPE",
 	"LC_NUMERIC",
@@ -26,6 +78,7 @@ int __setlocalecat(locale_t loc, int cat, const char *val)
 	int builtin = (val[0]=='C' && !val[1])
 		|| !strcmp(val, "C.UTF-8")
 		|| !strcmp(val, "POSIX");
+	struct __locale_map *data, *old;
 
 	switch (cat) {
 	case LC_CTYPE:
@@ -40,6 +93,11 @@ int __setlocalecat(locale_t loc, int cat, const char *val)
 		}
 		/* fall through */
 	default:
+		data = builtin ? 0 : findlocale(val, n);
+		if (data == loc->cat[cat-2]) break;
+		do old = loc->cat[cat-2];
+		while (a_cas_p(&loc->cat[cat-2], old, data) != old);
+	case LC_NUMERIC:
 		break;
 	}
 	return 0;
diff --git a/src/locale/setlocale.c b/src/locale/setlocale.c
index cbc0b551..8ea389a3 100644
--- a/src/locale/setlocale.c
+++ b/src/locale/setlocale.c
@@ -9,6 +9,9 @@ static char buf[2+4*(LOCALE_NAME_MAX+1)];
 
 char *setlocale(int cat, const char *name)
 {
+	struct __locale_map *lm;
+	int i, j;
+
 	if (!libc.global_locale.messages_name) {
 		libc.global_locale.messages_name =
 			buf + 2 + 3*(LOCALE_NAME_MAX+1);
@@ -24,7 +27,6 @@ char *setlocale(int cat, const char *name)
 	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];
@@ -45,6 +47,11 @@ char *setlocale(int cat, const char *name)
 		}
 		memset(buf, ';', 2 + 3*(LOCALE_NAME_MAX+1));
 		buf[0] = libc.global_locale.ctype_utf8 ? 'U' : 'C';
+		for (i=LC_TIME; i<LC_MESSAGES; i++) {
+			lm = libc.global_locale.cat[i-2];
+			if (lm) memcpy(buf + 2 + (i-2)*(LOCALE_NAME_MAX+1),
+				lm->name, strlen(lm->name));
+		}
 		return buf;
 	}
 
@@ -58,10 +65,13 @@ char *setlocale(int cat, const char *name)
 	switch (cat) {
 	case LC_CTYPE:
 		return libc.global_locale.ctype_utf8 ? "C.UTF-8" : "C";
+	case LC_NUMERIC:
+		return "C";
 	case LC_MESSAGES:
 		return libc.global_locale.messages_name[0]
 			? libc.global_locale.messages_name : "C";
 	default:
-		return "C";
+		lm = libc.global_locale.cat[cat-2];
+		return lm ? lm->name : "C";
 	}
 }