These patches implement the strerror_l function from POSIX:2024.
Doing so by using the localizations from the system (i.e. without
providing our own localizations in gnulib) is a bit tricky.
But fortunately it is possible without accessing sys_errlist and such.


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

        strerror_l: Add tests.
        * tests/test-strerror_l.c: New file.
        * modules/strerror_l-tests: New file.

        strerror_l: New module.
        * lib/string.in.h: Include <locale.h>.
        (strerror_l, strerror_l_r): New declarations.
        * lib/strerror_l.c: New file.
        * m4/strerror_l.m4: New file.
        * m4/string_h.m4 (gl_STRING_H): Test for strerror_l.
        (gl_STRING_H_REQUIRE_DEFAULTS): Initialize GNULIB_STRERROR_L.
        (gl_STRING_H_DEFAULTS): Initialize HAVE_STRERROR_L, REPLACE_STRERROR_L.
        * modules/string-h (Makefile.am): Substitute GNULIB_STRERROR_L,
        HAVE_STRERROR_L, REPLACE_STRERROR_L.
        * modules/strerror_l: New file.
        * tests/test-string-h-c++.cc: Check declaration of strerror_l.
        * doc/posix-functions/strerror_l.texi: Mention the new module.

>From 7f279c82678b2df49e04e7e80b1dfb206197fab2 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 18 Feb 2025 12:20:41 +0100
Subject: [PATCH 1/2] strerror_l: New module.

* lib/string.in.h: Include <locale.h>.
(strerror_l, strerror_l_r): New declarations.
* lib/strerror_l.c: New file.
* m4/strerror_l.m4: New file.
* m4/string_h.m4 (gl_STRING_H): Test for strerror_l.
(gl_STRING_H_REQUIRE_DEFAULTS): Initialize GNULIB_STRERROR_L.
(gl_STRING_H_DEFAULTS): Initialize HAVE_STRERROR_L, REPLACE_STRERROR_L.
* modules/string-h (Makefile.am): Substitute GNULIB_STRERROR_L,
HAVE_STRERROR_L, REPLACE_STRERROR_L.
* modules/strerror_l: New file.
* tests/test-string-h-c++.cc: Check declaration of strerror_l.
* doc/posix-functions/strerror_l.texi: Mention the new module.
---
 ChangeLog                           |  16 ++
 doc/posix-functions/strerror_l.texi |   9 +-
 lib/strerror_l.c                    | 222 ++++++++++++++++++++++++++++
 lib/string.in.h                     |  42 ++++++
 m4/strerror_l.m4                    |  26 ++++
 m4/string_h.m4                      |   8 +-
 modules/strerror_l                  |  46 ++++++
 modules/string-h                    |   3 +
 tests/test-string-h-c++.cc          |   4 +
 9 files changed, 370 insertions(+), 6 deletions(-)
 create mode 100644 lib/strerror_l.c
 create mode 100644 m4/strerror_l.m4
 create mode 100644 modules/strerror_l

diff --git a/ChangeLog b/ChangeLog
index b63eff9a35..54b21708fa 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2025-02-18  Bruno Haible  <br...@clisp.org>
+
+	strerror_l: New module.
+	* lib/string.in.h: Include <locale.h>.
+	(strerror_l, strerror_l_r): New declarations.
+	* lib/strerror_l.c: New file.
+	* m4/strerror_l.m4: New file.
+	* m4/string_h.m4 (gl_STRING_H): Test for strerror_l.
+	(gl_STRING_H_REQUIRE_DEFAULTS): Initialize GNULIB_STRERROR_L.
+	(gl_STRING_H_DEFAULTS): Initialize HAVE_STRERROR_L, REPLACE_STRERROR_L.
+	* modules/string-h (Makefile.am): Substitute GNULIB_STRERROR_L,
+	HAVE_STRERROR_L, REPLACE_STRERROR_L.
+	* modules/strerror_l: New file.
+	* tests/test-string-h-c++.cc: Check declaration of strerror_l.
+	* doc/posix-functions/strerror_l.texi: Mention the new module.
+
 2025-02-18  Bruno Haible  <br...@clisp.org>
 
 	errno-iter: New module.
diff --git a/doc/posix-functions/strerror_l.texi b/doc/posix-functions/strerror_l.texi
index 267602e4a2..bceff04e1c 100644
--- a/doc/posix-functions/strerror_l.texi
+++ b/doc/posix-functions/strerror_l.texi
@@ -4,15 +4,16 @@
 
 POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9799919799/functions/strerror_l.html}
 
