I don't believe it. I just don't believe it.
Solaris, with its fame of being seminal regarding internationalization,
ships a buggy strcasecmp() function. The bug is reproducible in Solaris 10,
Solaris 11.4, Solaris 11 OpenIndiana, Solaris 11 OmniOS.

========================= foo.c ========================
#include <stdio.h>
#include <ctype.h>
#include <locale.h>
#include <strings.h>

int main ()
{
  if (setlocale (LC_ALL, "fr_FR.ISO8859-1") == NULL)
    return 1;
  int c1 = (unsigned char) '\311';
  int c2 = (unsigned char) '\351';

  printf ("0x%02X -> 0x%02X, 0x%02X\n", c1, tolower (c1), toupper (c1));
  printf ("0x%02X -> 0x%02X, 0x%02X\n", c2, tolower (c2), toupper (c2));
  printf ("strcasecmp  -> %d\n", strcasecmp ("Fej\311r", "Fej\351r"));
  printf ("strncasecmp -> %d\n", strncasecmp ("Fej\311r", "Fej\351r", 5));
}
=======================================================================
$ gcc -Wall foo.c
$ ./a

Expected output:
0xC9 -> 0xE9, 0xC9
0xE9 -> 0xE9, 0xC9
strcasecmp  -> 0
strncasecmp -> 0

Actual output:
0xC9 -> 0xE9, 0xC9
0xE9 -> 0xE9, 0xC9
strcasecmp  -> -32
strncasecmp -> -32

I'm committing these workarounds.


2025-02-16  Bruno Haible  <br...@clisp.org>

        strncasecmp: Add tests.
        * tests/test-strncasecmp-1.sh: New file.
        * tests/test-strncasecmp-2.sh: New file.
        * tests/test-strncasecmp.c: New file.
        * modules/strncasecmp-tests: New file.

        strncasecmp: Work around Solaris, Cygwin bug.
        * lib/strings.in.h (strncasecmp): Consider REPLACE_STRNCASECMP. Use the
        usual idioms.
        * m4/strings_h.m4 (gl_STRINGS_H_DEFAULTS): Initialize HAVE_STRNCASECMP,
        REPLACE_STRNCASECMP.
        * m4/strncasecmp.m4 (gl_FUNC_STRNCASECMP): Invoke gl_STRNCASECMP_WORKS.
        Set REPLACE_STRNCASECMP. Assume that HAVE_STRNCASECMP is initialized.
        * modules/strncasecmp (Files): Add m4/strcasecmp.m4.
        (configure.ac): Consider REPLACE_STRNCASECMP.
        * modules/strings-h (Makefile.am): Substitute HAVE_STRNCASECMP,
        REPLACE_STRNCASECMP.
        * doc/posix-functions/strncasecmp.texi: Mention the Solaris, Cygwin bug.

2025-02-16  Bruno Haible  <br...@clisp.org>

        strcasecmp: Add tests.
        * tests/test-strcasecmp-1.sh: New file.
        * tests/test-strcasecmp-2.sh: New file.
        * tests/test-strcasecmp.c: New file.
        * modules/strcasecmp-tests: New file.

        strcasecmp: Work around Solaris, Cygwin bug.
        * lib/strings.in.h (strcasecmp): Consider REPLACE_STRCASECMP. Use the
        usual idioms.
        * m4/strings_h.m4 (gl_STRINGS_H_DEFAULTS): Initialize
        REPLACE_STRCASECMP.
        * m4/strcasecmp.m4 (gl_STRCASECMP_WORKS): New macro.
        (gl_FUNC_STRCASECMP): Invoke it. Set REPLACE_STRCASECMP.
        * modules/strcasecmp (configure.ac): Consider REPLACE_STRCASECMP.
        * modules/strings-h (Makefile.am): Substitute REPLACE_STRCASECMP.
        * doc/posix-functions/strcasecmp.texi: Mention the Solaris, Cygwin bug.

2025-02-16  Bruno Haible  <br...@clisp.org>

        strcasecmp, strncasecmp: Fix header reference.
        * lib/strcasecmp.c: Include <strings.h>, not <string.h>.
        * lib/strncasecmp.c: Likewise.

>From 6abed08f2cffaba927c105fc931a68984d2a84a6 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 16 Feb 2025 14:12:26 +0100
Subject: [PATCH 1/5] strcasecmp, strncasecmp: Fix header reference.

* lib/strcasecmp.c: Include <strings.h>, not <string.h>.
* lib/strncasecmp.c: Likewise.
---
 ChangeLog         | 6 ++++++
 lib/strcasecmp.c  | 2 +-
 lib/strncasecmp.c | 2 +-
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 549db282db..9669e2210d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-02-16  Bruno Haible  <br...@clisp.org>
+
+	strcasecmp, strncasecmp: Fix header reference.
+	* lib/strcasecmp.c: Include <strings.h>, not <string.h>.
+	* lib/strncasecmp.c: Likewise.
+
 2025-02-16  Bruno Haible  <br...@clisp.org>
 
 	realloc: Fix link error in C++ mode on CentOS 5 (regression 2024-11-04).
