maint: update copyright
[gnulib.git] / lib / strerror_r.c
index f0b59c7..4aa96cb 100644 (file)
@@ -1,6 +1,6 @@
 /* strerror_r.c --- POSIX compatible system error routine
 
-   Copyright (C) 2010-2011 Free Software Foundation, Inc.
+   Copyright (C) 2010-2014 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
 #include <string.h>
 
 #include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
 
-# if GNULIB_defined_ESOCK /* native Windows platforms */
-#  if HAVE_WINSOCK2_H
-#   include <winsock2.h>
-#  endif
-# endif
-
+#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
+extern int __xpg_strerror_r (int errnum, char *buf, size_t buflen);
 
 #elif HAVE_DECL_STRERROR_R && !(__GLIBC__ >= 2 || defined __UCLIBC__ || defined __CYGWIN__)
 
@@ -77,9 +75,6 @@ extern char *sys_errlist[];
 extern int sys_nerr;
 #  endif
 
-/* Get sys_nerr, sys_errlist on native Windows.  */
-#  include <stdlib.h>
-
 # else
 
 #  include "glthread/lock.h"
@@ -92,6 +87,51 @@ gl_lock_define_initialized(static, strerror_lock)
 
 #endif
 
+/* On MSVC, there is no snprintf() function, just a _snprintf().
+   It is of lower quality, but sufficient for the simple use here.
+   We only have to make sure to NUL terminate the result (_snprintf
+   does not NUL terminate, like strncpy).  */
+#if !HAVE_SNPRINTF
+static int
+local_snprintf (char *buf, size_t buflen, const char *format, ...)
+{
+  va_list args;
+  int result;
+
+  va_start (args, format);
+  result = _vsnprintf (buf, buflen, format, args);
+  va_end (args);
+  if (buflen > 0 && (result < 0 || result >= buflen))
+    buf[buflen - 1] = '\0';
+  return result;
+}
+# define snprintf local_snprintf
+#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 (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;
+}
+
 
 int
 strerror_r (int errnum, char *buf, size_t buflen)
@@ -102,332 +142,18 @@ strerror_r (int errnum, char *buf, size_t buflen)
   if (buflen <= 1)
     {
       if (buflen)
-        *buf = 0;
+        *buf = '\0';
       return ERANGE;
     }
+  *buf = '\0';
 
