Looking for a function which produces the internationalized description of
an errno value, with the two additional requirements that
  - it should be multithread-safe,
  - it should be easy to use,
I didn't find any answer in POSIX or gnulib.

https://man7.org/linux/man-pages/man3/strerror.3.html lists a few functions,
but:
  - strerror is not multithread-safe,
  - strerrordesc_np returns the English description, not internationalized,
  - strerror_l requires a locale_t object and thus does not work for the
    current locale,
  - strerror_r is not easy to use, because
      . it requires the caller to provide a preallocated buffer,
      . it comes in two variants (POSIX and GNU).

I considered adding a function xstrerror that would return the string
from strerror_r in a per-thread static buffer. But this return convention
has a pitfall: if the returned pointer ever gets passed to a different
thread, value corruption will occur, that is hard to detect and to debug.
(glibc's inet_ntoa function uses this return convention; but that does not
make it any more reliable.)

So, the best approach is still a function that returns the value in storage
with indefinite extent, namely malloc()ed.

I picked a 2-arguments function, rather than a 1-argument function, because
that's more useful in practice.

There's a potential conflict with the 1-argument function named xstrerror
from libiberty (GCC/binutils/gdb). The migration is simple:
  xstrerror_libiberty (errnum) == xstrerror_gnulib (NULL, errnum).


2023-10-05  Bruno Haible  <br...@clisp.org>

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

        xstrerror: New module.
        * lib/xstrerror.h: New file.
        * lib/xstrerror.c: New file.
        * modules/xstrerror: New file.
        * modules/strerror_r-posix (configure.ac): Update comment.

>From 6d20768e5a08a393aee4099419794e0cca495967 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 5 Oct 2023 18:01:05 +0200
Subject: [PATCH 1/2] xstrerror: New module.

* lib/xstrerror.h: New file.
* lib/xstrerror.c: New file.
* modules/xstrerror: New file.
* modules/strerror_r-posix (configure.ac): Update comment.
---
 ChangeLog                |  8 ++++++
 lib/xstrerror.c          | 59 ++++++++++++++++++++++++++++++++++++++++
 lib/xstrerror.h          | 45 ++++++++++++++++++++++++++++++
 modules/strerror_r-posix |  2 +-
 modules/xstrerror        | 26 ++++++++++++++++++
 5 files changed, 139 insertions(+), 1 deletion(-)
 create mode 100644 lib/xstrerror.c
 create mode 100644 lib/xstrerror.h
 create mode 100644 modules/xstrerror

diff --git a/ChangeLog b/ChangeLog
index f09de5233e..b8aefaf222 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2023-10-05  Bruno Haible  <br...@clisp.org>
+
+	xstrerror: New module.
+	* lib/xstrerror.h: New file.
+	* lib/xstrerror.c: New file.
+	* modules/xstrerror: New file.
+	* modules/strerror_r-posix (configure.ac): Update comment.
+
 2023-10-05  Paul Eggert  <egg...@cs.ucla.edu>
 
 	isnan: slightly simplify configuration
diff --git a/lib/xstrerror.c b/lib/xstrerror.c
new file mode 100644
index 0000000000..d00b996627
--- /dev/null
+++ b/lib/xstrerror.c
@@ -0,0 +1,59 @@
+/* Return diagnostic string based on error code.
+   Copyright (C) 2023 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>
+
+/* Specification.  */
+#include "xstrerror.h"
+
+#include <string.h>
+
+#include "xvasprintf.h"
+#include "xalloc.h"
+#include "gettext.h"
+
+#define _(msgid) gettext (msgid)
+
+char *
+xstrerror (const char *message, int errnum)
+{
+  /* Get the internationalized description of errnum,
+     in a stack-allocated buffer.  */
+  const char *errdesc;
+  char errbuf[1024];
+#if !GNULIB_STRERROR_R_POSIX && STRERROR_R_CHAR_P
+  errdesc = strerror_r (errnum, errbuf, sizeof errbuf);
+#else
+  if (strerror_r (errnum, errbuf, sizeof errbuf) == 0)
+    errdesc = errbuf;
+  else
+    errdesc = NULL;
+#endif
+  if (errdesc == NULL)
+    errdesc = _("Unknown system error");
+
+  if (message != NULL)
+    {
+      /* Allow e.g. French translators to insert a space before the colon.  */
+      char *result = xasprintf (_("%s: %s"), message, errdesc);
+      if (result == NULL)
+        /* Probably a buggy translation.  Use a safe fallback.  */
+        result = xasprintf ("%s%s%s", message, ": ", errdesc);
+      return result;
+    }
+  else
+    return xstrdup (errdesc);
+}
diff --git a/lib/xstrerror.h b/lib/xstrerror.h
new file mode 100644
index 0000000000..95beb1d64a
--- /dev/null
+++ b/lib/xstrerror.h
@@ -0,0 +1,45 @@
+/* Return diagnostic string based on error code.
+   Copyright (C) 2023 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/>.  */
+
+#ifndef _XSTRERROR_H
+#define _XSTRERROR_H
+
+/* This file uses _GL_ATTRIBUTE_MALLOC, _GL_ATTRIBUTE_RETURNS_NONNULL.  */
+#if !_GL_CONFIG_H_INCLUDED
+ #error "Please include config.h first."
+#endif
+
+/* Get 'free'.  */
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Return a freshly allocated diagnostic string that contains a given string
+   MESSAGE and the (internationalized) description of the error code ERRNUM.
+   Upon [ENOMEM] memory allocation error, call xalloc_die.
+
+   This function is multithread-safe.  */
+extern char *xstrerror (const char *message, int errnum)
+       _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE
+       _GL_ATTRIBUTE_RETURNS_NONNULL;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _XSTRERROR_H */
diff --git a/modules/strerror_r-posix b/modules/strerror_r-posix
index 25f049365a..779a821e9e 100644
--- a/modules/strerror_r-posix
+++ b/modules/strerror_r-posix
@@ -19,7 +19,7 @@ AS_IF([test $HAVE_DECL_STRERROR_R = 0 || test $REPLACE_STRERROR_R = 1], [
   gl_PREREQ_STRERROR_R
 ])
 gl_STRING_MODULE_INDICATOR([strerror_r])