diff --git a/lib/strcasecmp.c b/lib/strcasecmp.c
index fe21a6d5b3..16626d4d09 100644
--- a/lib/strcasecmp.c
+++ b/lib/strcasecmp.c
@@ -17,7 +17,7 @@
 #include <config.h>
 
 /* Specification.  */
-#include <string.h>
+#include <strings.h>
 
 #include <ctype.h>
 #include <limits.h>
diff --git a/lib/strncasecmp.c b/lib/strncasecmp.c
index 7a0115d050..7d7c5b7f04 100644
--- a/lib/strncasecmp.c
+++ b/lib/strncasecmp.c
@@ -17,7 +17,7 @@
 #include <config.h>
 
 /* Specification.  */
-#include <string.h>
+#include <strings.h>
 
 #include <ctype.h>
 #include <limits.h>
-- 
2.43.0

>From 9980b9e526d3436524fb148fc8ed8ebbd15e76dc Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 16 Feb 2025 17:59:48 +0100
Subject: [PATCH 2/5] strcasecmp: Work around Solaris, Cygwin bug.

* lib/strings.in.h (strcasecmp): Consider REPLACE_STRCASECMP. Use the
usual idioms.
* m4/strings_h.m4 (gl_STRINGS_H_DEFAULTS): Initialize
REPLACE_STRCASECMP.
* m4/strcasecmp.m4 (gl_STRCASECMP_WORKS): New macro.
(gl_FUNC_STRCASECMP): Invoke it. Set REPLACE_STRCASECMP.
* modules/strcasecmp (configure.ac): Consider REPLACE_STRCASECMP.
* modules/strings-h (Makefile.am): Substitute REPLACE_STRCASECMP.
* doc/posix-functions/strcasecmp.texi: Mention the Solaris, Cygwin bug.
---
 ChangeLog                           | 13 ++++++++
 doc/posix-functions/strcasecmp.texi |  5 +++
 lib/strings.in.h                    | 22 ++++++++++---
 m4/strcasecmp.m4                    | 50 +++++++++++++++++++++++++++--
 m4/strings_h.m4                     |  3 +-
 modules/strcasecmp                  |  3 +-
 modules/strings-h                   |  1 +
 7 files changed, 89 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9669e2210d..bf5e91746b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2025-02-16  Bruno Haible  <br...@clisp.org>
+
+	strcasecmp: Work around Solaris, Cygwin bug.
+	* lib/strings.in.h (strcasecmp): Consider REPLACE_STRCASECMP. Use the
+	usual idioms.
+	* m4/strings_h.m4 (gl_STRINGS_H_DEFAULTS): Initialize
+	REPLACE_STRCASECMP.
+	* m4/strcasecmp.m4 (gl_STRCASECMP_WORKS): New macro.
+	(gl_FUNC_STRCASECMP): Invoke it. Set REPLACE_STRCASECMP.
+	* modules/strcasecmp (configure.ac): Consider REPLACE_STRCASECMP.
+	* modules/strings-h (Makefile.am): Substitute REPLACE_STRCASECMP.
+	* doc/posix-functions/strcasecmp.texi: Mention the Solaris, Cygwin bug.
+
 2025-02-16  Bruno Haible  <br...@clisp.org>
 
 	strcasecmp, strncasecmp: Fix header reference.
diff --git a/doc/posix-functions/strcasecmp.texi b/doc/posix-functions/strcasecmp.texi
index af640589d9..8c07af89e9 100644
--- a/doc/posix-functions/strcasecmp.texi
+++ b/doc/posix-functions/strcasecmp.texi
@@ -12,6 +12,11 @@
 @item
 This function is missing on some platforms:
 MSVC 14.
+@item
+This function uses the case mappings of a wrong locale on some platforms:
+Solaris 11.4,
+@c https://sourceware.org/pipermail/cygwin/2025-February/257347.html
+Cygwin 3.5.6.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/lib/strings.in.h b/lib/strings.in.h
index cad01dc34b..b356115181 100644
--- a/lib/strings.in.h
+++ b/lib/strings.in.h
@@ -77,9 +77,23 @@ _GL_WARN_ON_USE (ffs, "ffs is not portable - use the ffs module");
    greater than zero if S1 is lexicographically less than, equal to or greater
    than S2.
    Note: This function does not work in multibyte locales.  */