-#if GNULIB_defined_ETXTBSY \
-    || GNULIB_defined_ESOCK \
-    || GNULIB_defined_ENOMSG \
-    || GNULIB_defined_EIDRM \
-    || GNULIB_defined_ENOLINK \
-    || GNULIB_defined_EPROTO \
-    || GNULIB_defined_EMULTIHOP \
-    || GNULIB_defined_EBADMSG \
-    || GNULIB_defined_EOVERFLOW \
-    || GNULIB_defined_ENOTSUP \
-    || GNULIB_defined_ESTALE \
-    || GNULIB_defined_EDQUOT \
-    || GNULIB_defined_ECANCELED
+  /* Check for gnulib overrides.  */
   {
-    char const *msg = NULL;
-    /* These error messages are taken from glibc/sysdeps/gnu/errlist.c.  */
-    switch (errnum)
-      {
-# if GNULIB_defined_ETXTBSY
-      case ETXTBSY:
-        msg = "Text file busy";
-        break;
-# endif
-
-# if GNULIB_defined_ESOCK /* native Windows platforms */
-      /* EWOULDBLOCK is the same as EAGAIN.  */
-      case EINPROGRESS:
-        msg = "Operation now in progress";
-        break;
-      case EALREADY:
-        msg = "Operation already in progress";
-        break;
-      case ENOTSOCK:
-        msg = "Socket operation on non-socket";
-        break;
-      case EDESTADDRREQ:
-        msg = "Destination address required";
-        break;
-      case EMSGSIZE:
-        msg = "Message too long";
-        break;
-      case EPROTOTYPE:
-        msg = "Protocol wrong type for socket";
-        break;
-      case ENOPROTOOPT:
-        msg = "Protocol not available";
-        break;
-      case EPROTONOSUPPORT:
-        msg = "Protocol not supported";
-        break;
-      case ESOCKTNOSUPPORT:
-        msg = "Socket type not supported";
-        break;
-      case EOPNOTSUPP:
-        msg = "Operation not supported";
-        break;
-      case EPFNOSUPPORT:
-        msg = "Protocol family not supported";
-        break;
-      case EAFNOSUPPORT:
-        msg = "Address family not supported by protocol";
-        break;
-      case EADDRINUSE:
-        msg = "Address already in use";
-        break;
-      case EADDRNOTAVAIL:
-        msg = "Cannot assign requested address";
-        break;
-      case ENETDOWN:
-        msg = "Network is down";
-        break;
-      case ENETUNREACH:
-        msg = "Network is unreachable";
-        break;
-      case ENETRESET:
-        msg = "Network dropped connection on reset";
-        break;
-      case ECONNABORTED:
-        msg = "Software caused connection abort";
-        break;
-      case ECONNRESET:
-        msg = "Connection reset by peer";
-        break;
-      case ENOBUFS:
-        msg = "No buffer space available";
-        break;
-      case EISCONN:
-        msg = "Transport endpoint is already connected";
-        break;
-      case ENOTCONN:
-        msg = "Transport endpoint is not connected";
-        break;
-      case ESHUTDOWN:
-        msg = "Cannot send after transport endpoint shutdown";
-        break;
-      case ETOOMANYREFS:
-        msg = "Too many references: cannot splice";
-        break;
-      case ETIMEDOUT:
-        msg = "Connection timed out";
-        break;
-      case ECONNREFUSED:
-        msg = "Connection refused";
-        break;
-      case ELOOP:
-        msg = "Too many levels of symbolic links";
-        break;
-      case EHOSTDOWN:
-        msg = "Host is down";
-        break;
-      case EHOSTUNREACH:
-        msg = "No route to host";
-        break;
-      case EPROCLIM:
-        msg = "Too many processes";
-        break;
-      case EUSERS:
-        msg = "Too many users";
-        break;
-      case EDQUOT:
-        msg = "Disk quota exceeded";
-        break;
-      case ESTALE:
-        msg = "Stale NFS file handle";
-        break;
-      case EREMOTE:
-        msg = "Object is remote";
-        break;
-#  if HAVE_WINSOCK2_H
-      /* WSA_INVALID_HANDLE maps to EBADF */
-      /* WSA_NOT_ENOUGH_MEMORY maps to ENOMEM */
-      /* WSA_INVALID_PARAMETER maps to EINVAL */
-      case WSA_OPERATION_ABORTED:
-        msg = "Overlapped operation aborted";
-        break;
-      case WSA_IO_INCOMPLETE:
-        msg = "Overlapped I/O event object not in signaled state";
-        break;
-      case WSA_IO_PENDING:
-        msg = "Overlapped operations will complete later";
-        break;
-      /* WSAEINTR maps to EINTR */
-      /* WSAEBADF maps to EBADF */
-      /* WSAEACCES maps to EACCES */
-      /* WSAEFAULT maps to EFAULT */
-      /* WSAEINVAL maps to EINVAL */
-      /* WSAEMFILE maps to EMFILE */
-      /* WSAEWOULDBLOCK maps to EWOULDBLOCK */
-      /* WSAEINPROGRESS is EINPROGRESS */
-      /* WSAEALREADY is EALREADY */
-      /* WSAENOTSOCK is ENOTSOCK */
-      /* WSAEDESTADDRREQ is EDESTADDRREQ */
-      /* WSAEMSGSIZE is EMSGSIZE */
-      /* WSAEPROTOTYPE is EPROTOTYPE */
-      /* WSAENOPROTOOPT is ENOPROTOOPT */
-      /* WSAEPROTONOSUPPORT is EPROTONOSUPPORT */
-      /* WSAESOCKTNOSUPPORT is ESOCKTNOSUPPORT */
-      /* WSAEOPNOTSUPP is EOPNOTSUPP */
-      /* WSAEPFNOSUPPORT is EPFNOSUPPORT */
-      /* WSAEAFNOSUPPORT is EAFNOSUPPORT */
-      /* WSAEADDRINUSE is EADDRINUSE */
-      /* WSAEADDRNOTAVAIL is EADDRNOTAVAIL */
-      /* WSAENETDOWN is ENETDOWN */
-      /* WSAENETUNREACH is ENETUNREACH */
-      /* WSAENETRESET is ENETRESET */
-      /* WSAECONNABORTED is ECONNABORTED */
-      /* WSAECONNRESET is ECONNRESET */
-      /* WSAENOBUFS is ENOBUFS */
-      /* WSAEISCONN is EISCONN */
-      /* WSAENOTCONN is ENOTCONN */
-      /* WSAESHUTDOWN is ESHUTDOWN */
-      /* WSAETOOMANYREFS is ETOOMANYREFS */
-      /* WSAETIMEDOUT is ETIMEDOUT */
-      /* WSAECONNREFUSED is ECONNREFUSED */
-      /* WSAELOOP is ELOOP */
-      /* WSAENAMETOOLONG maps to ENAMETOOLONG */
-      /* WSAEHOSTDOWN is EHOSTDOWN */
-      /* WSAEHOSTUNREACH is EHOSTUNREACH */
-      /* WSAENOTEMPTY maps to ENOTEMPTY */
-      /* WSAEPROCLIM is EPROCLIM */
-      /* WSAEUSERS is EUSERS */
-      /* WSAEDQUOT is EDQUOT */
-      /* WSAESTALE is ESTALE */
-      /* WSAEREMOTE is EREMOTE */
-      case WSASYSNOTREADY:
-        msg = "Network subsystem is unavailable";
-        break;
-      case WSAVERNOTSUPPORTED:
-        msg = "Winsock.dll version out of range";
-        break;
-      case WSANOTINITIALISED:
-        msg = "Successful WSAStartup not yet performed";
-        break;
-      case WSAEDISCON:
-        msg = "Graceful shutdown in progress";
-        break;
-      case WSAENOMORE: case WSA_E_NO_MORE:
-        msg = "No more results";
-        break;
-      case WSAECANCELLED: case WSA_E_CANCELLED:
-        msg = "Call was canceled";
-        break;
-      case WSAEINVALIDPROCTABLE:
-        msg = "Procedure call table is invalid";
-        break;
-      case WSAEINVALIDPROVIDER:
-        msg = "Service provider is invalid";
-        break;
-      case WSAEPROVIDERFAILEDINIT:
-        msg = "Service provider failed to initialize";
-        break;
-      case WSASYSCALLFAILURE:
-        msg = "System call failure";
-        break;
-      case WSASERVICE_NOT_FOUND:
-        msg = "Service not found";
-        break;
-      case WSATYPE_NOT_FOUND:
-        msg = "Class type not found";
-        break;
-      case WSAEREFUSED:
-        msg = "Database query was refused";
-        break;
-      case WSAHOST_NOT_FOUND:
-        msg = "Host not found";
-        break;
-      case WSATRY_AGAIN:
-        msg = "Nonauthoritative host not found";
-        break;
-      case WSANO_RECOVERY:
-        msg = "Nonrecoverable error";
-        break;
-      case WSANO_DATA:
-        msg = "Valid name, no data record of requested type";
-        break;
-      /* WSA_QOS_* omitted */
-#  endif
-# endif
-
-# if GNULIB_defined_ENOMSG
-      case ENOMSG:
-        msg = "No message of desired type";
-        break;
-# endif
-
-# if GNULIB_defined_EIDRM
-      case EIDRM:
-        msg = "Identifier removed";
-        break;
-# endif
-
-# if GNULIB_defined_ENOLINK
-      case ENOLINK:
-        msg = "Link has been severed";
-        break;
-# endif
-
-# if GNULIB_defined_EPROTO
-      case EPROTO:
-        msg = "Protocol error";
-        break;
-# endif
-
-# if GNULIB_defined_EMULTIHOP
-      case EMULTIHOP:
-        msg = "Multihop attempted";
-        break;
-# endif
-
-# if GNULIB_defined_EBADMSG
-      case EBADMSG:
-        msg = "Bad message";
-        break;
-# endif
-
-# if GNULIB_defined_EOVERFLOW
-      case EOVERFLOW:
-        msg = "Value too large for defined data type";
-        break;
-# endif
-
-# if GNULIB_defined_ENOTSUP
-      case ENOTSUP:
-        msg = "Not supported";
-        break;
-# endif
-
-# if GNULIB_defined_ESTALE
-      case ESTALE:
-        msg = "Stale NFS file handle";
-        break;
-# endif
-
-# if GNULIB_defined_EDQUOT
-      case EDQUOT:
-        msg = "Disk quota exceeded";
-        break;
-# endif
-
-# if GNULIB_defined_ECANCELED
-      case ECANCELED:
-        msg = "Operation canceled";
-        break;
-# endif
-      }
+    char const *msg = strerror_override (errnum);
 
     if (msg)
-      {
-        int saved_errno = errno;
-        size_t len = strlen (msg);
-        int ret = ERANGE;
-
-        if (len < buflen)
-          {
-            memcpy (buf, msg, len + 1);
-            ret = 0;
-          }
-        errno = saved_errno;
-        return ret;
-      }
+      return safe_copy (buf, buflen, msg);
   }
