changeset 14333:e58fa64818aa

setlocale: Workaround native Windows bug. * lib/setlocale.c (rpl_setlocale): On native Windows, when setlocale succeeds but sets LC_CTYPE to "C", report a failure. * tests/test-setlocale2.sh: New file. * tests/test-setlocale2.c: New file. * modules/setlocale-tests (Files): Add the new files. (Makefile.am): Enable test-setlocale2.sh test. * doc/posix-functions/setlocale.texi: Mention workaround.
author Bruno Haible <bruno@clisp.org>
date Sat, 12 Feb 2011 18:16:10 +0100
parents afd1582f5497
children 2a77dc0f4bd8
files ChangeLog doc/posix-functions/setlocale.texi lib/setlocale.c modules/setlocale-tests tests/test-setlocale2.c tests/test-setlocale2.sh
diffstat 6 files changed, 136 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2011-02-11  Bruno Haible  <bruno@clisp.org>
+
+	setlocale: Workaround native Windows bug.
+	* lib/setlocale.c (rpl_setlocale): On native Windows, when setlocale
+	succeeds but sets LC_CTYPE to "C", report a failure.
+	* tests/test-setlocale2.sh: New file.
+	* tests/test-setlocale2.c: New file.
+	* modules/setlocale-tests (Files): Add the new files.
+	(Makefile.am): Enable test-setlocale2.sh test.
+	* doc/posix-functions/setlocale.texi: Mention workaround.
+
 2011-02-11  Bruno Haible  <bruno@clisp.org>
 
 	Tests for module 'setlocale'.
--- a/doc/posix-functions/setlocale.texi
+++ b/doc/posix-functions/setlocale.texi
@@ -13,6 +13,10 @@
 ignores the environment variables @code{LC_ALL}, @code{@var{category}}, and
 @code{LANG}.
 @item
+On Windows platforms (excluding Cygwin), @code{setlocale(LC_ALL,@var{name})}
+succeeds and sets the LC_CTYPE category to @samp{C} when it does not support
+the encoding, instead of failing.
+@item
 On Windows platforms (excluding Cygwin), @code{setlocale} understands different
 locale names, that are not based on ISO 639 language names and ISO 3166 country
 names.
@@ -25,8 +29,4 @@
 @code{setlocale(LC_ALL,NULL)} always returns @code{"C"}.
 @item
 On Cygwin 1.7.0, only the charset portion of a locale designation is honored.
-@item
-On Windows platforms (excluding Cygwin), @code{setlocale(LC_ALL,@var{name})}
-succeeds and sets the LC_CTYPE category to @samp{C} when it does not support
-the encoding, instead of failing.
 @end itemize
--- a/lib/setlocale.c
+++ b/lib/setlocale.c
@@ -844,6 +844,14 @@
 
           if (setlocale_unixlike (LC_ALL, base_name) == NULL)
             goto fail;
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+          /* On native Windows, setlocale(LC_ALL,...) may succeed but set the
+             LC_CTYPE category to an invalid value ("C") when it does not
+             support the specified encoding.  Report a failure instead.  */
+          if (strchr (base_name, '.') != NULL
+              && strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+            goto fail;
+# endif
 
           for (i = 0; i < sizeof (categories) / sizeof (categories[0]); i++)
             {
@@ -886,7 +894,45 @@
         }
     }
   else
-    return setlocale_single (category, locale);
+    {
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+      if (category == LC_ALL && locale != NULL && strchr (locale, '.') != NULL)
+        {
+          char *saved_locale;
+
+          /* Back up the old locale.  */
+          saved_locale = setlocale (LC_ALL, NULL);
+          if (saved_locale == NULL)
+            return NULL;
+          saved_locale = strdup (saved_locale);
+          if (saved_locale == NULL)
+            return NULL;
+
+          if (setlocale_unixlike (LC_ALL, locale) == NULL)
+            {
+              free (saved_locale);
+              return NULL;
+            }
+
+          /* On native Windows, setlocale(LC_ALL,...) may succeed but set the
+             LC_CTYPE category to an invalid value ("C") when it does not
+             support the specified encoding.  Report a failure instead.  */
+          if (strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+            {
+              if (saved_locale[0] != '\0') /* don't risk an endless recursion */
+                setlocale (LC_ALL, saved_locale);
+              free (saved_locale);
+              return NULL;
+            }
+
+          /* It was really successful.  */
+          free (saved_locale);
+          return setlocale (LC_ALL, NULL);
+        }
+      else
+# endif
+        return setlocale_single (category, locale);
+    }
 }
 
 #endif
--- a/modules/setlocale-tests
+++ b/modules/setlocale-tests
@@ -1,6 +1,8 @@
 Files:
 tests/test-setlocale1.sh
 tests/test-setlocale1.c
+tests/test-setlocale2.sh
+tests/test-setlocale2.c
 tests/signature.h
 tests/macros.h
 m4/locale-fr.m4
@@ -18,10 +20,10 @@
 gt_LOCALE_ZH_CN
 
 Makefile.am:
-TESTS += test-setlocale1.sh
+TESTS += test-setlocale1.sh test-setlocale2.sh
 TESTS_ENVIRONMENT += \
   LOCALE_FR='@LOCALE_FR@' \
   LOCALE_FR_UTF8='@LOCALE_FR_UTF8@' \
   LOCALE_JA='@LOCALE_JA@' \
   LOCALE_ZH_CN='@LOCALE_ZH_CN@'
-check_PROGRAMS += test-setlocale1
+check_PROGRAMS += test-setlocale1 test-setlocale2
new file mode 100644
--- /dev/null
+++ b/tests/test-setlocale2.c
@@ -0,0 +1,55 @@
+/* Test of setting the current locale.
+   Copyright (C) 2011 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <locale.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+main ()
+{
+  /* Try to set the locale by implicitly looking at the LC_ALL environment
+     variable.  */
+  if (setlocale (LC_ALL, "") != NULL)
+    /* It was successful.  Check whether LC_CTYPE is non-trivial.  */
+    if (strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+      {
+        fprintf (stderr, "setlocale did not fail for implicit %s\n",
+                 getenv ("LC_ALL"));
+        return 1;
+      }
+
+  /* Reset the locale.  */
+  if (setlocale (LC_ALL, "C") == NULL)
+    return 1;
+
+  /* Try to set the locale by explicitly looking at the LC_ALL environment
+     variable.  */
+  if (setlocale (LC_ALL, getenv ("LC_ALL")) != NULL)
+    /* It was successful.  Check whether LC_CTYPE is non-trivial.  */
+    if (strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+      {
+        fprintf (stderr, "setlocale did not fail for explicit %s\n",
+                 getenv ("LC_ALL"));
+        return 1;
+      }
+
+  return 0;
+}
new file mode 100755
--- /dev/null
+++ b/tests/test-setlocale2.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Test locale names with likely unsupported encoding in Unix syntax.
+for name in ar_SA.ISO-8859-1 fr_FR.CP1251 zh_TW.GB18030 zh_CN.BIG5; do
+  LC_ALL=$name ./test-setlocale2${EXEEXT} 1 || exit 1
+done
+
+# Test locale names with likely unsupported encoding in native Windows syntax.
+for name in "Arabic_Saudi Arabia.1252" "Arabic_Saudi Arabia.65001" \
+            French_France.65001 Japanese_Japan.65001 Turkish_Turkey.65001 \
+            Chinese_Taiwan.65001 Chinese_China.54936 Chinese_China.65001; do
+  LC_ALL=$name ./test-setlocale2${EXEEXT} 1 || exit 1
+done
+
+exit 0