-# if ! @HAVE_STRCASECMP@
-extern int strcasecmp (char const *s1, char const *s2)
-     _GL_ARG_NONNULL ((1, 2));
+# if @REPLACE_STRCASECMP@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   undef strcasecmp
+#   define strcasecmp rpl_strcasecmp
+#  endif
+_GL_FUNCDECL_RPL (strcasecmp, int, (const char *, const char *),
+                                   _GL_ARG_NONNULL ((1, 2)));
+_GL_CXXALIAS_RPL (strcasecmp, int, (const char *, const char *));
+# else
+#  if !@HAVE_STRCASECMP@
+_GL_FUNCDECL_SYS (strcasecmp, int, (const char *, const char *),
+                                   _GL_ARG_NONNULL ((1, 2)));
+#  endif
+_GL_CXXALIAS_SYS (strcasecmp, int, (const char *, const char *));
+# endif
+# if __GLIBC__ >= 2
+_GL_CXXALIASWARN (strcasecmp);
 # endif
 #elif defined GNULIB_POSIXCHECK
 /* strcasecmp() does not work with multibyte strings:
@@ -88,7 +102,7 @@ extern int strcasecmp (char const *s1, char const *s2)
 # undef strcasecmp
 # if HAVE_RAW_DECL_STRCASECMP
 _GL_WARN_ON_USE (strcasecmp, "strcasecmp cannot work correctly on character "
-                 "strings in multibyte locales - "
+                 "strings in multibyte locales and is unportable - "
                  "use mbscasecmp if you care about "
                  "internationalization, or use c_strcasecmp "
                  "(gnulib module c-strcasecmp) if you want a locale "
diff --git a/m4/strcasecmp.m4 b/m4/strcasecmp.m4
index 0bbcc5ebe2..e40ee5d14a 100644
--- a/m4/strcasecmp.m4
+++ b/m4/strcasecmp.m4
@@ -1,5 +1,5 @@
 # strcasecmp.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 2002-2025 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -10,11 +10,57 @@ AC_DEFUN([gl_FUNC_STRCASECMP]
 [
   AC_REQUIRE([gl_STRINGS_H_DEFAULTS])
   AC_CHECK_FUNCS([strcasecmp])
-  if test $ac_cv_func_strcasecmp = no; then
+  if test $ac_cv_func_strcasecmp = yes; then
+    gl_STRCASECMP_WORKS
+    case "$gl_cv_func_strcasecmp_works" in
+      *yes) ;;
+      *) REPLACE_STRCASECMP=1 ;;
+    esac
+  else
     HAVE_STRCASECMP=0
   fi
 ])
 
+AC_DEFUN([gl_STRCASECMP_WORKS],
+[
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  AC_CACHE_CHECK([whether strcasecmp works],
+    [gl_cv_func_strcasecmp_works],
+    [dnl Prepare a guess, used when cross-compiling or when specific locales
+     dnl are not available.
+     case "$host_os" in
+       solaris* | cygwin*)
+         gl_cv_func_strcasecmp_works="guessing no" ;;
+       *)
+         gl_cv_func_strcasecmp_works="guessing yes" ;;
+     esac
+     AC_RUN_IFELSE(
+       [AC_LANG_SOURCE([[
+#include <stdio.h>
+#include <ctype.h>
+#include <locale.h>
+#include <strings.h>
+int main ()
+{
+  if (setlocale (LC_ALL, "fr_FR.ISO-8859-1") != NULL
+      || setlocale (LC_ALL, "fr_FR.ISO8859-1") != NULL)
+    {
+      int c1 = (unsigned char) '\311';
+      int c2 = (unsigned char) '\351';
+      if (tolower (c1) == c2 && toupper (c2) == c1)
+        return strcasecmp ("Fej\311r", "Fej\351r") != 0;
+    }
+  return 2;
+}]])],
+       [gl_cv_func_strcasecmp_works=yes],
+       [if test $? = 1; then
+          gl_cv_func_strcasecmp_works=no
+        fi
+       ],
+       [])
+    ])
+])
+
 # Prerequisites of lib/strcasecmp.c.
 AC_DEFUN([gl_PREREQ_STRCASECMP], [
   :
diff --git a/m4/strings_h.m4 b/m4/strings_h.m4
index 25596aa445..b88e1105fb 100644
--- a/m4/strings_h.m4
+++ b/m4/strings_h.m4
@@ -1,5 +1,5 @@
 # strings_h.m4
-# serial 10
+# serial 11
 dnl Copyright (C) 2007, 2009-2025 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -63,4 +63,5 @@ AC_DEFUN([gl_STRINGS_H_DEFAULTS]
   HAVE_FFS=1;              AC_SUBST([HAVE_FFS])
   HAVE_STRCASECMP=1;       AC_SUBST([HAVE_STRCASECMP])
   HAVE_DECL_STRNCASECMP=1; AC_SUBST([HAVE_DECL_STRNCASECMP])
+  REPLACE_STRCASECMP=1;    AC_SUBST([REPLACE_STRCASECMP])
 ])
diff --git a/modules/strcasecmp b/modules/strcasecmp
index b226990b2a..34d3050374 100644
--- a/modules/strcasecmp
+++ b/modules/strcasecmp
@@ -10,7 +10,8 @@ strings-h
 
 configure.ac:
 gl_FUNC_STRCASECMP
-gl_CONDITIONAL([GL_COND_OBJ_STRCASECMP], [test $HAVE_STRCASECMP = 0])
+gl_CONDITIONAL([GL_COND_OBJ_STRCASECMP],
+               [test $HAVE_STRCASECMP = 0 || test $REPLACE_STRCASECMP = 1])
 AM_COND_IF([GL_COND_OBJ_STRCASECMP], [
   gl_PREREQ_STRCASECMP
 ])
diff --git a/modules/strings-h b/modules/strings-h
index 7b3cc6ad44..5c6c6736c2 100644
--- a/modules/strings-h
+++ b/modules/strings-h
@@ -38,6 +38,7 @@ strings.h: strings.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(WARN_ON_USE
 	      -e 's|@''HAVE_FFS''@|$(HAVE_FFS)|g' \
 	      -e 's|@''HAVE_STRCASECMP''@|$(HAVE_STRCASECMP)|g' \
 	      -e 's|@''HAVE_DECL_STRNCASECMP''@|$(HAVE_DECL_STRNCASECMP)|g' \
+	      -e 's|@''REPLACE_STRCASECMP''@|$(REPLACE_STRCASECMP)|g' \
 	      -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
 	      -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
 	      -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \
-- 
2.43.0

>From fc6bec11ffd3761d87e1e4b0c21697a3c6771694 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 16 Feb 2025 18:02:00 +0100
Subject: [PATCH 3/5] strcasecmp: Add tests.

* tests/test-strcasecmp-1.sh: New file.
* tests/test-strcasecmp-2.sh: New file.
* tests/test-strcasecmp.c: New file.
* modules/strcasecmp-tests: New file.
---
 ChangeLog                  |  6 ++++
 modules/strcasecmp-tests   | 21 +++++++++++
 tests/test-strcasecmp-1.sh |  9 +++++
 tests/test-strcasecmp-2.sh | 15 ++++++++
 tests/test-strcasecmp.c    | 73 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 124 insertions(+)
 create mode 100644 modules/strcasecmp-tests
 create mode 100755 tests/test-strcasecmp-1.sh
 create mode 100755 tests/test-strcasecmp-2.sh
 create mode 100644 tests/test-strcasecmp.c

diff --git a/ChangeLog b/ChangeLog
index bf5e91746b..052ec74b9d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2025-02-16  Bruno Haible  <br...@clisp.org>
 
+	strcasecmp: Add tests.
+	* tests/test-strcasecmp-1.sh: New file.
+	* tests/test-strcasecmp-2.sh: New file.
+	* tests/test-strcasecmp.c: New file.
+	* modules/strcasecmp-tests: New file.
+
 	strcasecmp: Work around Solaris, Cygwin bug.
 	* lib/strings.in.h (strcasecmp): Consider REPLACE_STRCASECMP. Use the
 	usual idioms.
diff --git a/modules/strcasecmp-tests b/modules/strcasecmp-tests
new file mode 100644
index 0000000000..aeff0381a0
--- /dev/null
+++ b/modules/strcasecmp-tests
@@ -0,0 +1,21 @@
+Files:
+tests/test-strcasecmp-1.sh
+tests/test-strcasecmp-2.sh
+tests/test-strcasecmp.c
+tests/signature.h
+tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
+
+Depends-on:
+setlocale
+
+configure.ac:
+gt_LOCALE_FR
+
+Makefile.am:
+TESTS += test-strcasecmp-1.sh test-strcasecmp-2.sh
+TESTS_ENVIRONMENT += \
+  LOCALE_FR='@LOCALE_FR@'
+check_PROGRAMS += test-strcasecmp
+test_strcasecmp_LDADD = $(LDADD) $(SETLOCALE_LIB)
diff --git a/tests/test-strcasecmp-1.sh b/tests/test-strcasecmp-1.sh
new file mode 100755
index 0000000000..1a8ad80ac7
--- /dev/null
+++ b/tests/test-strcasecmp-1.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Test in the POSIX locale.
+LC_ALL=C \
+${CHECKER} ./test-strcasecmp${EXEEXT} 1 || exit 1
+LC_ALL=POSIX \
+${CHECKER} ./test-strcasecmp${EXEEXT} 1 || exit 1
+
+exit 0
diff --git a/tests/test-strcasecmp-2.sh b/tests/test-strcasecmp-2.sh
new file mode 100755
index 0000000000..b590a7a069
--- /dev/null
+++ b/tests/test-strcasecmp-2.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Test in an ISO-8859-1 or ISO-8859-15 locale.
+: "${LOCALE_FR=fr_FR}"
+if test $LOCALE_FR = none; then
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: no traditional french locale is installed"
+  else
+    echo "Skipping test: no traditional french locale is supported"
+  fi
+  exit 77
+fi
+
+LC_ALL=$LOCALE_FR \
+${CHECKER} ./test-strcasecmp${EXEEXT} 2
diff --git a/tests/test-strcasecmp.c b/tests/test-strcasecmp.c
new file mode 100644
index 0000000000..0be6ffaecb
--- /dev/null
+++ b/tests/test-strcasecmp.c
@@ -0,0 +1,73 @@
+/* Test of strcasecmp() function.
+   Copyright (C) 2008-2025 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 <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <strings.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (strcasecmp, int, (const char *, const char *));
+
+#include <locale.h>
+#include <stdlib.h>
+
+#include "macros.h"
+
+int
+main (int argc, char *argv[])
+{
+  /* configure should already have checked that the locale is supported.  */
+  if (setlocale (LC_ALL, "") == NULL)
+    return 1;
+
+  ASSERT (strcasecmp ("paragraph", "Paragraph") == 0);
+
+  ASSERT (strcasecmp ("paragrapH", "parAgRaph") == 0);
+
+  ASSERT (strcasecmp ("paragraph", "paraLyzed") < 0);
+  ASSERT (strcasecmp ("paraLyzed", "paragraph") > 0);
+
+  ASSERT (strcasecmp ("para", "paragraph") < 0);
+  ASSERT (strcasecmp ("paragraph", "para") > 0);
+
+  if (argc > 1)
+    switch (argv[1][0])
+      {
+      case '1':
+        /* C or POSIX locale.  */
+        return test_exit_status;
+
+      case '2':
+        /* Locale encoding is ISO-8859-1 or ISO-8859-15.  */
+
+        /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
+        /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
+        ASSERT (strcasecmp ("Fej\311r", "Fej\351r") == 0);
+        ASSERT (strcasecmp ("Fej\351r", "Fej\311r") == 0);
+        ASSERT (strcasecmp ("Fejer", "Fej\311r") < 0);
+        ASSERT (strcasecmp ("Fej\311r", "Fejer") > 0);
+
+        /* Compare with U+00D7 MULTIPLICATION SIGN */
+        ASSERT (strcasecmp ("Fej\311r", "Fej\327") > 0);
+        ASSERT (strcasecmp ("Fej\327", "Fej\311r") < 0);
+        ASSERT (strcasecmp ("Fej\351r", "Fej\327") > 0);
+        ASSERT (strcasecmp ("Fej\327", "Fej\351r") < 0);
+
+        return test_exit_status;
+      }
+
+  return 1;
+}
-- 
2.43.0

