Browse Source

Add locale-enabled strcoll function from vodz, plus supporting tool.

Manuel Novoa III 23 years ago
parent
commit
d85536af73

+ 6 - 4
extra/locale/README

@@ -1,6 +1,8 @@
 
-The program gen_ctype_from_glibc.c will generate data files which can be
-used by uClibc ctype functions to support locales.  From the comments:
+The programs gen_ctype_from_glibc.c and gen_collate_from_glibc.c
+will generate data files which can be
+used by uClibc ctype, strcoll and setlocale functions to support locales.
+From the comments:
 
 /*
  * Generator locale ctype tables
@@ -10,18 +12,18 @@ used by uClibc ctype functions to support locales.  From the comments:
  * ./LOCALE/LC_CTYPE files for system with uclibc
  *
  * Written by Vladimir Oleynik <vodz@usa.net> 2001
- * Base on ideas Nickolay Saukh  <nms@ussr.EU.net>
- *
  */
 
 
 Sample usage to dump all the data files in a tmp directory:
 
 gcc gen_ctype_from_glibc.c -o gen_ctype_from_glibc
+gcc gen_collate_from_glibc.c -o gen_ctype_from_glibc
 
 mkdir tmp
 cd tmp
 ../gen_ctype_from_glibc -d /usr/share/locale -c
+../gen_collate_from_glibc
 
 Then just move the directory or directories you need (not the .c files)
 to the uClibc locale file directory you set in Config.

+ 207 - 0
extra/locale/gen_collate_from_glibc.c

