* 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 <bruno@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 <bruno@clisp.org>
errno-iter: New module.
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
--- /dev/null
+/* 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 <bruno@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;
+}
/* 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>
# 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@
--- /dev/null
+# 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])
+ :
+])
# 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,
[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])
])
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])
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])
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])
--- /dev/null
+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
-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' \
-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' \
-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' \
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