>From 557f0e4958ca54ce1e660d611d8f4a872e2a981f Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 16 Feb 2025 18:22:31 +0100
Subject: [PATCH 4/5] strncasecmp: Work around Solaris, Cygwin bug.

* lib/strings.in.h (strncasecmp): Consider REPLACE_STRNCASECMP. Use the
usual idioms.
* m4/strings_h.m4 (gl_STRINGS_H_DEFAULTS): Initialize HAVE_STRNCASECMP,
REPLACE_STRNCASECMP.
* m4/strncasecmp.m4 (gl_FUNC_STRNCASECMP): Invoke gl_STRNCASECMP_WORKS.
Set REPLACE_STRNCASECMP. Assume that HAVE_STRNCASECMP is initialized.
* modules/strncasecmp (Files): Add m4/strcasecmp.m4.
(configure.ac): Consider REPLACE_STRNCASECMP.
* modules/strings-h (Makefile.am): Substitute HAVE_STRNCASECMP,
REPLACE_STRNCASECMP.
* doc/posix-functions/strncasecmp.texi: Mention the Solaris, Cygwin bug.
---
 ChangeLog                            | 15 +++++++++++++++
 doc/posix-functions/strncasecmp.texi |  5 +++++
 lib/strings.in.h                     | 24 ++++++++++++------------
 m4/strings_h.m4                      |  4 +++-
 m4/strncasecmp.m4                    |  9 +++++++--
 modules/strings-h                    |  2 ++
 modules/strncasecmp                  |  4 +++-
 7 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 052ec74b9d..e73bdd7804 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2025-02-16  Bruno Haible  <br...@clisp.org>