@@ -0,0 +1,207 @@
+/*
+ * Generator collate table from glibc special for Uclibc.
+ * Author Vladimir Oleynik. vodz@usa.net (c) 2001
+ *
+ * Require setuped work non-C LC_COLLATE
+ * This programm created ./LOCALE/LC_COLLATE file for Uclibc
+ * setlocale() and strcoll().
+ * Without argument this programm used setlocale(LC_COLLATE, "") -
+ * equivalent result setlocale(LC_COLLATE, getenv("LC_XXX"))
+ *
+ * Also, this programm have russian koi8 collate for test
+ * working Uclibc ;-)
+ *
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>   /* mkdir() */
+#include <errno.h>
+
+
+/* For strong test russian locale LC_COLLATE="ru_RU.KOI8-R" */
+static const unsigned char koi8_weights[256] = {
+  0,   99,  100,  101,  102,  103,  104,  105,
+106,    2,    5,    3,    6,    4,  107,  108,
+109,  110,  111,  112,  113,  114,  115,  116,
+117,  118,  119,  120,  121,  122,  123,  124,
+  1,   12,   21,   34,   30,   35,   33,   20,
+ 22,   23,   31,   36,    9,    8,   15,   14,
+127,  128,  129,  131,  132,  133,  134,  135,
+136,  137,   11,   10,   38,   40,   42,   13,
+ 29,  138,  140,  142,  144,  146,  148,  150,
+152,  154,  156,  158,  160,  162,  164,  166,
+168,  170,  172,  174,  176,  178,  180,  182,
+184,  186,  188,   24,   32,   25,   17,    7,
+ 16,  139,  141,  143,  145,  147,  149,  151,
+153,  155,  157,  159,  161,  163,  165,  167,
+169,  171,  173,  175,  177,  179,  181,  183,
+185,  187,  189,   26,   43,   27,   18,  125,
+ 50,   52,   54,   58,   62,   66,   70,   74,
+ 78,   82,   86,   90,   91,   92,   93,   94,
+ 95,   96,   97,   48,   98,   45,   46,   47,
+ 39,   41,  126,   49,   44,  130,   19,   37,
+ 51,   53,   55,  203,   56,   57,   59,   60,
+ 61,   63,   64,   65,   67,   68,   69,   71,
+ 72,   73,   75,  202,   76,   77,   79,   80,
+ 81,   83,   84,   85,   87,   88,   89,   28,
+253,  191,  193,  237,  199,  201,  233,  197,
+235,  209,  211,  213,  215,  217,  219,  221,
+223,  255,  225,  227,  229,  231,  205,  195,
+249,  247,  207,  241,  251,  243,  239,  245,
+252,  190,  192,  236,  198,  200,  232,  196,
+234,  208,  210,  212,  214,  216,  218,  220,
+222,  254,  224,  226,  228,  230,  204,  194,
+248,  246,  206,  240,  250,  242,  238,  244
+};
+
+int gen_weights(const char *collate)
+{
+	int weights[256];
+	int i,j;
+	char probe_str1[2];
+	char probe_str2[2];
+	char print_buf[16];
+	int  retcode = 0;
+	unsigned char out_weights[256];
+	FILE *out;
+
+	memset(weights, 0, sizeof(weights));
+	probe_str1[1]=probe_str2[1]=0;
+
+	for(i=0; i<256; i++) {
+		probe_str1[0] = i;
+		for(j=0; j<256; j++) {
+			probe_str2[0] = j;
+			if(strcoll(probe_str1, probe_str2)>0) {
+				weights[i]++;
+				if(i==j) {
+					fprintf(stderr, "\
+\nWarning! c1=%d == c2, but strcoll returned greater zero\n", i);
+				retcode++;
+				}
+			}
+		}
+	}
+	for(i=0; i<256; ) {
+		if(isprint(i))
+			sprintf(print_buf, " '%c'", i);
+		 else {
+			if(i=='\0')
+				strcpy(print_buf, "'\\0'");
+			else if(i=='\a')
+				strcpy(print_buf, "'\\a'");
+			else if(i=='\b')
+				strcpy(print_buf, "'\\b'");
+			else if(i=='\f')
+				strcpy(print_buf, "'\\f'");
+			else if(i=='\r')
+				strcpy(print_buf, "'\\r'");
+			else if(i=='\t')
+				strcpy(print_buf, "'\\t'");
+			else sprintf(print_buf, " x%02X", i);
+			}
+		printf("weights[%s] = %3d ", print_buf, weights[i]);
+		i++;
+		if( (i%4) == 0)
+			printf("\n");
+		}
+
+	for(i=0; i<256; i++) {
+		if(weights[i]<0 || weights[i]>=256) {
+			fprintf(stderr, "Hmm, weights[%d]=%d\n", i, weights[i]);
+			retcode++;
+		}
+		for(j=0; j<256; j++) {
+			if(i==j)
+				continue;
+			if(weights[i]==weights[j]) {
+				fprintf(stderr, "\
+Warning! c1=%d c2=%d and strcoll returned equivalent weight\n", i, j);
+			retcode++;
+			}
+		}
+	}
+	if(retcode)
+		return 1;
+
+	if(strcasecmp(collate, "ru_RU.KOI8-R")==0 ||
+				strcmp(collate, "ru_RU")==0 ||
+					strcmp(collate, "koi8-r")==0) {
+		for(i=0; i<256; i++)
+			if(weights[i]!=koi8_weights[i]) {
+				fprintf(stderr, "\
+Error koi8-r collate compare, glibc weights[%d]=%d but current generation %d\n",
+					i, koi8_weights[i], weights[i]);
+				retcode++;
+			}
+		if(retcode)
+			return 5;
+	}
+	for(i=0; i<256; i++)
+		out_weights[i] = weights[i];
+	out = fopen("LC_COLLATE", "w");
+	if(out == NULL) {
+		fprintf(stderr, "Can`t create ./%s/LC_COLLATE file\n", collate);
+		return 10;
+	}
+	if(fwrite(out_weights, 1, 256, out)!=256) {
+		fprintf(stderr, "IO error in process write ./%s/LC_COLLATE file\n", collate);
+		return 11;
+	}
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	char *locale;
+	char *slr;
+	char *collate;
+
+	if(argc<1 || argc>2) {
+		fprintf(stderr, "Usage: %s [locale]\n", argv[0]);
+	}
+	locale = argc==1 ? "" : argv[1];
+
+	collate = setlocale(LC_COLLATE, locale);
+	fprintf(stderr, "setlocale(LC_COLLATE, \"%s\") returned %s\n", locale, collate);
+	if(collate==0) {
+		fprintf(stderr, "Can`t set LC_COLLATE\n");
+		return 2;
+		}
+	if(strcmp(collate, "C")==0) {
+		fprintf(stderr, "\
+LC_COLLATE=\"C\" is trivial and not interesting for this programm\n");
+		return 3;
+	}
+	slr = setlocale(LC_CTYPE, locale);
+	fprintf(stderr, "setlocale(LC_CTYPE, \"%s\") returned %s\n", locale, slr);
+	if(slr==0) {
+		slr = setlocale(LC_CTYPE, "POSIX");
+		if(slr==0) {
+			fprintf(stderr, "Hmm, can`t set setlocale(LC_CTYPE, \"POSIX\")\n");
+			return 4;
+		}
+	}
+	if(mkdir(collate, 0755)!=0 && errno!=EEXIST) {
+		fprintf(stderr, "Can`t make directory %s\n", collate);
+		return 6;
+	}
+	if(chdir(collate)) {
+		fprintf(stderr, "Hmm, can`t change directory to %s\n", collate);
+		return 7;
+	}
+	if(gen_weights(collate)) {
+		if(chdir("..")) {
+			fprintf(stderr, "Hmm, can`t change to current directory\n");
+			return 7;
+		}
+		rmdir(collate);
+		return 1;
+	}
+	return 0;
+}

