]> Savannah Git Hosting - gnulib.git/commitdiff
strerror_l: New module.
authorBruno Haible <bruno@clisp.org>
Tue, 18 Feb 2025 11:20:41 +0000 (12:20 +0100)
committerBruno Haible <bruno@clisp.org>
Tue, 18 Feb 2025 11:22:07 +0000 (12:22 +0100)
* 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
doc/posix-functions/strerror_l.texi
lib/strerror_l.c [new file with mode: 0644]
lib/string.in.h
m4/strerror_l.m4 [new file with mode: 0644]
m4/string_h.m4
modules/strerror_l [new file with mode: 0644]
modules/string-h
tests/test-string-h-c++.cc

index b63eff9a35595585c9dcc0499c8a5d40fb236d34..54b21708fad836eeb985305eee4a50262aaa4ecc 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+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.
index 267602e4a2209c7d01d80a1e9e47f40fde20b3d8..bceff04e1cc3cb7b9c0aa2428909f19b4117b0c8 100644 (file)
@@ -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 (file)
index 0000000..1c2764c
--- /dev/null
@@ -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 <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;
+}
index d2bf29614603f00615e1d8d7cc2feff5d6915805..0093fea096ef72e7fabf36dd069dbebb315392a6 100644 (file)
 /* 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 (file)
index 0000000..f4ac721
--- /dev/null
@@ -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])
+  :
+])
index d0a676081143341012d0c1461eacea0a53d1f36c..bdcd6ef2b67e4977092b7e39acc044ffd42b3789 100644 (file)
@@ -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 (file)
index 0000000..1c93e37
--- /dev/null
@@ -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
index 4f9c20542491f58ee0a357a2fa0df7db9b3eb897..7dad53f040fa4f20e0305ff666e88366b0c370db 100644 (file)
@@ -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' \
index 624dcee6b23d6467a8b104aea0a9c5bfd7b93b0e..e5b45653fc2b05fcf027ea206479f964ce86f8c0 100644 (file)
@@ -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