+
+	strncasecmp: Work around Solaris, Cygwin bug.
+	* lib/strings.in.h (strncasecmp): Consider REPLACE_STRNCASECMP. Use the
+	usual idioms.
+	* m4/strings_h.m4 (gl_STRINGS_H_DEFAULTS): Initialize HAVE_STRNCASECMP,
+	REPLACE_STRNCASECMP.
+	* m4/strncasecmp.m4 (gl_FUNC_STRNCASECMP): Invoke gl_STRNCASECMP_WORKS.
+	Set REPLACE_STRNCASECMP. Assume that HAVE_STRNCASECMP is initialized.
+	* modules/strncasecmp (Files): Add m4/strcasecmp.m4.
+	(configure.ac): Consider REPLACE_STRNCASECMP.
+	* modules/strings-h (Makefile.am): Substitute HAVE_STRNCASECMP,
+	REPLACE_STRNCASECMP.
+	* doc/posix-functions/strncasecmp.texi: Mention the Solaris, Cygwin bug.
+
 2025-02-16  Bruno Haible  <br...@clisp.org>
 
 	strcasecmp: Add tests.
diff --git a/doc/posix-functions/strncasecmp.texi b/doc/posix-functions/strncasecmp.texi
index 1fc9e0307a..b52cedb4f3 100644
--- a/doc/posix-functions/strncasecmp.texi
+++ b/doc/posix-functions/strncasecmp.texi
@@ -12,6 +12,11 @@
 @item
 This function is missing on some platforms:
 MSVC 14.