+ 3 - 2
extra/locale/gen_ctype_from_glibc.c

@@ -19,8 +19,9 @@
 #include <string.h>
 #include <getopt.h>
 #include <unistd.h>
+#include <errno.h>
 
-#include "../../misc/ctype/ctype.h"
+#include "../../libc/misc/locale/_locale.h"
 
 
 #define DEFAULT_LOCALE_DIR      "/usr/share/locale/"
@@ -229,7 +230,7 @@ Defaults:\n\
       printf("setlocale(LC_CTYPE, %s) returned %s\n", ln, t);
       if(t==0)
 		continue;
-      if(mkdir(ln, 0755)) {
+      if(mkdir(ln, 0755)!=0 && errno!=EEXIST) {
 		fprintf(stderr, "Can`t create directory `%s'\n", ln);
 		continue;
       }

+ 1 - 1
libc/misc/ctype/ctype.c

@@ -157,7 +157,7 @@ toupper( int c )
 #else   /* __UCLIBC_HAS_LOCALE__ */
 
 #include <limits.h>
-#include "./ctype.h"
+#include "../locale/_locale.h"
 
 #define _UC_ISCTYPE(c, type) \
 ((c != -1) && ((_uc_ctype_b[(int)((unsigned char)c)] & type) != 0))

+ 1 - 0
libc/misc/ctype/ctype.h → libc/misc/locale/_locale.h

@@ -19,3 +19,4 @@ enum
   ISxdigit = ISbit (7),        /* 128 Hexnumeric.        */
 };
 
+extern const unsigned char *_uc_collate_b;

+ 246 - 75
libc/misc/locale/locale.c

@@ -1,5 +1,5 @@
 /* setlocale.c
- * Load LC_CTYPE locale only special for uclibc
+ * Load LC_CTYPE and LC_COLLATE locale only special for uclibc
  *
  * Written by Vladimir Oleynik (c) vodz@usa.net
  *
@@ -8,103 +8,162 @@
  * used ideas is part of the GNU C Library.
  */
 
-/*
- * No-locale-support setlocale() added.
- */
-
 #include <locale.h>
 #include <stdio.h>      /* NULL, fopen */
 #include <stdlib.h>     /* malloc */
 #include <string.h>
 #include <limits.h>     /* PATH_MAX */
+#include <errno.h>      /* EINVAL */
+#include <unistd.h>     /* get(e)[u|g]id */
 
-#include "../ctype/ctype.h"
-
-#undef TEST_LOCALE
+#include "_locale.h"
 
