Coverage Report

Created: 2026-01-10 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}