strerror_r: Avoid clobbering the strerror buffer when possible.
[gnulib.git] / lib / strerror_r.c
index 1fa52d9..3bba26c 100644 (file)
 
 #include <errno.h>
 
-#if HAVE_DECL_STRERROR_R && !(__GLIBC__ >= 2 || defined __UCLIBC__) && !EXTEND_STRERROR_R
+# if GNULIB_defined_ESOCK /* native Windows platforms */
+#  if HAVE_WINSOCK2_H
+#   include <winsock2.h>
+#  endif
+# endif
+
+
+#if HAVE_DECL_STRERROR_R && !(__GLIBC__ >= 2 || defined __UCLIBC__)
 
 /* The system's strerror_r function is OK, except that its third argument
    is 'int', not 'size_t', or its return type is wrong.  */
 
 # include <limits.h>
 
+# define USE_SYSTEM_STRERROR_R 1
+
+#elif (__GLIBC__ >= 2 || defined __UCLIBC__) && HAVE___XPG_STRERROR_R /* glibc >= 2.3.4 */
+
+# define USE_XPG_STRERROR_R 1
+
+#else /* (__GLIBC__ >= 2 || defined __UCLIBC__ ? !HAVE___XPG_STRERROR_R : !HAVE_DECL_STRERROR_R) */
+
+# include "glthread/lock.h"
+
+/* Use strerror(), with locking.  */
+# undef strerror
+
+# define USE_SYSTEM_STRERROR 1
+
+/* 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
+
+
 int
 strerror_r (int errnum, char *buf, size_t buflen)
-# undef strerror_r
+#undef strerror_r
 {
-  int ret;
+#if EXTEND_STRERROR_R
+  {
+    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 (buflen > INT_MAX)
-    buflen = INT_MAX;
+# 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
 
-# ifdef __hpux
-  /* On HP-UX 11.31, strerror_r always fails when buflen < 80.  */
-  {
-    char stackbuf[80];
+# if GNULIB_defined_ENOMSG
+      case ENOMSG:
+        msg = "No message of desired type";
+        break;
+# endif
 
-    if (buflen < sizeof (stackbuf))
+# 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
+      }
+
+    if (msg)
       {
-        ret = strerror_r (errnum, stackbuf, sizeof (stackbuf));
-        if (ret == 0)
-          {
-            size_t len = strlen (stackbuf);
+        size_t len = strlen (msg);
 
-            if (len < buflen)
-              memcpy (buf, stackbuf, len + 1);
-            else
-              ret = ERANGE;
+        if (len < buflen)
+          {
+            memcpy (buf, msg, len + 1);
+            return 0;
           }
+        else
+          return ERANGE;
       }
-    else
-      ret = strerror_r (errnum, buf, buflen);
   }
-# elif defined __CYGWIN__
-  /* Cygwin only provides the glibc interface, is thread-safe, and
-     always succeeds (although it may truncate). */
-  strerror_r (errnum, buf, buflen);
-  ret = 0;
-# else
-  ret = strerror_r (errnum, buf, buflen);
-# endif
+#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;
+
+#if USE_SYSTEM_STRERROR_R
+
+    if (buflen > INT_MAX)
+      buflen = INT_MAX;
+
+# ifdef __hpux
+    /* On HP-UX 11.31, strerror_r always fails when buflen < 80.  */
     {
-      /* Retry with a larger buffer.  */
-      char largerbuf[10];
-      ret = strerror_r (errnum, largerbuf, sizeof (largerbuf));
-      if (ret < 0 && errno == EINVAL)
+      char stackbuf[80];
+
+      if (buflen < sizeof (stackbuf))
         {
-          /* errnum was out of range.  */
-          return EINVAL;
+          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
-        {
-          /* buf was too small.  */
-          return ERANGE;
-        }
+        ret = strerror_r (errnum, buf, buflen);
     }
+# elif defined __CYGWIN__
+    /* Cygwin only provides the glibc interface, is thread-safe, and
+       always succeeds (although it may truncate). */
+    strerror_r (errnum, buf, buflen);
+    ret = 0;
+# else
+    ret = strerror_r (errnum, buf, buflen);
 # endif
 
-  /* Some old implementations may return (-1, EINVAL) instead of EINVAL.  */
-  return (ret < 0 ? errno : ret);
-}
-
-#elif (__GLIBC__ >= 2 || defined __UCLIBC__) && HAVE___XPG_STRERROR_R /* glibc >= 2.3.4 */ && !EXTEND_STRERROR_R
+# 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)
+      {
+        /* 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.  */
+            ret = EINVAL;
+          }
+        else
+          {
+            /* buf was too small.  */
+            ret = ERANGE;
+          }
+      }
+# endif
 
-int
-strerror_r (int errnum, char *buf, size_t buflen)
-{
-  extern int __xpg_strerror_r (int errnum, char *buf, size_t buflen);
+    /* Some old implementations may return (-1, EINVAL) instead of EINVAL.  */
+    if (ret < 0)
+      ret = errno;
 
-  int ret = __xpg_strerror_r (errnum, buf, buflen);
-  return (ret < 0 ? errno : ret);
-}
+#elif USE_XPG_STRERROR_R
 
-#else /* (__GLIBC__ >= 2 || defined __UCLIBC__ ? !HAVE___XPG_STRERROR_R : !HAVE_DECL_STRERROR_R) || EXTEND_STRERROR_R */
+    {
+      extern int __xpg_strerror_r (int errnum, char *buf, size_t buflen);
 
-# include "glthread/lock.h"
+      ret = __xpg_strerror_r (errnum, buf, buflen);
+      if (ret < 0)
+        ret = errno;
+    }
 
-/* Use strerror(), with locking.  */
+#else /* USE_SYSTEM_STRERROR */
 
-/* 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)
+    gl_lock_lock (strerror_lock);
 
-int
-strerror_r (int errnum, char *buf, size_t buflen)
-{
-  gl_lock_lock (strerror_lock);
+    {
+      char *errmsg = strerror (errnum);
 
-  {
-    char *errmsg = strerror (errnum);
-    size_t len = strlen (errmsg);
-    int ret;
+      /* 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
+        {
+          size_t len = strlen (errmsg);
 
-    if (len < buflen)
-      {
-        memcpy (buf, errmsg, len + 1);
-        ret = 0;
-      }
-    else
-      ret = ERANGE;
+          if (len < buflen)
+            {
+              memcpy (buf, errmsg, len + 1);
+              ret = 0;
+            }
+          else
+            ret = ERANGE;
+        }
+    }
 
     gl_lock_unlock (strerror_lock);
 
+#endif
+
     return ret;
   }
 }
-
-#endif