The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
syserror.c
Go to the documentation of this file.
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 #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 #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 {
48  if (talloc_free(fr_syserror_buffer) < 0) return -1;
49  fr_syserror_buffer = NULL;
50  logging_stop = true;
51  return 0;
52 }
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 {
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  {
191  char *q;
192 
193  q = strerror_r(num, buffer, buff_len);
194  if (!q) {
195 # ifndef NDEBUG
196  fprintf(stderr, "strerror_r() failed to write error for errno %i to buffer %p "
197  "(%zu bytes): %s\n", num, buffer, (size_t)FR_SYSERROR_BUFSIZE, strerror(errno));
198 # endif
199  buffer[0] = '\0';
200  return -1;
201  }
202 
203  /*
204  * If strerror_r used a static string, copy it to the buffer
205  */
206  if (q != buffer) {
207  size_t len;
208 
209  len = strlen(q) + 1;
210  if (len >= buff_len) len = buff_len; /* Truncate */
211  return strlcpy(buffer, q, len);
212  }
213 
214  return strlen(q);
215  }
216 #endif
217 }
218 
219 static inline CC_HINT(always_inline)
221 {
222  char *buffer;
223 
225  if (!buffer) {
226  buffer = talloc_array(NULL, char, FR_SYSERROR_BUFSIZE);
227  if (!buffer) {
228  fr_perror("Failed allocating memory for system error buffer");
229  return NULL;
230  }
232  }
233  return buffer;
234 }
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 {
245  char *buffer, *p, *end;
246 
247  /*
248  * Try and produce something useful,
249  * even if the thread is exiting.
250  */
251  if (logging_stop) {
252  error:
253  if (HAVE_DEFINITION(num)) return fr_syserror_macro_names[num];
254  return "";
255  }
256 
257  if (num == 0) return "No additional error information";
258 
259  /*
260  * Grab our thread local buffer
261  */
263  if (!buffer) goto error;
264 
265  p = buffer;
266  end = p + FR_SYSERROR_BUFSIZE;
267 
268  /*
269  * Prefix system errors with the macro name and number
270  * if we're debugging.
271  */
272  if (HAVE_DEFINITION(num)) {
273  p += snprintf(p, end - p, "%s: ", fr_syserror_macro_names[num]);
274  } else {
275  p += snprintf(p, end - p, "errno %i: ", num);
276  }
277  if (p >= end) return p;
278 
279  if (_fr_syserror(num, p, end - p) < 0) goto error;
280 
281  return buffer;
282 }
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 {
293  char *buffer;
294 
295  if (logging_stop) return "";
296 
297  /*
298  * Grab our thread local buffer
299  */
301  if (!buffer || (_fr_syserror(num, buffer, FR_SYSERROR_BUFSIZE) < 0)) return "Failed retrieving error";
302 
303  return buffer;
304 }
static int const char char buffer[256]
Definition: acutest.h:574
#define fr_atexit_thread_local(_name, _free, _uctx)
Definition: atexit.h:221
#define RCSID(id)
Definition: build.h:444
#define UNUSED
Definition: build.h:313
talloc_free(reap)
long int ssize_t
Definition: merged_model.c:24
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:689
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:34
static int _fr_logging_free(UNUSED void *arg)
Definition: syserror.c:46
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
#define FR_SYSERROR_BUFSIZE
Definition: syserror.c:32
static _Thread_local bool logging_stop
Due to ordering issues we may get errors being logged from within other thread local destructors whic...
Definition: syserror.c:35
#define HAVE_DEFINITION(_errno)
Definition: syserror.c:40
char const * fr_syserror_simple(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:291
static ssize_t _fr_syserror(int num, char *buffer, size_t buff_len)
Definition: syserror.c:163
static char * _fr_syserror_buffer(void)
Definition: syserror.c:220
static _Thread_local char * fr_syserror_buffer
Definition: syserror.c:34
static char const * fr_syserror_macro_names[]
POSIX-2008 errno macros.
Definition: syserror.c:58
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition: strerror.c:733