diff lib/strerror_r.c @ 14815:820329b2a895

strerror_r: enforce POSIX recommendations POSIX recommends (but does not require) that strerror_r populate buf even on error. But since we guarantee this behavior for strerror, we might as well also guarantee it for strerror_r. * lib/strerror_r.c (safe_copy): New helper method. (strerror_r): Guarantee a non-empty string. * tests/test-strerror_r.c (main): Enhance tests to incorporate recent POSIX rulings and to match our strerror guarantees. * doc/posix-functions/strerror_r.texi (strerror_r): Document this. Signed-off-by: Eric Blake <eblake@redhat.com>
author Eric Blake <eblake@redhat.com>
date Wed, 18 May 2011 15:19:51 -0600
parents fedc69ad1054
children b6363790caec
line wrap: on
line diff
--- a/lib/strerror_r.c
+++ b/lib/strerror_r.c
@@ -92,6 +92,30 @@
 
 #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,9 +126,10 @@
   if (buflen <= 1)
     {
       if (buflen)
-        *buf = 0;
+        *buf = '\0';
       return ERANGE;
     }
+  *buf = '\0';
 
 #if GNULIB_defined_ETXTBSY \
     || GNULIB_defined_ESOCK \
@@ -413,19 +438,7 @@
       }
 
     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
 
@@ -441,6 +454,13 @@
       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
@@ -453,18 +473,11 @@
     {
       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);
@@ -479,19 +492,7 @@
 
     /* FreeBSD rejects 0; see http://austingroupbugs.net/view.php?id=382.  */
     if (errnum == 0 && ret == EINVAL)
-      {
-        if (buflen <= strlen ("Success"))
-          {
-            ret = ERANGE;
-            if (buflen)
-              buf[0] = 0;
-          }
-        else
-          {
-            ret = 0;
-            strcpy (buf, "Success");
-          }
-      }
+      ret = safe_copy (buf, buflen, "Success");
 
 #else /* USE_SYSTEM_STRERROR */
 
@@ -528,17 +529,7 @@
         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 +549,7 @@
         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 +567,7 @@
       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 +576,9 @@
 
 #endif
 
+    if (ret == EINVAL && !*buf)
+      snprintf (buf, buflen, "Unknown error %d", errnum);
+
     errno = saved_errno;
     return ret;
   }