+@item
+This function uses the case mappings of a wrong locale on some platforms:
+Solaris 11.4,
+@c https://sourceware.org/pipermail/cygwin/2025-February/257347.html
+Cygwin 3.5.6.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/lib/strings.in.h b/lib/strings.in.h
index b356115181..12a9c40a7d 100644
--- a/lib/strings.in.h
+++ b/lib/strings.in.h
@@ -77,23 +77,23 @@ _GL_WARN_ON_USE (ffs, "ffs is not portable - use the ffs module");
    greater than zero if S1 is lexicographically less than, equal to or greater
    than S2.
    Note: This function does not work in multibyte locales.  */
-# if @REPLACE_STRCASECMP@
+# if @REPLACE_STRNCASECMP@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
-#   undef strcasecmp
-#   define strcasecmp rpl_strcasecmp
+#   undef strncasecmp
+#   define strncasecmp rpl_strncasecmp
 #  endif
-_GL_FUNCDECL_RPL (strcasecmp, int, (const char *, const char *),
-                                   _GL_ARG_NONNULL ((1, 2)));
-_GL_CXXALIAS_RPL (strcasecmp, int, (const char *, const char *));
+_GL_FUNCDECL_RPL (strncasecmp, int, (const char *, const char *, size_t),
+                                    _GL_ARG_NONNULL ((1, 2)));
+_GL_CXXALIAS_RPL (strncasecmp, int, (const char *, const char *, size_t));
 # else
-#  if !@HAVE_STRCASECMP@
-_GL_FUNCDECL_SYS (strcasecmp, int, (const char *, const char *),
-                                   _GL_ARG_NONNULL ((1, 2)));
+#  if !@HAVE_STRNCASECMP@
+_GL_FUNCDECL_SYS (strncasecmp, int, (const char *, const char *, size_t),
+                                    _GL_ARG_NONNULL ((1, 2)));
 #  endif
-_GL_CXXALIAS_SYS (strcasecmp, int, (const char *, const char *));
+_GL_CXXALIAS_SYS (strncasecmp, int, (const char *, const char *, size_t));
 # endif
 # if __GLIBC__ >= 2
