strerror_r-posix: work with glibc 2.13
[gnulib.git] / lib / strerror_r.c
1 /* strerror_r.c --- POSIX compatible system error routine
2
3    Copyright (C) 2010-2011 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 /* Written by Bruno Haible <bruno@clisp.org>, 2010.  */
19
20 #include <config.h>
21
22 /* Specification.  */
23 #include <string.h>
24
25 #include <errno.h>
26
27 #if HAVE_DECL_STRERROR_R && !(__GLIBC__ >= 2 || defined __UCLIBC__) && !EXTEND_STRERROR_R
28
29 /* The system's strerror_r function is OK, except that its third argument
30    is 'int', not 'size_t'.  */
31
32 # include <limits.h>
33
34 int
35 strerror_r (int errnum, char *buf, size_t buflen)
36 # undef strerror_r
37 {
38   int ret;
39
40   if (buflen > INT_MAX)
41     buflen = INT_MAX;
42
43 # ifdef __hpux
44   /* On HP-UX 11.31, strerror_r always fails when buflen < 80.  */
45   {
46     char stackbuf[80];
47
48     if (buflen < sizeof (stackbuf))
49       {
50         ret = strerror_r (errnum, stackbuf, sizeof (stackbuf));
51         if (ret == 0)
52           {
53             size_t len = strlen (stackbuf);
54
55             if (len < buflen)
56               memcpy (buf, stackbuf, len + 1);
57             else
58               ret = ERANGE;
59           }
60       }
61     else
62       ret = strerror_r (errnum, buf, buflen);
63   }
64 # else
65   ret = strerror_r (errnum, buf, buflen);
66 # endif
67
68 # ifdef _AIX
69   /* On AIX 6.1, strerror_r returns -1 and sets errno to EINVAL
70      if buflen <= 1.  */
71   if (ret < 0 && errno == EINVAL && buflen <= 1)
72     {
73       /* Retry with a larger buffer.  */
74       char largerbuf[10];
75       ret = strerror_r (errnum, largerbuf, sizeof (largerbuf));
76       if (ret < 0 && errno == EINVAL)
77         {
78           /* errnum was out of range.  */
79           return EINVAL;
80         }
81       else
82         {
83           /* buf was too small.  */
84           return ERANGE;
85         }
86     }
87 # endif
88
89   /* Some old implementations may return (-1, EINVAL) instead of EINVAL.  */
90   return (ret < 0 ? errno : ret);
91 }
92
93 #elif (__GLIBC__ >= 2 || defined __UCLIBC__) && HAVE___XPG_STRERROR_R /* glibc >= 2.3.4 */ && !EXTEND_STRERROR_R
94
95 int
96 strerror_r (int errnum, char *buf, size_t buflen)
97 {
98   extern int __xpg_strerror_r (int errnum, char *buf, size_t buflen);
99
100   int ret = __xpg_strerror_r (errnum, buf, buflen);
101   return (ret < 0 ? errno : ret);
102 }
103
104 #else /* (__GLIBC__ >= 2 || defined __UCLIBC__ ? !HAVE___XPG_STRERROR_R : !HAVE_DECL_STRERROR_R) || EXTEND_STRERROR_R */
105
106 # include "glthread/lock.h"
107
108 /* Use strerror(), with locking.  */
109
110 /* This lock protects the buffer returned by strerror().  We assume that
111    no other uses of strerror() exist in the program.  */
112 gl_lock_define_initialized(static, strerror_lock)
113
114 int
115 strerror_r (int errnum, char *buf, size_t buflen)
116 {
117   gl_lock_lock (strerror_lock);
118
119   {
120     char *errmsg = strerror (errnum);
121     size_t len = strlen (errmsg);
122     int ret;
123
124     if (len < buflen)
125       {
126         memcpy (buf, errmsg, len + 1);
127         ret = 0;
128       }
129     else
130       ret = ERANGE;
131
132     gl_lock_unlock (strerror_lock);
133
134     return ret;
135   }
136 }
137
138 #endif