On 2026-08-21 01:14, Kirill Makurin wrote:
I believe that original impression of setting LC_CTYPE to "C" comes from "C" 
being the active locale before problematic `setlocale` call.
Are you suggesting something like the attached patch to Gnulib? If not, what 
would be a better patch?

Is it possible that sometimes setlocale leaves LC_CTYPE alone, and sometimes sets it to 
"C"? If so, the attached code patch would handle either case, but the 
documentation patch would need changing.

I have not installed the attached patch, as I don't use MS-Windows and can't 
easily test it, and it's not obviously OK.
From 85471114b8e46f200afb94b532f26a58cdfbc05c Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Fri, 21 Aug 2026 09:44:22 -0700
Subject: [PATCH] setlocale: port better to MS-Windows

Problem reported by Kirill Makurin in:
https://lists.gnu.org/r/bug-gnulib/2026-08/msg00247.html
* lib/setlocale.c (setlocale_improved):
When setlocale(LC_ALL,...) misbehaves on MS-Windows, it
can leave LC_CTYPE alone, instead of setting it to "C".
---
 ChangeLog                          |  9 ++++++++
 doc/posix-functions/setlocale.texi |  4 ++--
 lib/setlocale.c                    | 36 +++++++++++++++++++++---------
 modules/setlocale                  |  1 +
 4 files changed, 38 insertions(+), 12 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 44345023a7..6cc55b7015 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2026-08-21  Paul Eggert  <[email protected]>
+
+	setlocale: port better to MS-Windows
+	Problem reported by Kirill Makurin in:
+	https://lists.gnu.org/r/bug-gnulib/2026-08/msg00247.html
+	* lib/setlocale.c (setlocale_improved):
+	When setlocale(LC_ALL,...) misbehaves on MS-Windows, it
+	can leave LC_CTYPE alone, instead of setting it to "C".
+
 2026-08-21  Bruno Haible  <[email protected]>
 
 	getugroups: Clarify and optimize.
diff --git a/doc/posix-functions/setlocale.texi b/doc/posix-functions/setlocale.texi
index 25f8a9500a..3426dabe25 100644
--- a/doc/posix-functions/setlocale.texi
+++ b/doc/posix-functions/setlocale.texi
@@ -16,8 +16,8 @@ and @code{setlocale(@var{category},NULL)} both ignore the environment variables
 @code{LC_ALL}, @code{@var{category}}, and @code{LANG}.
 @item
 On Windows platforms (excluding Cygwin) and Cygwin 1.5.x,
-@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.
+when an encoding is not supported @code{setlocale(LC_ALL,@var{name})}
+succeeds without changing the LC_CTYPE category, 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
diff --git a/lib/setlocale.c b/lib/setlocale.c
index f40b6295d4..93322672bd 100644
--- a/lib/setlocale.c
+++ b/lib/setlocale.c
@@ -1452,6 +1452,19 @@ setlocale_improved (int category, const char *locale)
           if (base_name == NULL)
             base_name = gl_locale_name_default ();
 
+#  if defined _WIN32 && ! defined __CYGWIN__
+          /* On native Windows, setlocale(LC_ALL,...) may succeed but
+             leave the LC_CTYPE category unchanged when it does not
+             support the specified encoding.  To detect this below,
+             temporarily set the LC_CTYPE category to "C" first.  */
+          bool dotted_locale = strchr (base_name, '.') != NULL;
+          if (dotted_locale && setlocale (LC_CTYPE, "C") == NULL)
+            {
+              free (saved_locale);
+              return NULL;
+            }
+#  endif
+
           unsigned int i;
           if (setlocale_unixlike (LC_ALL, base_name) != NULL)
             {
@@ -1469,11 +1482,10 @@ setlocale_improved (int category, const char *locale)
               i = 0;
             }
 #  if 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
-              && streq (setlocale (LC_CTYPE, NULL), "C"))
+          /* Report a failure if setlocale(LC_ALL,...) succeeded but
+             left the LC_CTYPE category unchanged because it does not
+             support the specified encoding.  */
+          if (dotted_locale && streq (setlocale (LC_CTYPE, NULL), "C"))
             goto fail;
 #  endif
 
@@ -1685,16 +1697,20 @@ setlocale_improved (int category, const char *locale)
                   if (saved_locale == NULL)
                     return NULL;
 
-                  if (setlocale_unixlike (LC_ALL, native_locale) == NULL)
+                  /* On native Windows, setlocale(LC_ALL,...) may succeed but
+                     leave the LC_CTYPE category unchanged when it does not
+                     support the specified encoding.  To detect this below,
+                     temporarily set the LC_CTYPE category to "C" first.  */
+                  if (setlocale (LC_CTYPE, "C") == NULL
+                      || setlocale_unixlike (LC_ALL, native_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.  */
+                  /* Report a failure if setlocale(LC_ALL,...) succeeded but
+                     left the LC_CTYPE category unchanged because it does not
+                     support the specified encoding.  */
                   if (streq (setlocale (LC_CTYPE, NULL), "C"))
                     {
                       /* Don't risk an endless recursion.  */
diff --git a/modules/setlocale b/modules/setlocale
index aff365ac95..76b7dd3c95 100644
--- a/modules/setlocale
+++ b/modules/setlocale
@@ -6,6 +6,7 @@ lib/setlocale.c
 m4/setlocale.m4
 
 Depends-on:
+bool
 locale-h
 stdcountof-h
 streq
-- 
2.53.0

Reply via email to