-Gnulib module: ---
+Gnulib module: strerror_l
+@mindex strerror_l
 
 Portability problems fixed by Gnulib:
 @itemize
+@item
+This function is missing on some platforms:
+glibc 2.5, macOS 14, FreeBSD 12.4, NetBSD 6.1, OpenBSD 6.1, Minix 3.1.8, AIX 7.1, HP-UX 11, Solaris 11.3, Cygwin 2.5.x, mingw, MSVC 14, Android 5.1.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
-@item
-This function is missing on some platforms:
-glibc 2.5, macOS 14, FreeBSD 12.0, NetBSD 5.0, OpenBSD 6.0, Minix 3.1.8, AIX 5.1, HP-UX 11, Solaris 11.3, Cygwin 1.7.x, mingw, MSVC 14, Android 5.1.
 @end itemize
diff --git a/lib/strerror_l.c b/lib/strerror_l.c
new file mode 100644
index 0000000000..1c2764c5f7
--- /dev/null
+++ b/lib/strerror_l.c
@@ -0,0 +1,222 @@
+/* Internationalized description of system error code.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <string.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "at-init.h"
+#include "errno-iter.h"
+#include "glthread/tls.h"
+#include "glthread/once.h"
+
+/* Minimum and maximum valid errno value (other than 0).  */
+static int errno_min;
+static int errno_max;
+
+/* Array of descriptions of system error codes in the "C" locale.
+   Note: Such a linear array would not work
+   - on GNU/Hurd, where errno_min = -309, errno_max = 1073741945,
+   - on Haiku, where errno_min = -2147483648, errno_max = 2016.  */
+static char **c_error_strings;
+
+/* We need to initialize them before main() is entered, because main() often
+   contains a call to setlocale().  */
+AT_INIT (init_c_error_strings);
+#ifdef __SUNPRO_C
+# pragma init (init_c_error_strings)
+#endif
+
+static int
+errno_minmax_callback (void *data, int err)
+{
+  if (err < errno_min)
+    errno_min = err;
+  if (err > errno_max)
+    errno_max = err;
+  return 0;
+}
+
+static int
+errno_string_callback (void *data, int err)
+{
+  char *s = strerror (err);
+  if (s != NULL && *s != '\0')
+    c_error_strings[err - errno_min] = strdup (s);
+  return 0;
+}
+
+/* Initializes errno_min, errno_max, c_error_strings.  */
+static void
+init_c_error_strings (void)
+{
+  /* Compute errno_min, errno_max.  */
+  errno_min = errno_max = EDOM;
+  errno_iterate (errno_minmax_callback, NULL);
+
+#if 0 /* for debugging */
+  {
+    char buf[80];
+    sprintf (buf, "min.errno = %d\nmax.errno = %d\n", errno_min, errno_max);
+    write (STDOUT_FILENO, buf, strlen (buf));
+  }
+#endif
+
+  /* Allocate memory for c_error_strings.  */
+  c_error_strings =
+    (char **) calloc (errno_max - errno_min + 1, sizeof (char *));
+  /* Fetch the descriptions of system error codes.  */
+  if (c_error_strings != NULL)
+    errno_iterate (errno_string_callback, NULL);
+}
+
+#if GNULIB_defined_locale_t
+
+/* Copy as much of MSG into BUF as possible, without corrupting errno.
+   Return 0 if MSG fit in BUFLEN, otherwise return ERANGE.  */
+static int
+safe_copy (char *buf, size_t buflen, const char *msg)
+{
+  size_t len = strlen (msg);
+  size_t moved = len < buflen ? len : buflen - 1;
+
+  /* Although POSIX lets memmove corrupt errno, we don't
+     know of any implementation where this is a real problem.  */
+  memmove (buf, msg, moved);
+  buf[moved] = '\0';
+  return len < buflen ? 0 : ERANGE;
+}
+
+#endif
+
+int
+strerror_l_r (int errnum, char *buf, size_t buflen, locale_t locale)
+{
+  int ret;
+  int saved_errno = errno;
+
+#if GNULIB_defined_locale_t
+
+  struct gl_locale_category_t *plc =
+    &locale->category[gl_log2_lc_mask (LC_MESSAGES)];
+  if (plc->is_c_locale)
+    {
+      /* Implementation for the "C" locale.  */
+      char stackbuf[80];
+      char *s;
+      if (errnum == 0)
+        s = "No error";
+      else
+        {
+          if (errnum >= errno_min && errnum <= errno_max
+              && c_error_strings != NULL)
+            s = c_error_strings[errnum - errno_min];
+          else
+            s = NULL;
+          if (s == NULL)
+            {
+# if defined __HAIKU__
+              /* For consistency with perror().  */
+              sprintf (stackbuf, "Unknown Application Error (%d)", errnum);
+# else
+              sprintf (stackbuf, "Unknown error %d", errnum);
+# endif
+              s = stackbuf;
+            }
+        }
+      ret = safe_copy (buf, buflen, s);
+    }
+  else
+    {
+      /* Implementation for the global locale.  */
+# if HAVE_WORKING_USELOCALE
+      locale_t saved_locale = uselocale (LC_GLOBAL_LOCALE);
+# endif
+      ret = strerror_r (errnum, buf, buflen);
+# if HAVE_WORKING_USELOCALE
+      uselocale (saved_locale);
+# endif
+    }
+
+#else
+
+# if HAVE_WORKING_USELOCALE
+  locale_t saved_locale = uselocale (locale);
+# endif
+  ret = strerror_r (errnum, buf, buflen);
+# if HAVE_WORKING_USELOCALE
+  uselocale (saved_locale);
+# endif
+
+#endif
+
+  if (ret == 0)
+    errno = saved_errno;
+  return ret;
+}
+
+/* Maximum supported size of an error code description.  */
+#define RESULT_MAX_SIZE 200
+
+static gl_tls_key_t result_key; /* TLS key for a 'char *' */
+
+static void
+key_init (void)
+{
+  gl_tls_key_init (result_key, free);
+  /* The per-thread initial value is NULL.  */
+}
+
+/* Ensure that key_init is called once only.  */
+gl_once_define(static, key_init_once)
+
+char *
+strerror_l (int errnum, locale_t locale)
+{
+  int saved_errno = errno;
+
+  /* Call strerror_l_r, returning the string in thread-local storage.  */
+  gl_once (key_init_once, key_init);
+  char *result = gl_tls_get (result_key);
+  if (result == NULL)
+    {
+      result = malloc (RESULT_MAX_SIZE);
+      if (result == NULL)
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+      gl_tls_set (result_key, result);
+    }
+
+  int ret = strerror_l_r (errnum, result, RESULT_MAX_SIZE, locale);
+  if (ret == 0)
+    {
+      errno = saved_errno;
+      return result;
+    }
+  else
+    return NULL;
+}
diff --git a/lib/string.in.h b/lib/string.in.h
index d2bf296146..0093fea096 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -54,6 +54,11 @@
 /* NetBSD 5.0 mis-defines NULL.  */
 #include <stddef.h>
 