-dnl For the modules argp, error.
+dnl For the modules argp, error, xstrerror.
 gl_MODULE_INDICATOR([strerror_r-posix])
 
 Makefile.am:
diff --git a/modules/xstrerror b/modules/xstrerror
new file mode 100644
index 0000000000..927d4c74a7
--- /dev/null
+++ b/modules/xstrerror
@@ -0,0 +1,26 @@
+Description:
+xstrerror() function: return diagnostic string based on error code.
+Multithread-safe. With out-of-memory checking.
+
+Files:
+lib/xstrerror.h
+lib/xstrerror.c
+
+Depends-on:
+strerror_r-posix
+xalloc
+xvasprintf
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += xstrerror.c
+
+Include:
+"xstrerror.h"
+
+License:
+GPL
+
+Maintainer:
+all
-- 
2.34.1

>From 70c869be3fcd5da3813a73520dc6bf5b91d68afd Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 5 Oct 2023 18:02:26 +0200
Subject: [PATCH 2/2] xstrerror: Add tests.

* tests/test-xstrerror.c: New file.
* modules/xstrerror-tests: New file.
---
 ChangeLog               |  4 ++++
 modules/xstrerror-tests | 12 +++++++++++
 tests/test-xstrerror.c  | 45 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 61 insertions(+)
 create mode 100644 modules/xstrerror-tests
 create mode 100644 tests/test-xstrerror.c

diff --git a/ChangeLog b/ChangeLog
index b8aefaf222..6df9aaaef7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2023-10-05  Bruno Haible  <br...@clisp.org>
 
+	xstrerror: Add tests.
+	* tests/test-xstrerror.c: New file.
+	* modules/xstrerror-tests: New file.
+
 	xstrerror: New module.
 	* lib/xstrerror.h: New file.
 	* lib/xstrerror.c: New file.
diff --git a/modules/xstrerror-tests b/modules/xstrerror-tests
new file mode 100644
index 0000000000..24a5fc9a10
--- /dev/null
+++ b/modules/xstrerror-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-xstrerror.c
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-xstrerror
+check_PROGRAMS += test-xstrerror
+test_xstrerror_LDADD = $(LDADD) $(LIBINTL)
diff --git a/tests/test-xstrerror.c b/tests/test-xstrerror.c
new file mode 100644
index 0000000000..d725aa1c94
--- /dev/null
+++ b/tests/test-xstrerror.c
@@ -0,0 +1,45 @@
+/* Test of xstrerror function.
+   Copyright (C) 2023 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>, 2023.  */
+
+#include <config.h>
+
+#include "xstrerror.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+
+int
+main ()
+{
+  /* Test in the "C" locale.  */
+  {
+    char *s = xstrerror ("can't steal", EACCES);
+    ASSERT (strcmp (s, "can't steal: Permission denied") == 0);
+    free (s);
+  }
+  {
+    char *s = xstrerror (NULL, EACCES);
+    ASSERT (strcmp (s, "Permission denied") == 0);
+    free (s);
+  }
+
+  return 0;
+}
-- 
2.34.1

Reply via email to