From: Bruno Haible Date: Tue, 18 Feb 2025 11:20:41 +0000 (+0100) Subject: strerror_l: New module. X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=7f279c82678b2df49e04e7e80b1dfb206197fab2;p=gnulib.git strerror_l: New module. * lib/string.in.h: Include . (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. --- diff --git a/ChangeLog b/ChangeLog index b63eff9a35..54b21708fa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2025-02-18 Bruno Haible + + strerror_l: New module. + * lib/string.in.h: Include . + (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 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 . */ + +/* Written by Bruno Haible , 2025. */ + +#include + +/* Specification. */ +#include + +#include +#include +#include +#include +#include + +#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 +#if @GNULIB_STRERROR_L@ +/* Get locale_t. */ +# include +#endif + /* MirBSD defines mbslen as a macro. */ #if @GNULIB_MBSLEN@ && defined __MirBSD__ # include @@ -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 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: + + +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