+#if @GNULIB_STRERROR_L@
+/* Get locale_t.  */
+# include <locale.h>
+#endif
+
 /* MirBSD defines mbslen as a macro.  */
 #if @GNULIB_MBSLEN@ && defined __MirBSD__
 # include <wchar.h>
@@ -1469,6 +1474,43 @@ _GL_WARN_ON_USE (strerror_r, "strerror_r is unportable - "
 # endif
 #endif
 
+/* Map any int, typically from errno, into an error message.
+   With locale_t argument.  */
+#if @GNULIB_STRERROR_L@
+# if @REPLACE_STRERROR_L@
+#  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+#   undef strerror_l
+#   define strerror_l rpl_strerror_l
+#  endif
+_GL_FUNCDECL_RPL (strerror_l, char *, (int errnum, locale_t locale),
+                                                   _GL_ARG_NONNULL ((2)));
+_GL_CXXALIAS_RPL (strerror_l, char *, (int errnum, locale_t locale));
+# else
+#  if !@HAVE_STRERROR_L@
+_GL_FUNCDECL_SYS (strerror_l, char *, (int errnum, locale_t locale),
+                                                   _GL_ARG_NONNULL ((2)));
+#  endif
+_GL_CXXALIAS_SYS (strerror_l, char *, (int errnum, locale_t locale));
+# endif
+# if __GLIBC__ >= 2
+_GL_CXXALIASWARN (strerror_l);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef strerror_l
+/* Assume strerror_l is always declared.  */
+_GL_WARN_ON_USE (strerror_l, "strerror_l is unportable - "
+                 "use gnulib module strerror_l for portability");
+#endif
+
+/* Map any int, typically from errno, into an error message.  Multithread-safe,
+   with locale_t argument.
+   Not portable! Only provided by gnulib.  */
+#if @GNULIB_STRERROR_L@
+_GL_FUNCDECL_SYS (strerror_l_r, int,
+                  (int errnum, char *buf, size_t buflen, locale_t locale),
+                  _GL_ARG_NONNULL ((2, 4)));
+#endif
+
 /* Return the name of the system error code ERRNUM.  */
 #if @GNULIB_STRERRORNAME_NP@
 # if @REPLACE_STRERRORNAME_NP@
diff --git a/m4/strerror_l.m4 b/m4/strerror_l.m4
new file mode 100644
index 0000000000..f4ac721152
--- /dev/null
+++ b/m4/strerror_l.m4
@@ -0,0 +1,26 @@
+# strerror_l.m4
+# serial 1
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+dnl This file is offered as-is, without any warranty.
+
+AC_DEFUN([gl_FUNC_STRERROR_L],
+[
+  AC_REQUIRE([gl_STRING_H_DEFAULTS])
+
+  dnl Persuade glibc <string.h> to declare strerror_l().
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_FUNCS_ONCE([strerror_l])
+  if test $ac_cv_func_strerror_l = no; then
+    HAVE_STRERROR_L=0
+  fi
+])
+
+# Prerequisites of lib/strerror_l.c.
+AC_DEFUN([gl_PREREQ_STRERROR_L], [
+  AC_REQUIRE([gt_FUNC_USELOCALE])
+  :
+])
diff --git a/m4/string_h.m4 b/m4/string_h.m4
index d0a6760811..bdcd6ef2b6 100644
--- a/m4/string_h.m4
+++ b/m4/string_h.m4
@@ -1,5 +1,5 @@
 # string_h.m4
-# serial 43
+# serial 44
 dnl Copyright (C) 2007-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,
@@ -25,7 +25,8 @@ AC_DEFUN_ONCE([gl_STRING_H]
     [explicit_bzero ffsl ffsll memmem mempcpy memrchr memset_explicit
      rawmemchr stpcpy stpncpy strchrnul
      strdup strncat strndup strnlen strpbrk strsep strcasestr strtok_r
-     strerror_r strerrorname_np sigabbrev_np sigdescr_np strsignal strverscmp])
+     strerror_l strerror_r strerrorname_np
+     sigabbrev_np sigdescr_np strsignal strverscmp])
 
   AC_REQUIRE([AC_C_RESTRICT])
 ])