-_GL_CXXALIASWARN (strcasecmp);
+_GL_CXXALIASWARN (strncasecmp);
 # endif
 #elif defined GNULIB_POSIXCHECK
 /* strcasecmp() does not work with multibyte strings:
@@ -126,7 +126,7 @@ extern int strncasecmp (char const *s1, char const *s2, size_t n)
 # undef strncasecmp
 # if HAVE_RAW_DECL_STRNCASECMP
 _GL_WARN_ON_USE (strncasecmp, "strncasecmp cannot work correctly on character "
-                 "strings in multibyte locales - "
+                 "strings in multibyte locales and is unportable - "
                  "use mbsncasecmp or mbspcasecmp if you care about "
                  "internationalization, or use c_strncasecmp "
                  "(gnulib module c-strncasecmp) if you want a locale "
diff --git a/m4/strings_h.m4 b/m4/strings_h.m4
index b88e1105fb..f635575012 100644
--- a/m4/strings_h.m4
+++ b/m4/strings_h.m4
@@ -1,5 +1,5 @@
 # strings_h.m4
-# serial 11
+# serial 12
 dnl Copyright (C) 2007, 2009-2025 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -62,6 +62,8 @@ AC_DEFUN([gl_STRINGS_H_DEFAULTS]
   dnl Assume proper GNU behavior unless another module says otherwise.
   HAVE_FFS=1;              AC_SUBST([HAVE_FFS])
   HAVE_STRCASECMP=1;       AC_SUBST([HAVE_STRCASECMP])
+  HAVE_STRNCASECMP=1;      AC_SUBST([HAVE_STRNCASECMP])
   HAVE_DECL_STRNCASECMP=1; AC_SUBST([HAVE_DECL_STRNCASECMP])
   REPLACE_STRCASECMP=1;    AC_SUBST([REPLACE_STRCASECMP])
+  REPLACE_STRNCASECMP=1;   AC_SUBST([REPLACE_STRNCASECMP])
 ])
diff --git a/m4/strncasecmp.m4 b/m4/strncasecmp.m4
index d27a4a0c5e..c7c8b24036 100644
--- a/m4/strncasecmp.m4
+++ b/m4/strncasecmp.m4
@@ -1,5 +1,5 @@
 # strncasecmp.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 2002-2025 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -11,7 +11,12 @@ AC_DEFUN([gl_FUNC_STRNCASECMP]
   AC_REQUIRE([gl_STRINGS_H_DEFAULTS])
   AC_CHECK_FUNCS([strncasecmp])
   if test $ac_cv_func_strncasecmp = yes; then
-    HAVE_STRNCASECMP=1
+    dnl Assume that strncasecmp and strcasecmp share the same bugs.
+    gl_STRCASECMP_WORKS
+    case "$gl_cv_func_strcasecmp_works" in
+      *yes) ;;
+      *) REPLACE_STRNCASECMP=1 ;;
+    esac
   else
     HAVE_STRNCASECMP=0
   fi
diff --git a/modules/strings-h b/modules/strings-h
index 5c6c6736c2..2e0f0c1012 100644
--- a/modules/strings-h
+++ b/modules/strings-h
@@ -37,8 +37,10 @@ strings.h: strings.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(WARN_ON_USE
 	      -e 's/@''GNULIB_STRNCASECMP''@/$(GNULIB_STRNCASECMP)/g' \
 	      -e 's|@''HAVE_FFS''@|$(HAVE_FFS)|g' \
 	      -e 's|@''HAVE_STRCASECMP''@|$(HAVE_STRCASECMP)|g' \
+	      -e 's|@''HAVE_STRNCASECMP''@|$(HAVE_STRNCASECMP)|g' \
 	      -e 's|@''HAVE_DECL_STRNCASECMP''@|$(HAVE_DECL_STRNCASECMP)|g' \
 	      -e 's|@''REPLACE_STRCASECMP''@|$(REPLACE_STRCASECMP)|g' \
+	      -e 's|@''REPLACE_STRNCASECMP''@|$(REPLACE_STRNCASECMP)|g' \
 	      -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
 	      -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
 	      -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \
diff --git a/modules/strncasecmp b/modules/strncasecmp
index 5fca265bad..816cd0fae6 100644
--- a/modules/strncasecmp
+++ b/modules/strncasecmp
@@ -4,13 +4,15 @@ Case-insensitive string comparison function for unibyte locales.
 Files:
 lib/strncasecmp.c
 m4/strncasecmp.m4
+m4/strcasecmp.m4
 
 Depends-on:
 strings-h
 
 configure.ac:
 gl_FUNC_STRNCASECMP
-gl_CONDITIONAL([GL_COND_OBJ_STRNCASECMP], [test $HAVE_STRNCASECMP = 0])
+gl_CONDITIONAL([GL_COND_OBJ_STRNCASECMP],
+               [test $HAVE_STRNCASECMP = 0 || test $REPLACE_STRNCASECMP = 1])
 AM_COND_IF([GL_COND_OBJ_STRNCASECMP], [
   gl_PREREQ_STRNCASECMP
 ])
-- 
2.43.0

>From 0a9ad49feb5dcdaf2a252caf95eb9eb19ae1a70d Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 16 Feb 2025 18:24:16 +0100
Subject: [PATCH 5/5] strncasecmp: Add tests.

* tests/test-strncasecmp-1.sh: New file.
* tests/test-strncasecmp-2.sh: New file.
* tests/test-strncasecmp.c: New file.
* modules/strncasecmp-tests: New file.
---
 ChangeLog                   |  6 +++
 modules/strncasecmp-tests   | 21 +++++++++++
 tests/test-strncasecmp-1.sh |  9 +++++
 tests/test-strncasecmp-2.sh | 15 ++++++++
 tests/test-strncasecmp.c    | 73 +++++++++++++++++++++++++++++++++++++
 5 files changed, 124 insertions(+)
 create mode 100644 modules/strncasecmp-tests
 create mode 100755 tests/test-strncasecmp-1.sh
 create mode 100755 tests/test-strncasecmp-2.sh
 create mode 100644 tests/test-strncasecmp.c

diff --git a/ChangeLog b/ChangeLog
index e73bdd7804..1fa41775dc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2025-02-16  Bruno Haible  <br...@clisp.org>
 
+	strncasecmp: Add tests.
+	* tests/test-strncasecmp-1.sh: New file.
+	* tests/test-strncasecmp-2.sh: New file.
+	* tests/test-strncasecmp.c: New file.
+	* modules/strncasecmp-tests: New file.
+
 	strncasecmp: Work around Solaris, Cygwin bug.
 	* lib/strings.in.h (strncasecmp): Consider REPLACE_STRNCASECMP. Use the
 	usual idioms.
diff --git a/modules/strncasecmp-tests b/modules/strncasecmp-tests
new file mode 100644
index 0000000000..54f2a347b3
--- /dev/null
+++ b/modules/strncasecmp-tests
@@ -0,0 +1,21 @@
+Files:
+tests/test-strncasecmp-1.sh
+tests/test-strncasecmp-2.sh
+tests/test-strncasecmp.c
+tests/signature.h
+tests/macros.h
+m4/locale-fr.m4
+m4/codeset.m4
+
+Depends-on:
+setlocale
+
+configure.ac:
+gt_LOCALE_FR
+
+Makefile.am:
+TESTS += test-strncasecmp-1.sh test-strncasecmp-2.sh
+TESTS_ENVIRONMENT += \
+  LOCALE_FR='@LOCALE_FR@'
+check_PROGRAMS += test-strncasecmp
+test_strncasecmp_LDADD = $(LDADD) $(SETLOCALE_LIB)
diff --git a/tests/test-strncasecmp-1.sh b/tests/test-strncasecmp-1.sh
new file mode 100755
index 0000000000..b64a876a44
--- /dev/null
+++ b/tests/test-strncasecmp-1.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Test in the POSIX locale.
+LC_ALL=C \
+${CHECKER} ./test-strncasecmp${EXEEXT} 1 || exit 1
+LC_ALL=POSIX \
+${CHECKER} ./test-strncasecmp${EXEEXT} 1 || exit 1
+
+exit 0
diff --git a/tests/test-strncasecmp-2.sh b/tests/test-strncasecmp-2.sh
new file mode 100755
index 0000000000..83a422d4bf
--- /dev/null
+++ b/tests/test-strncasecmp-2.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Test in an ISO-8859-1 or ISO-8859-15 locale.
+: "${LOCALE_FR=fr_FR}"
+if test $LOCALE_FR = none; then
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: no traditional french locale is installed"
+  else
+    echo "Skipping test: no traditional french locale is supported"
+  fi
+  exit 77
+fi
+
+LC_ALL=$LOCALE_FR \
+${CHECKER} ./test-strncasecmp${EXEEXT} 2
diff --git a/tests/test-strncasecmp.c b/tests/test-strncasecmp.c
new file mode 100644
index 0000000000..fe479e408a
--- /dev/null
+++ b/tests/test-strncasecmp.c
@@ -0,0 +1,73 @@
+/* Test of strncasecmp() function.
+   Copyright (C) 2008-2025 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 <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <strings.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (strncasecmp, int, (const char *, const char *, size_t));
+
+#include <locale.h>
+#include <stdlib.h>
+
+#include "macros.h"
+
+int
+main (int argc, char *argv[])
+{
+  /* configure should already have checked that the locale is supported.  */
+  if (setlocale (LC_ALL, "") == NULL)
+    return 1;
+
+  ASSERT (strncasecmp ("paragraph", "Paragraph", 9) == 0);
+
+  ASSERT (strncasecmp ("paragrapH", "parAgRaph", 9) == 0);
+
+  ASSERT (strncasecmp ("paragraph", "paraLyzed", 9) < 0);
+  ASSERT (strncasecmp ("paraLyzed", "paragraph", 9) > 0);
+
+  ASSERT (strncasecmp ("para", "paragraph", 9) < 0);
+  ASSERT (strncasecmp ("paragraph", "para", 9) > 0);
+
+  if (argc > 1)
+    switch (argv[1][0])
+      {
+      case '1':
+        /* C or POSIX locale.  */
+        return test_exit_status;
+
+      case '2':
+        /* Locale encoding is ISO-8859-1 or ISO-8859-15.  */
+
+        /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
+        /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
+        ASSERT (strncasecmp ("Fej\311r", "Fej\351r", 5) == 0);
+        ASSERT (strncasecmp ("Fej\351r", "Fej\311r", 5) == 0);
+        ASSERT (strncasecmp ("Fejer", "Fej\311r", 5) < 0);
+        ASSERT (strncasecmp ("Fej\311r", "Fejer", 5) > 0);
+
+        /* Compare with U+00D7 MULTIPLICATION SIGN */
+        ASSERT (strncasecmp ("Fej\311r", "Fej\327", 5) > 0);
+        ASSERT (strncasecmp ("Fej\327", "Fej\311r", 5) < 0);
+        ASSERT (strncasecmp ("Fej\351r", "Fej\327", 5) > 0);
+        ASSERT (strncasecmp ("Fej\327", "Fej\351r", 5) < 0);
+
+        return test_exit_status;
+      }
+
+  return 1;
+}
-- 
2.43.0

Reply via email to