+static char C_LOCALE_NAME    []="C";
+static char POSIX_LOCALE_NAME[]="POSIX";
+static char composite_name_C []=
+"LC_CTYPE=C;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=C";
 
 #ifdef __UCLIBC_HAS_LOCALE__
 
-static char C_LOCALE_NAME[]="C";
-
 #ifdef TEST_LOCALE
 static const char PATH_LOCALE[]="./";
 #else
 static const char PATH_LOCALE[]=__UCLIBC_LOCALE_DIR;
 #endif
 
-static const char LC_CTYPE_STR[]="/LC_CTYPE";
-
 struct SAV_LOADED_LOCALE {
+	int category;
 	char *locale;
 	const unsigned char *buf;
 	struct SAV_LOADED_LOCALE *next;
 };
 
+static struct SAV_LOADED_LOCALE sll_C_LC_MESSAGES = {
+  LC_MESSAGES, C_LOCALE_NAME,  0, 0
+};
 
-static struct SAV_LOADED_LOCALE sav_loaded_locale [1] = {
-	{ C_LOCALE_NAME, _uc_ctype_b_C, 0 }
+static struct SAV_LOADED_LOCALE sll_C_LC_MONETARY = {
+  LC_MONETARY, C_LOCALE_NAME, 0, &sll_C_LC_MESSAGES
 };
 
-static struct SAV_LOADED_LOCALE * old_locale = sav_loaded_locale;
+static struct SAV_LOADED_LOCALE sll_C_LC_COLLATE = {
+  LC_COLLATE,  C_LOCALE_NAME, 0, &sll_C_LC_MONETARY
+};
 
-static char *set_new_locale(struct SAV_LOADED_LOCALE * s_locale)
-{
-	_uc_ctype_b     = s_locale->buf;
-	_uc_ctype_trans = s_locale->buf+LOCALE_BUF_SIZE/2;
-	old_locale      = s_locale;
-	return            s_locale->locale;
-}
+static struct SAV_LOADED_LOCALE sll_C_LC_TIME = {
+  LC_TIME,     C_LOCALE_NAME, 0, &sll_C_LC_COLLATE
+};
 
-/* Current support only LC_CTYPE or LC_ALL category */
+static struct SAV_LOADED_LOCALE sll_C_LC_NUMERIC = {
+  LC_NUMERIC,  C_LOCALE_NAME, 0, &sll_C_LC_TIME
+};
 
-char *setlocale(int category, const char *locale)
+static struct SAV_LOADED_LOCALE sll_C_LC_CTYPE = {
+  LC_CTYPE,    C_LOCALE_NAME, _uc_ctype_b_C, &sll_C_LC_NUMERIC
+};
+
+static struct SAV_LOADED_LOCALE *sll = &sll_C_LC_CTYPE;
+
+
+#endif /* __UCLIBC_HAS_LOCALE__ */
+
+
+static char *nl_current[LC_ALL+1] = {
+	C_LOCALE_NAME, C_LOCALE_NAME, C_LOCALE_NAME,
+	C_LOCALE_NAME, C_LOCALE_NAME, C_LOCALE_NAME,
+	composite_name_C
+};
+
+static const char * const LC_strs[LC_ALL+1] = {
+	"/LC_CTYPE",
+	"/LC_NUMERIC",
+	"/LC_TIME",
+	"/LC_COLLATE",
+	"/LC_MONETARY",
+	"/LC_MESSAGES",
+	"/LC_ALL"
+};
+
+static char *find_locale(int c, const char **plocale)
 {
-	FILE * fl;
+#ifdef __UCLIBC_HAS_LOCALE__
 	struct SAV_LOADED_LOCALE *cur;
-	struct SAV_LOADED_LOCALE *bottom;
-	char   full_path[PATH_MAX];
-	char * buf = 0;
-	int    l;
+#endif
+	const char *name = *plocale;
+
+	if (name[0] == '\0') {
+	/* The user decides which locale to use by setting environment
+								variables. */
+		name = getenv (&LC_strs[LC_ALL][1]);
+		if (name == NULL || name[0] == '\0')
+			name = getenv (&LC_strs[c][1]);
+		if (name == NULL || name[0] == '\0')
+			name = getenv ("LANG");
+		if (name == NULL || name[0] == '\0')
+			name = C_LOCALE_NAME;
+	}
 
-	if(category!=LC_CTYPE && category!=LC_ALL)
-		return NULL;
+	if (strcmp (name, C_LOCALE_NAME) == 0 ||
+				strcmp (name, POSIX_LOCALE_NAME) == 0 ||
+		/* TODO! */     (c!=LC_CTYPE && c!=LC_COLLATE))
+	    name = C_LOCALE_NAME;
 
-	if(locale==0)
-		return set_new_locale(old_locale);
+	*plocale = name;
 
-	if(strcmp(locale, "POSIX")==0)
-		return set_new_locale(sav_loaded_locale);
-	 else if(*locale == '\0') {
+#ifdef __UCLIBC_HAS_LOCALE__
+	for(cur = sll; cur; cur = cur->next)
+		if(cur->category == c && strcmp(cur->locale, name)==0)
+			return cur->locale;
+#else
+	if(name == C_LOCALE_NAME)
+		return C_LOCALE_NAME;
+#endif
+	return NULL;
+}
 
-		locale = getenv(LC_CTYPE_STR+1);
-		if(locale == 0 || *locale == 0)
-			locale = getenv("LANG");
-		if(locale == 0 || *locale == '\0')
-			return set_new_locale(old_locale);
-		if(strcmp(locale, "POSIX")==0)
-			return set_new_locale(sav_loaded_locale);
-	}
 
-	for(cur = sav_loaded_locale; cur; cur = cur->next)
-		if(strcmp(cur->locale, locale)==0)
-			return set_new_locale(cur);
+#ifdef __UCLIBC_HAS_LOCALE__
+static char *load_locale(int category, const char *locale)
+{
+	FILE * fl;
+	char   full_path[PATH_MAX];
+	char * buf = 0;
+	struct SAV_LOADED_LOCALE *cur;
+	struct SAV_LOADED_LOCALE *bottom;
+	int    bufsize;
+	int    l = strlen(locale);
 
-	l = strlen(locale);
-	if((l+sizeof(PATH_LOCALE)+sizeof(LC_CTYPE_STR))>=PATH_MAX)
+	if((l+sizeof(PATH_LOCALE)+strlen(LC_strs[category]))>=PATH_MAX)
 		return NULL;
 
+	/* Not allow acces suid/sgid binaries to outside PATH_LOCALE */
+	if((geteuid()!=getuid() || getegid()!=getgid()) &&
+		strchr(locale, '/')!=NULL)
+			return NULL;
+
 	strcpy(full_path, PATH_LOCALE);
 	strcat(full_path, locale);
-	strcat(full_path, LC_CTYPE_STR);
+	strcat(full_path, LC_strs[category]);
 	fl = fopen(full_path, "r");
 	if(fl==0)
 		return NULL;
 
-	cur = malloc(sizeof(struct SAV_LOADED_LOCALE)+LOCALE_BUF_SIZE+l);
+	switch(category) {
+		case LC_CTYPE:
+			bufsize = LOCALE_BUF_SIZE;
+			break;
+		case LC_COLLATE:
+			bufsize = 256;
+			break;
+		default:        /* TODO */
+			bufsize = 0;
+			break;
+	}
+
+	cur = malloc(sizeof(struct SAV_LOADED_LOCALE)+bufsize+l+2);
 	if(cur) {
 		buf = (char *)(cur+1);
-		if(fread(buf, 1, LOCALE_BUF_SIZE+1, fl)!=(LOCALE_BUF_SIZE)) {
+		if(bufsize!=0 && fread(buf, 1, bufsize+1, fl)!=(bufsize)) {
 			/* broken locale file */
 			free(cur);
 			buf = 0;
@@ -117,45 +176,157 @@ char *setlocale(int category, const char *locale)
 	fclose(fl);
 	if(cur==0)      /* not enough memory */
 		return NULL;
-	if(buf==0)      /* broken locale file, set to "C" */
-		return set_new_locale(sav_loaded_locale);
+	if(buf==0) {    /* broken locale file, set to "C" */
+		return C_LOCALE_NAME;
+	}
 
-	cur->next = 0;
-	strcpy(buf+LOCALE_BUF_SIZE, locale);
+	cur->next     = 0;
+	cur->buf      = buf;
+	cur->category = category;
+	cur->locale   = buf+bufsize;
+	strcpy(cur->locale, locale);
 
-	bottom = sav_loaded_locale;
+	bottom = sll;
 	while(bottom->next!=0)
 		bottom = bottom->next;
 	bottom->next = cur;
 
-	/* next two line only pedantic */
-	cur->buf    = buf;
-	cur->locale = buf+LOCALE_BUF_SIZE;
+	return cur->locale;
+}
+
+static char *set_composite(int category, char *locale)
+{
+	int i, l;
+	char *old_composite_name = nl_current[LC_ALL];
+	char *new_composite_name;
+	struct SAV_LOADED_LOCALE *cur;
 
-	return set_new_locale(cur);
+	for(l=i=0; i<LC_ALL; i++) {
+		new_composite_name = i == category ? locale : nl_current[i];
+						/* '=' + ';' or '\0' */
+		l += strlen(&LC_strs[i][1])+strlen(new_composite_name)+2;
+	}
+
+	new_composite_name = malloc(l);
+	if(new_composite_name==NULL)
+		return NULL;
+	if(old_composite_name!=composite_name_C)
+		free(old_composite_name);
+	nl_current[category] = locale;  /* change after malloc */
+
+	*new_composite_name = 0;
+	for(i=0; i<LC_ALL; i++) {
+		if(i)
+			strcat(new_composite_name, ";");
+		strcat(new_composite_name, &LC_strs[i][1]);
+		strcat(new_composite_name, "=");
+		strcat(new_composite_name, nl_current[i]);
+	}
+	nl_current[LC_ALL] = new_composite_name;
+
+	/* set locale data for ctype and strcollate functions */
+	for(cur = sll; ; cur = cur->next)
+		if(cur->category == category && cur->locale == locale)
+			break;
+
+	switch(category) {
+		case LC_CTYPE:
+			_uc_ctype_b     = cur->buf;
+			_uc_ctype_trans = cur->buf+LOCALE_BUF_SIZE/2;
+			break;
+		case LC_COLLATE:
+			_uc_collate_b   = cur->buf;
+			break;
+		default:        /* TODO */
+			break;
+	}
+	return locale;
 }
 
-#else /* no locale support */
+#endif /* __UCLIBC_HAS_LOCALE__ */
 
 char *setlocale(int category, const char *locale)
 {
-	/* Allow locales "C" and "" (native).  Both are "C" for our purposes. */
-	if (locale) {
-		if (*locale == 'C') {
-			++locale;
-		}
-		if (*locale) {				/* locale wasn't "C" or ""!! */
-			return NULL;
-		}
-	}
+	char * tl;
+#ifdef __UCLIBC_HAS_LOCALE__
+	int    i;
+#endif
 
-	/* Allow any legal category for "C" or "" (native) locale. */
-	if((category < LC_CTYPE) || (category > LC_ALL)) { /* Illegal category! */
+	if (category < 0 || category > LC_ALL) {
+#ifdef __UCLIBC_HAS_LOCALE__
+einval:
+#endif
+		errno = EINVAL;
 		return NULL;
 	}
 
-	return "C";
-}
+	if(locale==NULL)
+		return nl_current[category];
 
+	if(category!=LC_ALL) {
+		tl = find_locale(category, &locale);
+#ifdef __UCLIBC_HAS_LOCALE__
+		if(tl==NULL)
+			tl = load_locale(category, locale);
+		if(tl) {
+			if(nl_current[category] != tl)
+				tl = set_composite(category, tl);
+		}
 #endif
+		return tl;
+	}
+	/* LC_ALL */
+#ifdef __UCLIBC_HAS_LOCALE__
+	/* The user wants to set all categories.  The desired locales
+	 for the individual categories can be selected by using a
+	 composite locale name.  This is a semi-colon separated list
+	 of entries of the form `CATEGORY=VALUE'.  */
+	tl = strchr(locale, ';');
+	if(tl==NULL) {
+	/* This is not a composite name. Load the data for each category. */
+		for(i=0; i<LC_ALL; i++)
+			setlocale(i, locale);   /* recursive */
+	} else {
+	/* This is a composite name.  Make a copy and split it up. */
+	  const char *newnames[LC_ALL];
+	  char *np;
+	  char *cp;
+
+	  i  = strlen(locale);
+	  np = alloca (i);
+	  if(np)
+		strcpy(np, locale);
+	  else
+		return NULL;
+	  for (i = 0; i < LC_ALL; ++i)
+		newnames[i] = 0;
+
+	  while ((cp = strchr (np, '=')) != NULL) {
+	      for (i = 0; i < LC_ALL; ++i)
+		if ((size_t) (cp - np) == strlen(&LC_strs[i][1])
+		    && memcmp (np, &LC_strs[i][1], (cp - np)) == 0)
+		  break;
+
+	      if (i == LC_ALL)
+		/* Bogus category name.  */
+		goto einval;
+
+	      /* Found the category this clause sets.  */
+	      newnames[i] = ++cp;
+	      cp = strchr (cp, ';');
+	      if (cp != NULL) {
+		  /* Examine the next clause.  */
+		  *cp = '\0';
+		  np = cp + 1;
+	      } else
+		/* This was the last clause.  We are done.  */
+		break;
+	  }
+
+	  for (i = 0; i < LC_ALL; ++i)
+		setlocale(i, newnames[i]);   /* recursive */
+	}
 
+#endif
+	return nl_current[LC_ALL];
+}

+ 4 - 0
libc/string/Makefile

@@ -28,6 +28,10 @@ MOBJ=strlen.o strcat.o strcpy.o strchr.o strcmp.o strncat.o strncpy.o \
 	strncmp.o strrchr.o strdup.o memcpy.o memccpy.o memset.o \
 	memmove.o memcmp.o memchr.o ffs.o strnlen.o strxfrm.o
 
+ifeq ($(HAS_LOCALE),true)
+	MOBJ += strcoll.o
+endif
+
 MSRC1=strsignal.c
 MOBJ1=strsignal.o psignal.o
 

+ 31 - 0
libc/string/string.c

@@ -76,7 +76,38 @@ int strcmp(const char *s1, const char *s2)
 	return c1 - c2;
 }
 
+#ifndef __UCLIBC_HAS_LOCALE__
 __asm__(".weak strcoll; strcoll = strcmp");
+#endif /* __UCLIBC_HAS_LOCALE__ */
+#endif
+
+/***** Function strcoll (locale only, as non-locale is alias of strcmp *****/
+
+#ifdef L_strcoll
+#ifdef __UCLIBC_HAS_LOCALE__
+
+#include "../misc/locale/_locale.h"
+
+const unsigned char *_uc_collate_b;  /* NULL for no collate, strcoll->strcmp */
+
+int strcoll(const char *s1, const char *s2)
+{
+	unsigned char c1, c2;
+
+	while(1) {
+		c1 = (unsigned char) *s1;
+		c2 = (unsigned char) *s2;
+		if(_uc_collate_b) {     /* setuped non-C locale? */
+			c1 = _uc_collate_b[c1];
+			c2 = _uc_collate_b[c2];
+		}
+		if (*s1 == '\0' || c1 != c2)
+			return c1 - c2;
+		s1++;
+		s2++;
+	}
+}
+#endif /* __UCLIBC_HAS_LOCALE__ */
 #endif
 
 /********************** Function strncat ************************************/