-#endif
 
   {
     int ret;
@@ -436,11 +162,16 @@ strerror_r (int errnum, char *buf, size_t buflen)
 #if USE_XPG_STRERROR_R
 
     {
-      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)
+        {
+          /* 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));
+        }
     }
 
 #elif USE_SYSTEM_STRERROR_R
@@ -449,62 +180,73 @@ strerror_r (int errnum, char *buf, size_t buflen)
       buflen = INT_MAX;
 
 # ifdef __hpux
-    /* On HP-UX 11.31, strerror_r always fails when buflen < 80.  */
+    /* On HP-UX 11.31, strerror_r always fails when buflen < 80; it
+       also fails to change buf on EINVAL.  */
     {
       char stackbuf[80];
 
-      if (buflen < sizeof (stackbuf))
+      if (buflen < sizeof stackbuf)
         {
-          ret = strerror_r (errnum, stackbuf, 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;
-            }
+            ret = safe_copy (buf, buflen, stackbuf);
         }
       else
         ret = strerror_r (errnum, buf, buflen);
     }
 # else
     ret = strerror_r (errnum, buf, buflen);
-# endif
 
     /* Some old implementations may return (-1, EINVAL) instead of EINVAL.  */
     if (ret < 0)
       ret = errno;
+# endif
 
-    /* FreeBSD rejects 0; see http://austingroupbugs.net/view.php?id=382.  */
-    if (errnum == 0 && ret == EINVAL)
+# 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)
       {
-        if (buflen <= strlen ("Success"))
-          {
-            ret = ERANGE;
-            if (buflen)
-              buf[0] = 0;
-          }
-        else
-          {
-            ret = 0;
-            strcpy (buf, "Success");
-          }
+        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];
+
+        /* STACKBUF_LEN should have been large enough.  */
+        if (strerror_r (errnum, stackbuf, sizeof stackbuf) == ERANGE)
+          abort ();
+        safe_copy (buf, buflen, stackbuf);
+      }
+# endif
 
 #else /* USE_SYSTEM_STRERROR */
 
     /* Try to do what strerror (errnum) does, but without clobbering the
        buffer used by strerror().  */
 
-# if defined __NetBSD__ || defined __hpux || ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __CYGWIN__ /* NetBSD, HP-UX, native Win32, Cygwin */
+# if defined __NetBSD__ || defined __hpux || ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __CYGWIN__ /* NetBSD, HP-UX, native Windows, Cygwin */
 
-    /* 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>.  */
+    /* 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 Windows: 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)
       {
 #  if HAVE_CATGETS && (defined __NetBSD__ || defined __hpux)
@@ -528,17 +270,7 @@ strerror_r (int errnum, char *buf, size_t buflen)
         if (errmsg == NULL || *errmsg == '\0')
           ret = EINVAL;
         else
-          {
-            size_t len = strlen (errmsg);
-
-            if (len < buflen)
-              {
-                memcpy (buf, errmsg, len + 1);
-                ret = 0;
-              }
-            else
-              ret = ERANGE;
-          }
+          ret = safe_copy (buf, buflen, errmsg);
 #  if HAVE_CATGETS && (defined __NetBSD__ || defined __hpux)
         if (catd != (nl_catd)-1)
           catclose (catd);
@@ -558,17 +290,7 @@ strerror_r (int errnum, char *buf, size_t buflen)
         if (errmsg == NULL || *errmsg == '\0')
           ret = EINVAL;
         else
-          {
-            size_t len = strlen (errmsg);
-
-            if (len < buflen)
-              {
-                memcpy (buf, errmsg, len + 1);
-                ret = 0;
-              }
-            else
-              ret = ERANGE;
-          }
+          ret = safe_copy (buf, buflen, errmsg);
       }
     else
       ret = EINVAL;
@@ -586,17 +308,7 @@ strerror_r (int errnum, char *buf, size_t buflen)
       if (errmsg == NULL || *errmsg == '\0')
         ret = EINVAL;
       else
-        {
-          size_t len = strlen (errmsg);
-
-          if (len < buflen)
-            {
-              memcpy (buf, errmsg, len + 1);
-              ret = 0;
-            }
-          else
-            ret = ERANGE;
-        }
+        ret = safe_copy (buf, buflen, errmsg);
     }
 
     gl_lock_unlock (strerror_lock);
@@ -605,6 +317,9 @@ strerror_r (int errnum, char *buf, size_t buflen)
 
 #endif
 
+    if (ret == EINVAL && !*buf)
+      snprintf (buf, buflen, "Unknown error %d", errnum);
+
     errno = saved_errno;
     return ret;
   }