#include <config.h>
+/* Enable declaration of sys_nerr and sys_errlist in <errno.h> on NetBSD. */
+#define _NETBSD_SOURCE 1
+
/* Specification. */
#include <string.h>
#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "strerror-override.h"
+
+#if (__GLIBC__ >= 2 || defined __UCLIBC__ || defined __CYGWIN__) && HAVE___XPG_STRERROR_R /* glibc >= 2.3.4, cygwin >= 1.7.9 */
+
+# define USE_XPG_STRERROR_R 1
-#if HAVE_DECL_STRERROR_R && !(__GLIBC__ >= 2 || defined __UCLIBC__) && !EXTEND_STRERROR_R
+#elif HAVE_DECL_STRERROR_R && !(__GLIBC__ >= 2 || defined __UCLIBC__ || defined __CYGWIN__)
/* The system's strerror_r function is OK, except that its third argument
- is 'int', not 'size_t'. */
+ is 'int', not 'size_t', or its return type is wrong. */
# include <limits.h>
-int
-strerror_r (int errnum, char *buf, size_t buflen)
-# undef strerror_r
+# define USE_SYSTEM_STRERROR_R 1
+
+#else /* (__GLIBC__ >= 2 || defined __UCLIBC__ || defined __CYGWIN__ ? !HAVE___XPG_STRERROR_R : !HAVE_DECL_STRERROR_R) */
+
+/* Use the system's strerror(). Exclude glibc and cygwin because the
+ system strerror_r has the wrong return type, and cygwin 1.7.9
+ strerror_r clobbers strerror. */
+# undef strerror
+
+# define USE_SYSTEM_STRERROR 1
+
+# if defined __NetBSD__ || defined __hpux || ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __sgi || (defined __sun && !defined _LP64) || defined __CYGWIN__
+
+/* No locking needed. */
+
+/* Get catgets internationalization functions. */
+# if HAVE_CATGETS
+# include <nl_types.h>
+# endif
+
+/* Get sys_nerr, sys_errlist on HP-UX (otherwise only declared in C++ mode).
+ Get sys_nerr, sys_errlist on IRIX (otherwise only declared with _SGIAPI). */
+# if defined __hpux || defined __sgi
+extern int sys_nerr;
+extern char *sys_errlist[];
+# endif
+
+/* Get sys_nerr on Solaris. */
+# if defined __sun && !defined _LP64
+extern int sys_nerr;
+# endif
+
+# else
+
+# include "glthread/lock.h"
+
+/* This lock protects the buffer returned by strerror(). We assume that
+ no other uses of strerror() exist in the program. */
+gl_lock_define_initialized(static, strerror_lock)
+
+# endif
+
+#endif
+
+/* 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);
int ret;
- if (buflen > INT_MAX)
- buflen = INT_MAX;
+ if (len < buflen)
+ {
+ /* Although POSIX allows memcpy() to corrupt errno, we don't
+ know of any implementation where this is a real problem. */
+ memcpy (buf, msg, len + 1);
+ ret = 0;
+ }
+ else
+ {
+ memcpy (buf, msg, buflen - 1);
+ buf[buflen - 1] = '\0';
+ ret = ERANGE;
+ }
+ return ret;
+}
-# ifdef __hpux
- /* On HP-UX 11.31, strerror_r always fails when buflen < 80. */
+
+int
+strerror_r (int errnum, char *buf, size_t buflen)
+#undef strerror_r
+{
+ /* Filter this out now, so that rest of this replacement knows that
+ there is room for a non-empty message and trailing NUL. */
+ if (buflen <= 1)
+ {
+ if (buflen)
+ *buf = '\0';
+ return ERANGE;
+ }
+ *buf = '\0';
+
+ /* Check for gnulib overrides. */
{
- char stackbuf[80];
+ char const *msg = strerror_override (errnum);
- if (buflen < sizeof (stackbuf))
- {
- ret = strerror_r (errnum, stackbuf, sizeof (stackbuf));
- if (ret == 0)
- {
- size_t len = strlen (stackbuf);
-
- if (len < buflen)
- memcpy (buf, stackbuf, len + 1);
- else
- ret = ERANGE;
- }
- }
- else
- ret = strerror_r (errnum, buf, buflen);
+ if (msg)
+ return safe_copy (buf, buflen, msg);
}
-# else
- ret = strerror_r (errnum, buf, buflen);
-# endif
-# ifdef _AIX
- /* On AIX 6.1, strerror_r returns -1 and sets errno to EINVAL
- if buflen <= 1. */
- if (ret < 0 && errno == EINVAL && buflen <= 1)
+ {
+ int ret;
+ int saved_errno = errno;
+
+#if USE_XPG_STRERROR_R
+
{
- /* Retry with a larger buffer. */
- char largerbuf[10];
- ret = strerror_r (errnum, largerbuf, sizeof (largerbuf));
- if (ret < 0 && errno == EINVAL)
- {
- /* errnum was out of range. */
- return EINVAL;
- }
- else
+ extern int __xpg_strerror_r (int errnum, char *buf, size_t buflen);
+
+ ret = __xpg_strerror_r (errnum, buf, buflen);
+ if (ret < 0)
+ ret = errno;
+ if (!*buf)
{
- /* buf was too small. */
- return ERANGE;
+ /* glibc 2.13 would not touch buf on err, so we have to fall
+ back to GNU strerror_r which always returns a thread-safe
+ untruncated string to (partially) copy into our buf. */
+ safe_copy (buf, buflen, strerror_r (errnum, buf, buflen));
}
}
-# endif
- /* Some old implementations may return (-1, EINVAL) instead of EINVAL. */
- return (ret < 0 ? errno : ret);
-}
+#elif USE_SYSTEM_STRERROR_R
-#elif (__GLIBC__ >= 2 || defined __UCLIBC__) && HAVE___XPG_STRERROR_R /* glibc >= 2.3.4 */ && !EXTEND_STRERROR_R
+ if (buflen > INT_MAX)
+ buflen = INT_MAX;
-int
-strerror_r (int errnum, char *buf, size_t buflen)
-{
- extern int __xpg_strerror_r (int errnum, char *buf, size_t buflen);
+# ifdef __hpux
+ /* On HP-UX 11.31, strerror_r always fails when buflen < 80; it
+ also fails to change buf on EINVAL. */
+ {
+ char stackbuf[80];
- int ret = __xpg_strerror_r (errnum, buf, buflen);
- return (ret < 0 ? errno : ret);
-}
+ if (buflen < sizeof stackbuf)
+ {
+ ret = strerror_r (errnum, stackbuf, sizeof stackbuf);
+ if (ret == 0)
+ ret = safe_copy (buf, buflen, stackbuf);
+ }
+ else
+ ret = strerror_r (errnum, buf, buflen);
+ }
+# else
+ ret = strerror_r (errnum, buf, buflen);
-#else /* (__GLIBC__ >= 2 || defined __UCLIBC__ ? !HAVE___XPG_STRERROR_R : !HAVE_DECL_STRERROR_R) || EXTEND_STRERROR_R */
+ /* Some old implementations may return (-1, EINVAL) instead of EINVAL. */
+ if (ret < 0)
+ ret = errno;
+# endif
-# include "glthread/lock.h"
+# ifdef _AIX
+ /* AIX returns 0 rather than ERANGE when truncating strings; try
+ again until we are sure we got the entire string. */
+ if (!ret && strlen (buf) == buflen - 1)
+ {
+ char stackbuf[STACKBUF_LEN];
+ size_t len;
+ strerror_r (errnum, stackbuf, sizeof stackbuf);
+ len = strlen (stackbuf);
+ /* STACKBUF_LEN should have been large enough. */
+ if (len + 1 == sizeof stackbuf)
+ abort ();
+ if (buflen <= len)
+ ret = ERANGE;
+ }
+# else
+ /* Solaris 10 does not populate buf on ERANGE. OpenBSD 4.7
+ truncates early on ERANGE rather than return a partial integer.
+ We prefer the maximal string. We set buf[0] earlier, and we
+ know of no implementation that modifies buf to be an
+ unterminated string, so this strlen should be portable in
+ practice (rather than pulling in a safer strnlen). */
+ if (ret == ERANGE && strlen (buf) < buflen - 1)
+ {
+ char stackbuf[STACKBUF_LEN];
-/* Use strerror(), with locking. */
+ /* STACKBUF_LEN should have been large enough. */
+ if (strerror_r (errnum, stackbuf, sizeof stackbuf) == ERANGE)
+ abort ();
+ safe_copy (buf, buflen, stackbuf);
+ }
+# endif
-/* This lock protects the buffer returned by strerror(). We assume that
- no other uses of strerror() exist in the program. */
-gl_lock_define_initialized(static, strerror_lock)
+#else /* USE_SYSTEM_STRERROR */
-int
-strerror_r (int errnum, char *buf, size_t buflen)
-{
- gl_lock_lock (strerror_lock);
+ /* Try to do what strerror (errnum) does, but without clobbering the
+ buffer used by strerror(). */
- {
- char *errmsg = strerror (errnum);
- size_t len = strlen (errmsg);
- int ret;
+# if defined __NetBSD__ || defined __hpux || ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __CYGWIN__ /* NetBSD, HP-UX, native Win32, Cygwin */
- if (len < buflen)
+ /* NetBSD: sys_nerr, sys_errlist are declared through _NETBSD_SOURCE
+ and <errno.h> above.
+ HP-UX: sys_nerr, sys_errlist are declared explicitly above.
+ native Win32: sys_nerr, sys_errlist are declared in <stdlib.h>.
+ Cygwin: sys_nerr, sys_errlist are declared in <errno.h>. */
+ if (errnum >= 0 && errnum < sys_nerr)
{
- memcpy (buf, errmsg, len + 1);
- ret = 0;
+# if HAVE_CATGETS && (defined __NetBSD__ || defined __hpux)
+# if defined __NetBSD__
+ nl_catd catd = catopen ("libc", NL_CAT_LOCALE);
+ const char *errmsg =
+ (catd != (nl_catd)-1
+ ? catgets (catd, 1, errnum, sys_errlist[errnum])
+ : sys_errlist[errnum]);
+# endif
+# if defined __hpux
+ nl_catd catd = catopen ("perror", NL_CAT_LOCALE);
+ const char *errmsg =
+ (catd != (nl_catd)-1
+ ? catgets (catd, 1, 1 + errnum, sys_errlist[errnum])
+ : sys_errlist[errnum]);
+# endif
+# else
+ const char *errmsg = sys_errlist[errnum];
+# endif
+ if (errmsg == NULL || *errmsg == '\0')
+ ret = EINVAL;
+ else
+ ret = safe_copy (buf, buflen, errmsg);
+# if HAVE_CATGETS && (defined __NetBSD__ || defined __hpux)
+ if (catd != (nl_catd)-1)
+ catclose (catd);
+# endif
}
else
- ret = ERANGE;
+ ret = EINVAL;
+
+# elif defined __sgi || (defined __sun && !defined _LP64) /* IRIX, Solaris <= 9 32-bit */
+
+ /* For a valid error number, the system's strerror() function returns
+ a pointer to a not copied string, not to a buffer. */
+ if (errnum >= 0 && errnum < sys_nerr)
+ {
+ char *errmsg = strerror (errnum);
+
+ if (errmsg == NULL || *errmsg == '\0')
+ ret = EINVAL;
+ else
+ ret = safe_copy (buf, buflen, errmsg);
+ }
+ else
+ ret = EINVAL;
+
+# else
+
+ gl_lock_lock (strerror_lock);
+
+ {
+ char *errmsg = strerror (errnum);
+
+ /* For invalid error numbers, strerror() on
+ - IRIX 6.5 returns NULL,
+ - HP-UX 11 returns an empty string. */
+ if (errmsg == NULL || *errmsg == '\0')
+ ret = EINVAL;
+ else
+ ret = safe_copy (buf, buflen, errmsg);
+ }
gl_lock_unlock (strerror_lock);
+# endif
+
+#endif
+
+ if (ret == EINVAL && !*buf)
+ snprintf (buf, buflen, "Unknown error %d", errnum);
+
+ errno = saved_errno;
return ret;
}
}
-
-#endif