@@ -90,6 +91,7 @@ AC_DEFUN([gl_STRING_H_REQUIRE_DEFAULTS]
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MBS_ENDSWITH])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRERROR])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRERROR_R])
+    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRERROR_L])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STRERRORNAME_NP])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SIGABBREV_NP])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SIGDESCR_NP])
@@ -128,6 +130,7 @@ AC_DEFUN([gl_STRING_H_DEFAULTS]
   HAVE_STRCASESTR=1;            AC_SUBST([HAVE_STRCASESTR])
   HAVE_DECL_STRTOK_R=1;         AC_SUBST([HAVE_DECL_STRTOK_R])
   HAVE_DECL_STRERROR_R=1;       AC_SUBST([HAVE_DECL_STRERROR_R])
+  HAVE_STRERROR_L=1;            AC_SUBST([HAVE_STRERROR_L])
   HAVE_STRERRORNAME_NP=1;       AC_SUBST([HAVE_STRERRORNAME_NP])
   HAVE_SIGABBREV_NP=1;          AC_SUBST([HAVE_SIGABBREV_NP])
   HAVE_SIGDESCR_NP=1;           AC_SUBST([HAVE_SIGDESCR_NP])
@@ -150,6 +153,7 @@ AC_DEFUN([gl_STRING_H_DEFAULTS]
   REPLACE_STRTOK_R=0;           AC_SUBST([REPLACE_STRTOK_R])
   REPLACE_STRERROR=0;           AC_SUBST([REPLACE_STRERROR])
   REPLACE_STRERROR_R=0;         AC_SUBST([REPLACE_STRERROR_R])
+  REPLACE_STRERROR_L=0;         AC_SUBST([REPLACE_STRERROR_L])
   REPLACE_STRERRORNAME_NP=0;    AC_SUBST([REPLACE_STRERRORNAME_NP])
   REPLACE_STRSIGNAL=0;          AC_SUBST([REPLACE_STRSIGNAL])
   REPLACE_STRVERSCMP=0;         AC_SUBST([REPLACE_STRVERSCMP])
diff --git a/modules/strerror_l b/modules/strerror_l
new file mode 100644
index 0000000000..1c93e37cb8
--- /dev/null
+++ b/modules/strerror_l
@@ -0,0 +1,46 @@
+Description:
+strerror_l() function: get string describing error code.
+
+Files:
+lib/strerror_l.c
+m4/strerror_l.m4
+m4/intl-thread-locale.m4
+
+Depends-on:
+string-h
+locale-h
+extensions
+errno-h          [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+at-init          [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+errno-iter       [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+write            [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+tls              [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+once             [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+strerror         [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+strdup           [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+calloc-posix     [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+strerror_r-posix [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1]
+
+configure.ac:
+gl_FUNC_STRERROR_L
+gl_CONDITIONAL([GL_COND_OBJ_STRERROR_L],
+               [test $HAVE_STRERROR_L = 0 || test $REPLACE_STRERROR_L = 1])
+AM_COND_IF([GL_COND_OBJ_STRERROR_L], [
+  gl_PREREQ_STRERROR_L
+])
+gl_MODULE_INDICATOR([strerror_l])
+gl_STRING_MODULE_INDICATOR([strerror_l])
+
+Makefile.am:
+if GL_COND_OBJ_STRERROR_L
+lib_SOURCES += strerror_l.c
+endif
+
+Include:
+<string.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/string-h b/modules/string-h
index 4f9c205424..7dad53f040 100644
--- a/modules/string-h
+++ b/modules/string-h
@@ -75,6 +75,7 @@ string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
 	      -e 's/@''GNULIB_STR_STARTSWITH''@/$(GNULIB_STR_STARTSWITH)/g' \
 	      -e 's/@''GNULIB_STRERROR''@/$(GNULIB_STRERROR)/g' \
 	      -e 's/@''GNULIB_STRERROR_R''@/$(GNULIB_STRERROR_R)/g' \
+	      -e 's/@''GNULIB_STRERROR_L''@/$(GNULIB_STRERROR_L)/g' \
 	      -e 's/@''GNULIB_STRERRORNAME_NP''@/$(GNULIB_STRERRORNAME_NP)/g' \
 	      -e 's/@''GNULIB_SIGABBREV_NP''@/$(GNULIB_SIGABBREV_NP)/g' \
 	      -e 's/@''GNULIB_SIGDESCR_NP''@/$(GNULIB_SIGDESCR_NP)/g' \
@@ -105,6 +106,7 @@ string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
 	      -e 's|@''HAVE_STRCASESTR''@|$(HAVE_STRCASESTR)|g' \
 	      -e 's|@''HAVE_DECL_STRTOK_R''@|$(HAVE_DECL_STRTOK_R)|g' \
 	      -e 's|@''HAVE_DECL_STRERROR_R''@|$(HAVE_DECL_STRERROR_R)|g' \
+	      -e 's|@''HAVE_STRERROR_L''@|$(HAVE_STRERROR_L)|g' \
 	      -e 's|@''HAVE_STRERRORNAME_NP''@|$(HAVE_STRERRORNAME_NP)|g' \
 	      -e 's|@''HAVE_SIGABBREV_NP''@|$(HAVE_SIGABBREV_NP)|g' \
 	      -e 's|@''HAVE_SIGDESCR_NP''@|$(HAVE_SIGDESCR_NP)|g' \
@@ -128,6 +130,7 @@ string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
 	      -e 's|@''REPLACE_STRTOK_R''@|$(REPLACE_STRTOK_R)|g' \
 	      -e 's|@''REPLACE_STRERROR''@|$(REPLACE_STRERROR)|g' \
 	      -e 's|@''REPLACE_STRERROR_R''@|$(REPLACE_STRERROR_R)|g' \
+	      -e 's|@''REPLACE_STRERROR_L''@|$(REPLACE_STRERROR_L)|g' \
 	      -e 's|@''REPLACE_STRERRORNAME_NP''@|$(REPLACE_STRERRORNAME_NP)|g' \
 	      -e 's|@''REPLACE_STRSIGNAL''@|$(REPLACE_STRSIGNAL)|g' \
 	      -e 's|@''REPLACE_STRVERSCMP''@|$(REPLACE_STRVERSCMP)|g' \
diff --git a/tests/test-string-h-c++.cc b/tests/test-string-h-c++.cc
index 624dcee6b2..e5b45653fc 100644
--- a/tests/test-string-h-c++.cc
+++ b/tests/test-string-h-c++.cc
@@ -138,6 +138,10 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::strerror, char *, (int));
 SIGNATURE_CHECK (GNULIB_NAMESPACE::strerror_r, int, (int, char *, size_t));
 #endif
 
+#if GNULIB_TEST_STRERROR_L
+SIGNATURE_CHECK (GNULIB_NAMESPACE::strerror_l, char *, (int, locale_t));
+#endif
+
 #if GNULIB_TEST_STRERRORNAME_NP
 SIGNATURE_CHECK (GNULIB_NAMESPACE::strerrorname_np, const char *, (int));
 #endif
-- 
2.43.0

>From 66d32ceddd565abff57901c59014023a9426cc8a Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 18 Feb 2025 12:22:30 +0100
Subject: [PATCH 2/2] strerror_l: Add tests.

* tests/test-strerror_l.c: New file.
* modules/strerror_l-tests: New file.
---
 ChangeLog                |  4 ++
 modules/strerror_l-tests | 15 +++++++
 tests/test-strerror_l.c  | 93 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 112 insertions(+)
 create mode 100644 modules/strerror_l-tests
 create mode 100644 tests/test-strerror_l.c

diff --git a/ChangeLog b/ChangeLog
index 54b21708fa..be92e36631 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2025-02-18  Bruno Haible  <br...@clisp.org>
 
+	strerror_l: Add tests.
+	* tests/test-strerror_l.c: New file.
+	* modules/strerror_l-tests: New file.
+
 	strerror_l: New module.
 	* lib/string.in.h: Include <locale.h>.
 	(strerror_l, strerror_l_r): New declarations.
diff --git a/modules/strerror_l-tests b/modules/strerror_l-tests
new file mode 100644
index 0000000000..07ef0cd21a
--- /dev/null
+++ b/modules/strerror_l-tests
@@ -0,0 +1,15 @@
+Files:
+tests/test-strerror_l.c
+tests/signature.h
+tests/macros.h
+
+Depends-on:
+newlocale
+freelocale
+xalloc
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-strerror_l
+check_PROGRAMS += test-strerror_l
diff --git a/tests/test-strerror_l.c b/tests/test-strerror_l.c
new file mode 100644
index 0000000000..6befa1ed89
--- /dev/null
+++ b/tests/test-strerror_l.c
@@ -0,0 +1,93 @@
+/* Test of strerror_l() function.
+   Copyright (C) 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/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2025.  */
+
+#include <config.h>
+
+#include <string.h>
+#include <locale.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (strerror_l, char *, (int, locale_t));
+
+#include <errno.h>
+#include <stdio.h>
+
+#include "xalloc.h"
+#include "macros.h"
+
+#if defined _WIN32 && !defined __CYGWIN__
+
+# define FRENCH   "French_France"
+# define GERMAN   "German_Germany"
+# define ENCODING ".1252"
+
+# define LOCALE1 FRENCH ENCODING
+# define LOCALE2 GERMAN ENCODING
+
+#else
+
+# define LOCALE1 "fr_FR.UTF-8"
+# define LOCALE2 "de_DE.UTF-8"
+
+#endif
+
+int
+main ()
+{
+  char *c_message;
+  char *fr_message;
+
+  c_message = xstrdup (strerror (ERANGE));
+
+  if (setlocale (LC_ALL, LOCALE1) == NULL)
+    {
+      fprintf (stderr, "Skipping test: French locale %s not installed.\n", LOCALE1);
+      return 77;
+    }
+  fr_message = xstrdup (strerror (ERANGE));
+  if (strcmp (fr_message, c_message) == 0)
+    {
+      fprintf (stderr, "Skipping test: error descriptions are not localized.\n");
+      return 77;
+    }
+
+  {
+    locale_t l1 = newlocale (LC_ALL_MASK, "C", NULL);
+    ASSERT (l1 != NULL);
+    char *message = strerror_l (ERANGE, l1);
+    ASSERT (message != NULL);
+    ASSERT (strcmp (message, c_message) == 0);
+    freelocale (l1);
+  }
+  {
+    locale_t l1 = newlocale (LC_ALL_MASK, LOCALE2, NULL);
+    if (l1 == NULL)
+      {
+        fprintf (stderr, "Skipping test: German locale %s not installed or not supported by newlocale().\n", LOCALE2);
+        return 77;
+      }
+    char *message = strerror_l (ERANGE, l1);
+    ASSERT (message != NULL);
+    /* German is neither English nor French.  */
+    ASSERT (strcmp (message, c_message) != 0);
+    ASSERT (strcmp (message, fr_message) != 0);
+    freelocale (l1);
+  }
+
+  return test_exit_status;
+}
-- 
2.43.0

Reply via email to