/src/freeradius-server/src/lib/util/syserror.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** Support functions to allow libraries to get system errors in a threadsafe and easily debuggable way |
18 | | * |
19 | | * @file src/lib/util/syserror.c |
20 | | * |
21 | | * @copyright 2017 The FreeRADIUS server project |
22 | | * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
23 | | */ |
24 | | RCSID("$Id: 253b4d36a0657a0ba72bb31dea7fd462a637d718 $") |
25 | | |
26 | | #include <freeradius-devel/util/log.h> |
27 | | #include <freeradius-devel/util/strerror.h> |
28 | | #include <freeradius-devel/util/syserror.h> |
29 | | #include <freeradius-devel/util/atexit.h> |
30 | | |
31 | | |
32 | 0 | #define FR_SYSERROR_BUFSIZE (2048) |
33 | | |
34 | | static _Thread_local char *fr_syserror_buffer; |
35 | | static _Thread_local bool logging_stop; //!< Due to ordering issues we may get errors being |
36 | | ///< logged from within other thread local destructors |
37 | | ///< which cause a crash on exit if the logging buffer |
38 | | ///< has already been freed. |
39 | | |
40 | 0 | #define HAVE_DEFINITION(_errno) ((_errno) < (int)(NUM_ELEMENTS(fr_syserror_macro_names))) |
41 | | |
42 | | /* |
43 | | * Explicitly cleanup the memory allocated to the error buffer, |
44 | | * just in case valgrind complains about it. |
45 | | */ |
46 | | static int _fr_logging_free(UNUSED void *arg) |
47 | 0 | { |
48 | 0 | if (talloc_free(fr_syserror_buffer) < 0) return -1; |
49 | 0 | fr_syserror_buffer = NULL; |
50 | 0 | logging_stop = true; |
51 | 0 | return 0; |
52 | 0 | } |
53 | | |
54 | | /** POSIX-2008 errno macros |
55 | | * |
56 | | * Non-POSIX macros may be added, but you must check they're defined. |
57 | | */ |
58 | | static char const *fr_syserror_macro_names[] = { |
59 | | [E2BIG] = "E2BIG", |
60 | | [EACCES] = "EACCES", |
61 | | [EADDRINUSE] = "EADDRINUSE", |
62 | | [EADDRNOTAVAIL] = "EADDRNOTAVAIL", |
63 | | [EAFNOSUPPORT] = "EAFNOSUPPORT", |
64 | | #if EWOULDBLOCK == EAGAIN |
65 | | [EWOULDBLOCK] = "EWOULDBLOCK or EAGAIN", |
66 | | #else |
67 | | [EAGAIN] = "EAGAIN", |
68 | | [EWOULDBLOCK] = "EWOULDBLOCK", |
69 | | #endif |
70 | | [EALREADY] = "EALREADY", |
71 | | [EBADF] = "EBADF", |
72 | | [EBADMSG] = "EBADMSG", |
73 | | [EBUSY] = "EBUSY", |
74 | | [ECANCELED] = "ECANCELED", |
75 | | [ECHILD] = "ECHILD", |
76 | | [ECONNABORTED] = "ECONNABORTED", |
77 | | [ECONNREFUSED] = "ECONNREFUSED", |
78 | | [ECONNRESET] = "ECONNRESET", |
79 | | [EDEADLK] = "EDEADLK", |
80 | | [EDESTADDRREQ] = "EDESTADDRREQ", |
81 | | [EDOM] = "EDOM", |
82 | | [EDQUOT] = "EDQUOT", |
83 | | [EEXIST] = "EEXIST", |
84 | | [EFAULT] = "EFAULT", |
85 | | [EFBIG] = "EFBIG", |
86 | | [EHOSTUNREACH] = "EHOSTUNREACH", |
87 | | [EIDRM] = "EIDRM", |
88 | | [EILSEQ] = "EILSEQ", |
89 | | [EINPROGRESS] = "EINPROGRESS", |
90 | | [EINTR] = "EINTR", |
91 | | [EINVAL] = "EINVAL", |
92 | | [EIO] = "EIO", |
93 | | [EISCONN] = "EISCONN", |
94 | | [EISDIR] = "EISDIR", |
95 | | [ELOOP] = "ELOOP", |
96 | | [EMFILE] = "EMFILE", |
97 | | [EMLINK] = "EMLINK", |
98 | | [EMSGSIZE] = "EMSGSIZE", |
99 | | [EMULTIHOP] = "EMULTIHOP", |
100 | | [ENAMETOOLONG] = "ENAMETOOLONG", |
101 | | [ENETDOWN] = "ENETDOWN", |
102 | | [ENETRESET] = "ENETRESET", |
103 | | [ENETUNREACH] = "ENETUNREACH", |
104 | | [ENFILE] = "ENFILE", |
105 | | [ENOBUFS] = "ENOBUFS", |
106 | | #ifdef ENODATA |
107 | | [ENODATA] = "ENODATA", |
108 | | #endif |
109 | | [ENODEV] = "ENODEV", |
110 | | [ENOENT] = "ENOENT", |
111 | | [ENOEXEC] = "ENOEXEC", |
112 | | [ENOLCK] = "ENOLCK", |
113 | | [ENOLINK] = "ENOLINK", |
114 | | [ENOMEM] = "ENOMEM", |
115 | | [ENOMSG] = "ENOMSG", |
116 | | [ENOPROTOOPT] = "ENOPROTOOPT", |
117 | | [ENOSPC] = "ENOSPC", |
118 | | #ifdef ENOSR |
119 | | [ENOSR] = "ENOSR", |
120 | | #endif |
121 | | #ifdef ENOSTR |
122 | | [ENOSTR] = "ENOSTR", |
123 | | #endif |
124 | | [ENOSYS] = "ENOSYS", |
125 | | [ENOTCONN] = "ENOTCONN", |
126 | | [ENOTDIR] = "ENOTDIR", |
127 | | [ENOTEMPTY] = "ENOTEMPTY", |
128 | | #ifdef ENOTRECOVERABLE |
129 | | [ENOTRECOVERABLE] = "ENOTRECOVERABLE", |
130 | | #endif |
131 | | [ENOTSOCK] = "ENOTSOCK", |
132 | | #if ENOTSUP == EOPNOTSUPP |
133 | | [ENOTSUP] = "ENOTSUP or EOPNOTSUPP", |
134 | | #else |
135 | | [ENOTSUP] = "ENOTSUP", |
136 | | [EOPNOTSUPP] = "EOPNOTSUPP", |
137 | | #endif |
138 | | [ENOTTY] = "ENOTTY", |
139 | | [ENXIO] = "ENXIO", |
140 | | [EOVERFLOW] = "EOVERFLOW", |
141 | | #ifdef EOWNERDEAD |
142 | | [EOWNERDEAD] = "EOWNERDEAD", |
143 | | #endif |
144 | | [EPERM] = "EPERM", |
145 | | [EPIPE] = "EPIPE", |
146 | | [EPROTO] = "EPROTO", |
147 | | [EPROTONOSUPPORT] = "EPROTONOSUPPORT", |
148 | | [EPROTOTYPE] = "EPROTOTYPE", |
149 | | [ERANGE] = "ERANGE", |
150 | | [EROFS] = "EROFS", |
151 | | [ESPIPE] = "ESPIPE", |
152 | | [ESRCH] = "ESRCH", |
153 | | [ESTALE] = "ESTALE", |
154 | | #ifdef ETIME |
155 | | [ETIME] = "ETIME", |
156 | | #endif |
157 | | [ETIMEDOUT] = "ETIMEDOUT", |
158 | | [ETXTBSY] = "ETXTBSY", |
159 | | [EXDEV] = "EXDEV" |
160 | | }; |
161 | | |
162 | | static inline CC_HINT(always_inline) |
163 | | ssize_t _fr_syserror(int num, char *buffer, size_t buff_len) |
164 | 0 | { |
165 | | /* |
166 | | * XSI-Compliant version |
167 | | */ |
168 | | #if !defined(HAVE_FEATURES_H) || !defined(__GLIBC__) || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 500) && ! _GNU_SOURCE) |
169 | | { |
170 | | int ret; |
171 | | |
172 | | ret = strerror_r(num, buffer, buff_len); |
173 | | if (ret != 0) { |
174 | | # ifndef NDEBUG |
175 | | fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p (%zu bytes), " |
176 | | "returned %i: %s\n", num, buffer, (size_t)FR_SYSERROR_BUFSIZE, ret, strerror(ret)); |
177 | | # endif |
178 | | buffer[0] = '\0'; |
179 | | return -1; |
180 | | } |
181 | | } |
182 | | return strlen(buffer); |
183 | | #else |
184 | | /* |
185 | | * GNU Specific version |
186 | | * |
187 | | * The GNU Specific version returns a char pointer. That pointer may point |
188 | | * the buffer you just passed in, or to an immutable static string. |
189 | | */ |
190 | 0 | { |
191 | 0 | char *q; |
192 | |
|
193 | 0 | q = strerror_r(num, buffer, buff_len); |
194 | 0 | if (!q) { |
195 | 0 | # ifndef NDEBUG |
196 | 0 | fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p " |
197 | 0 | "(%zu bytes): %s\n", num, buffer, (size_t)FR_SYSERROR_BUFSIZE, strerror(errno)); |
198 | 0 | # endif |
199 | 0 | buffer[0] = '\0'; |
200 | 0 | return -1; |
201 | 0 | } |
202 | | |
203 | | /* |
204 | | * If strerror_r used a static string, copy it to the buffer |
205 | | */ |
206 | 0 | if (q != buffer) { |
207 | 0 | size_t len; |
208 | |
|
209 | 0 | len = strlen(q) + 1; |
210 | 0 | if (len >= buff_len) len = buff_len; /* Truncate */ |
211 | 0 | return strlcpy(buffer, q, len); |
212 | 0 | } |
213 | | |
214 | 0 | return strlen(q); |
215 | 0 | } |
216 | 0 | #endif |
217 | 0 | } |
218 | | |
219 | | static inline CC_HINT(always_inline) |
220 | | char *_fr_syserror_buffer(void) |
221 | 0 | { |
222 | 0 | char *buffer; |
223 | |
|
224 | 0 | buffer = fr_syserror_buffer; |
225 | 0 | if (!buffer) { |
226 | 0 | buffer = talloc_array(NULL, char, FR_SYSERROR_BUFSIZE); |
227 | 0 | if (!buffer) { |
228 | 0 | fr_perror("Failed allocating memory for system error buffer"); |
229 | 0 | return NULL; |
230 | 0 | } |
231 | 0 | fr_atexit_thread_local(fr_syserror_buffer, _fr_logging_free, buffer); |
232 | 0 | } |
233 | 0 | return buffer; |
234 | 0 | } |
235 | | |
236 | | /** Guaranteed to be thread-safe version of strerror |
237 | | * |
238 | | * @param num errno as returned by function or from global errno. |
239 | | * @return Error string relating to errno, with the macro name added as a prefix. |
240 | | * |
241 | | * @hidecallergraph |
242 | | */ |
243 | | char const *fr_syserror(int num) |
244 | 0 | { |
245 | 0 | char *buffer, *p, *end; |
246 | | |
247 | | /* |
248 | | * Try and produce something useful, |
249 | | * even if the thread is exiting. |
250 | | */ |
251 | 0 | if (logging_stop) { |
252 | 0 | error: |
253 | 0 | if (HAVE_DEFINITION(num)) return fr_syserror_macro_names[num]; |
254 | 0 | return ""; |
255 | 0 | } |
256 | | |
257 | 0 | if (num == 0) return "No additional error information"; |
258 | | |
259 | | /* |
260 | | * Grab our thread local buffer |
261 | | */ |
262 | 0 | buffer = _fr_syserror_buffer(); |
263 | 0 | if (!buffer) goto error; |
264 | | |
265 | 0 | p = buffer; |
266 | 0 | end = p + FR_SYSERROR_BUFSIZE; |
267 | | |
268 | | /* |
269 | | * Prefix system errors with the macro name and number |
270 | | * if we're debugging. |
271 | | */ |
272 | 0 | if (HAVE_DEFINITION(num)) { |
273 | 0 | p += snprintf(p, end - p, "%s: ", fr_syserror_macro_names[num]); |
274 | 0 | } else { |
275 | 0 | p += snprintf(p, end - p, "errno %i: ", num); |
276 | 0 | } |
277 | 0 | if (p >= end) return p; |
278 | | |
279 | 0 | if (_fr_syserror(num, p, end - p) < 0) goto error; |
280 | | |
281 | 0 | return buffer; |
282 | 0 | } |
283 | | |
284 | | /** Guaranteed to be thread-safe version of strerror |
285 | | * |
286 | | * @param num errno as returned by function or from global errno. |
287 | | * @return Error string relating to errno with no decoration. |
288 | | * |
289 | | * @hidecallergraph |
290 | | */ |
291 | | char const *fr_syserror_simple(int num) |
292 | 0 | { |
293 | 0 | char *buffer; |
294 | |
|
295 | 0 | if (logging_stop) return ""; |
296 | | |
297 | | /* |
298 | | * Grab our thread local buffer |
299 | | */ |
300 | 0 | buffer = _fr_syserror_buffer(); |
301 | 0 | if (!buffer || (_fr_syserror(num, buffer, FR_SYSERROR_BUFSIZE) < 0)) return "Failed retrieving error"; |
302 | | |
303 | 0 | return buffer; |
304 | 0 | } |