Quiet gcc
[freeradius.git] / src / lib / debug.c
1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program 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
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * @file debug.c
19  * @brief Various functions to aid in debugging
20  *
21  * @copyright 2013  The FreeRADIUS server project
22  * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  */
24 #include <assert.h>
25 #include <freeradius-devel/libradius.h>
26 #include <sys/stat.h>
27
28 /*
29  *      runtime backtrace functions are not POSIX but are included in
30  *      glibc, OSX >= 10.5 and various BSDs
31  */
32 #ifdef HAVE_EXECINFO
33 #  include <execinfo.h>
34 #endif
35
36 #ifdef HAVE_SYS_PRCTL_H
37 #  include <sys/prctl.h>
38 #endif
39
40 #ifdef HAVE_SYS_RESOURCE_H
41 #  include <sys/resource.h>
42 #endif
43
44 #ifdef HAVE_PTHREAD_H
45 #  define PTHREAD_MUTEX_LOCK pthread_mutex_lock
46 #  define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
47 #else
48 #  define PTHREAD_MUTEX_LOCK(_x)
49 #  define PTHREAD_MUTEX_UNLOCK(_x)
50 #endif
51
52 #ifdef HAVE_EXECINFO
53 #  define MAX_BT_FRAMES 128
54 #  define MAX_BT_CBUFF  65536                           //!< Should be a power of 2
55
56 #  ifdef HAVE_PTHREAD_H
57 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
58 #  endif
59
60 typedef struct fr_bt_info {
61         void            *obj;                           //!< Memory address of the block of allocated memory.
62         void            *frames[MAX_BT_FRAMES];         //!< Backtrace frame data
63         int             count;                          //!< Number of frames stored
64 } fr_bt_info_t;
65
66 struct fr_bt_marker {
67         void            *obj;                           //!< Pointer to the parent object, this is our needle
68                                                         //!< when we iterate over the contents of the circular buffer.
69         fr_cbuff_t      *cbuff;                         //!< Where we temporarily store the backtraces
70 };
71 #endif
72
73 static char panic_action[512];                          //!< The command to execute when panicking.
74 static fr_fault_cb_t panic_cb = NULL;                   //!< Callback to execute whilst panicking, before the
75                                                         //!< panic_action.
76 static fr_fault_log_t fr_fault_log = NULL;              //!< Function to use to process logging output.
77 static int fr_fault_log_fd = STDERR_FILENO;             //!< Where to write debug output.
78
79 static int fr_debugger_present = -1;                    //!< Whether were attached to by a debugger.
80
81 #ifdef HAVE_SYS_RESOURCE_H
82 static struct rlimit core_limits;
83 #endif
84
85 /** Stub callback to see if the SIGTRAP handler is overriden
86  *
87  * @param signum signal raised.
88  */
89 static void _sigtrap_handler(UNUSED int signum)
90 {
91         fr_debugger_present = 0;
92         signal(SIGTRAP, SIG_DFL);
93 }
94
95 /** Break in debugger (if were running under a debugger)
96  *
97  * If the server is running under a debugger this will raise a
98  * SIGTRAP which will pause the running process.
99  *
100  * If the server is not running under debugger then this will do nothing.
101  */
102 void fr_debug_break(void)
103 {
104         if (fr_debugger_present == -1) {
105                 fr_debugger_present = 0;
106                 signal(SIGTRAP, _sigtrap_handler);
107                 raise(SIGTRAP);
108         } else if (fr_debugger_present == 1) {
109                 raise(SIGTRAP);
110         }
111 }
112
113 #ifdef HAVE_EXECINFO
114 /** Generate a backtrace for an object during destruction
115  *
116  * If this is the first entry being inserted
117  */
118 static int _fr_do_bt(fr_bt_marker_t *marker)
119 {
120         fr_bt_info_t *bt;
121
122         if (!fr_assert(marker->obj) || !fr_assert(marker->cbuff)) {
123                 return -1;
124         }
125
126         bt = talloc_zero(marker->cbuff, fr_bt_info_t);
127         if (!bt) {
128                 return -1;
129         }
130         bt->count = backtrace(bt->frames, MAX_BT_FRAMES);
131         fr_cbuff_rp_insert(marker->cbuff, bt);
132
133         return 0;
134 }
135
136 /** Print backtrace entry for a given object
137  *
138  * @param cbuff to search in.
139  * @param obj pointer to original object
140  */
141 void backtrace_print(fr_cbuff_t *cbuff, void *obj)
142 {
143         fr_bt_info_t *p;
144         bool found = false;
145         int i = 0;
146         char **frames;
147
148         while ((p = fr_cbuff_rp_next(cbuff, NULL))) {
149                 if ((p == obj) || !obj) {
150                         found = true;
151                         frames = backtrace_symbols(p->frames, p->count);
152
153                         fprintf(stderr, "Stacktrace for: %p\n", p);
154                         for (i = 0; i < p->count; i++) {
155                                 fprintf(stderr, "%s\n", frames[i]);
156                         }
157
158                         /* We were only asked to look for one */
159                         if (obj) {
160                                 return;
161                         }
162                 }
163         };
164
165         if (!found) {
166                 fprintf(stderr, "No backtrace available for %p", obj);
167         }
168 }
169
170 /** Inserts a backtrace marker into the provided context
171  *
172  * Allows for maximum laziness and will initialise a circular buffer if one has not already been created.
173  *
174  * Code augmentation should look something like:
175 @verbatim
176         // Create a static cbuffer pointer, the first call to backtrace_attach will initialise it
177         static fr_cbuff *my_obj_bt;
178
179         my_obj_t *alloc_my_obj(TALLOC_CTX *ctx) {
180                 my_obj_t *this;
181
182                 this = talloc(ctx, my_obj_t);
183
184                 // Attach backtrace marker to object
185                 backtrace_attach(&my_obj_bt, this);
186
187                 return this;
188         }
189 @endverbatim
190  *
191  * Then, later when a double free occurs:
192 @verbatim
193         (gdb) call backtrace_print(&my_obj_bt, <pointer to double freed memory>)
194 @endverbatim
195  *
196  * which should print a limited backtrace to stderr. Note, this backtrace will not include any argument
197  * values, but should at least show the code path taken.
198  *
199  * @param cbuff this should be a pointer to a static *fr_cbuff.
200  * @param obj we want to generate a backtrace for.
201  */
202 fr_bt_marker_t *fr_backtrace_attach(fr_cbuff_t **cbuff, TALLOC_CTX *obj)
203 {
204         fr_bt_marker_t *marker;
205
206         if (*cbuff == NULL) {
207                 PTHREAD_MUTEX_LOCK(&fr_debug_init);
208                 /* Check again now we hold the mutex - eww*/
209                 if (*cbuff == NULL) {
210                         TALLOC_CTX *ctx;
211
212                         ctx = fr_autofree_ctx();
213                         *cbuff = fr_cbuff_alloc(ctx, MAX_BT_CBUFF, true);
214                 }
215                 PTHREAD_MUTEX_UNLOCK(&fr_debug_init);
216         }
217
218         marker = talloc(obj, fr_bt_marker_t);
219         if (!marker) {
220                 return NULL;
221         }
222
223         marker->obj = (void *) obj;
224         marker->cbuff = *cbuff;
225
226         talloc_set_destructor(marker, _fr_do_bt);
227
228         return marker;
229 }
230 #else
231 void backtrace_print(UNUSED fr_cbuff_t *cbuff, UNUSED void *obj)
232 {
233         fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
234 }
235 fr_bt_marker_t *fr_backtrace_attach(UNUSED fr_cbuff_t **cbuff, UNUSED TALLOC_CTX *obj)
236 {
237         fprintf(stderr, "Server built without fr_backtrace_* support, requires execinfo.h and possibly -lexecinfo\n");
238         abort();
239 }
240 #endif /* ifdef HAVE_EXECINFO */
241
242 /** Set the dumpable flag, also controls whether processes can PATTACH
243  *
244  * @param dumpable whether we should allow core dumping
245  */
246 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)
247 static int fr_set_dumpable_flag(bool dumpable)
248 {
249         if (prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0) < 0) {
250                 fr_strerror_printf("Cannot re-enable core dumps: prctl(PR_SET_DUMPABLE) failed: %s",
251                                    fr_syserror(errno));
252                 return -1;
253         }
254
255         return 0;
256 }
257 #else
258 static int fr_set_dumpable_flag(UNUSED bool dumpable)
259 {
260         fr_strerror_printf("Changing value of PR_DUMPABLE not supported on this system");
261         return -2;
262 }
263 #endif
264
265 /** Get the processes dumpable flag
266  *
267  * @param dumpable whether we should allow core dumping
268  */
269 #if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_DUMPABLE)
270 static int fr_get_dumpable_flag(void)
271 {
272         int ret;
273
274         ret = prctl(PR_GET_DUMPABLE);
275         if (ret < 0) {
276                 fr_strerror_printf("Cannot get dumpable flag: %s", fr_syserror(errno));
277                 return -1;
278         }
279
280         return ret;
281 }
282 #else
283 static int fr_get_dumpable_flag(void)
284 {
285         fr_strerror_printf("Getting value of PR_DUMPABLE not supported on this system");
286         return -2;
287 }
288 #endif
289
290
291 /** Get the current maximum for core files
292  *
293  * Do this before anything else so as to ensure it's properly initialized.
294  */
295 int fr_set_dumpable_init(void)
296 {
297 #ifdef HAVE_SYS_RESOURCE_H
298         if (getrlimit(RLIMIT_CORE, &core_limits) < 0) {
299                 fr_strerror_printf("Failed to get current core limit:  %s", fr_syserror(errno));
300                 return -1;
301         }
302 #endif
303         return 0;
304 }
305
306 /** Enable or disable core dumps
307  *
308  * @param allow_core_dumps whether to enable or disable core dumps.
309  */
310 int fr_set_dumpable(bool allow_core_dumps)
311 {
312         /*
313          *      If configured, turn core dumps off.
314          */
315         if (!allow_core_dumps) {
316 #ifdef HAVE_SYS_RESOURCE_H
317                 struct rlimit no_core;
318
319                 no_core.rlim_cur = 0;
320                 no_core.rlim_max = 0;
321
322                 if (setrlimit(RLIMIT_CORE, &no_core) < 0) {
323                         fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno));
324
325                         return -1;
326                 }
327 #endif
328                 return 0;
329         }
330
331         if (fr_set_dumpable_flag(true) < 0) return -1;
332
333         /*
334          *      Reset the core dump limits to their original value.
335          */
336 #ifdef HAVE_SYS_RESOURCE_H
337         if (setrlimit(RLIMIT_CORE, &core_limits) < 0) {
338                 fr_strerror_printf("Cannot update core dump limit: %s", fr_syserror(errno));
339
340                 return -1;
341         }
342 #endif
343         return 0;
344 }
345
346 /** Check to see if panic_action file is world writeable
347  *
348  * @return 0 if file is OK, else -1.
349  */
350 static int fr_fault_check_permissions(void)
351 {
352         char const *p, *q;
353         char *filename = NULL;
354         struct stat statbuf;
355
356         /*
357          *      Try and guess which part of the command is the binary, and check to see if
358          *      it's world writeable, to try and save the admin from their own stupidity.
359          *
360          *      @fixme we should do this properly and take into account single and double
361          *      quotes.
362          */
363         if ((q = strchr(panic_action, ' '))) {
364                 if (asprintf(&filename, "%.*s", (int)(q - panic_action), panic_action) < 0) {
365                         fr_strerror_printf("Failed writing panic_action to temporary buffer");
366                         return -1;
367                 }
368                 p = filename;
369         } else {
370                 p = panic_action;
371         }
372
373         if (stat(p, &statbuf) == 0) {
374 #ifdef S_IWOTH
375                 if ((statbuf.st_mode & S_IWOTH) != 0) {
376                         fr_strerror_printf("panic_action file \"%s\" is globally writable", p);
377                         return -1;
378                 }
379 #endif
380         }
381
382         free(filename);
383
384         return 0;
385 }
386
387 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
388  *
389  * @param sig caught
390  */
391 void fr_fault(int sig)
392 {
393         char cmd[sizeof(panic_action) + 20];
394         char *out = cmd;
395         size_t left = sizeof(cmd), ret;
396
397         char const *p = panic_action;
398         char const *q;
399
400         int code;
401
402         fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
403
404         /*
405          *      Check for administrator sanity.
406          */
407         if (fr_fault_check_permissions() < 0) {
408                 fr_fault_log("Refusing to execute panic action: %s\n", fr_strerror());
409                 goto finish;
410         }
411
412         /*
413          *      Run the callback if one was registered
414          */
415         if (panic_cb && (panic_cb(sig) < 0)) goto finish;
416
417         /*
418          *      Produce a simple backtrace - They've very basic but at least give us an
419          *      idea of the area of the code we hit the issue in.
420          */
421 #ifdef HAVE_EXECINFO
422         {
423                 size_t frame_count, i;
424                 void *stack[MAX_BT_FRAMES];
425                 char **strings;
426
427                 frame_count = backtrace(stack, MAX_BT_FRAMES);
428
429                 fr_fault_log("Backtrace of last %zu frames:\n", frame_count);
430                 strings = backtrace_symbols(stack, frame_count);
431                 for (i = 0; i < frame_count; i++) {
432                         fr_fault_log("%s\n", strings[i]);
433                 }
434                 free(strings);
435         }
436 #endif
437
438         /* No panic action set... */
439         if (panic_action[0] == '\0') {
440                 fr_fault_log("No panic action set\n");
441                 goto finish;
442         }
443
444         /* Substitute %p for the current PID (useful for attaching a debugger) */
445         while ((q = strstr(p, "%p"))) {
446                 out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid());
447                 if (left <= ret) {
448                 oob:
449                         fr_fault_log("Panic action too long");
450                         fr_exit_now(1);
451                 }
452                 left -= ret;
453                 p = q + 2;
454         }
455         if (strlen(p) >= left) goto oob;
456         strlcpy(out, p, left);
457
458         fr_fault_log("Calling: %s\n", cmd);
459
460         {
461                 bool disabled;
462                 bool disable = false;
463
464                 /*
465                  *      Here we temporarily enable the dumpable flag so if GBD or LLDB
466                  *      is called in the panic_action, they can pattach tot he running
467                  *      process.
468                  */
469                 disabled = (fr_get_dumpable_flag() == 0);
470                 if (disabled) {
471                         if (fr_set_dumpable_flag(true) < 0) {
472                                 fr_fault_log("Failed setting dumpable flag, pattach may not work: %s", fr_strerror());
473                         } else {
474                                 disable = true;
475                         }
476                 }
477
478                 code = system(cmd);
479
480                 /*
481                  *      We only want to error out here, if dumable was originally disabled
482                  *      and we managed to change the value to enabled, but failed
483                  *      setting it back to disabled.
484                  */
485                 if (disable && (fr_set_dumpable_flag(false) < 0)) {
486                         fr_fault_log("Failed reseting dumpable flag to off: %s", fr_strerror());
487                         fr_fault_log("Exiting due to insecure process state");
488                         fr_exit_now(1);
489                 }
490         }
491
492         fr_fault_log("Panic action exited with %i", code);
493
494 finish:
495 #ifdef SIGUSR1
496         if (sig == SIGUSR1) {
497                 return;
498         }
499 #endif
500         fr_exit_now(1);
501 }
502
503 #ifdef SIGABRT
504 /** Work around debuggers which can't backtrace past the signal handler
505  *
506  * At least this provides us some information when we get talloc errors.
507  */
508 static void _fr_talloc_fault(char const *reason)
509 {
510         fr_fault_log("talloc abort: %s\n", reason);
511         fr_fault(SIGABRT);
512 }
513 #endif
514
515 /** Wrapper to pass talloc log output to our fr_fault_log function
516  *
517  */
518 static void _fr_talloc_log(char const *msg)
519 {
520         fr_fault_log("%s\n", msg);
521 }
522
523 /** Generate a talloc memory report for a context and print to stderr/stdout
524  *
525  * @param ctx to generate a report for, may be NULL in which case the root context is used.
526  */
527 int fr_log_talloc_report(TALLOC_CTX *ctx)
528 {
529         FILE *log;
530         char const *null_ctx = NULL;
531         int i = 0;
532         int fd;
533
534         fd = dup(fr_fault_log_fd);
535         if (fd < 0) {
536                 fr_strerror_printf("Couldn't write memory report, failed to dup log fd: %s", fr_syserror(errno));
537                 return -1;
538         }
539         log = fdopen(fd, "w");
540         if (!log) {
541                 fr_strerror_printf("Couldn't write memory report, fdopen failed: %s", fr_syserror(errno));
542                 return -1;
543         }
544
545         fprintf(log, "Current state of talloced memory:\n");
546         if (ctx) {
547                 null_ctx = talloc_get_name(NULL);
548         }
549
550         if (!ctx) {
551                 talloc_report_full(NULL, log);
552         } else do {
553                 fprintf(log, "Context level %i", i++);
554
555                 talloc_report_full(ctx, log);
556         } while ((ctx = talloc_parent(ctx)) && (talloc_get_name(ctx) != null_ctx));  /* Stop before we hit NULL ctx */
557
558         fclose(log);
559
560         return 0;
561 }
562
563 /** Signal handler to print out a talloc memory report
564  *
565  * @param sig caught
566  */
567 static void _fr_fault_mem_report(int sig)
568 {
569         fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
570
571         if (fr_log_talloc_report(NULL) < 0) fr_perror("memreport");
572 }
573
574 /** Registers signal handlers to execute panic_action on fatal signal
575  *
576  * May be called multiple time to change the panic_action/program.
577  *
578  * @param cmd to execute on fault. If present %p will be substituted
579  *        for the parent PID before the command is executed, and %e
580  *        will be substituted for the currently running program.
581  * @param program Name of program currently executing (argv[0]).
582  * @return 0 on success -1 on failure.
583  */
584 int fr_fault_setup(char const *cmd, char const *program)
585 {
586         static bool setup = false;
587
588         char *out = panic_action;
589         size_t left = sizeof(panic_action), ret;
590
591         char const *p = cmd;
592         char const *q;
593
594         if (cmd) {
595                 /* Substitute %e for the current program */
596                 while ((q = strstr(p, "%e"))) {
597                         out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : "");
598                         if (left <= ret) {
599                         oob:
600                                 fr_strerror_printf("Panic action too long");
601                                 return -1;
602                         }
603                         left -= ret;
604                         p = q + 2;
605                 }
606                 if (strlen(p) >= left) goto oob;
607                 strlcpy(out, p, left);
608         } else {
609                 *panic_action = '\0';
610         }
611
612         /*
613          *      Check for administrator sanity.
614          */
615         if (fr_fault_check_permissions() < 0) return -1;
616
617         /* Unsure what the side effects of changing the signal handler mid execution might be */
618         if (!setup) {
619 #ifdef SIGSEGV
620                 if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1;
621 #endif
622 #ifdef SIGBUS
623                 if (fr_set_signal(SIGBUS, fr_fault) < 0) return -1;
624 #endif
625 #ifdef SIGABRT
626                 if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1;
627                 /*
628                  *  Use this instead of abort so we get a
629                  *  full backtrace with broken versions of LLDB
630                  */
631                 talloc_set_abort_fn(_fr_talloc_fault);
632 #endif
633 #ifdef SIGFPE
634                 if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1;
635 #endif
636
637 #ifdef SIGUSR1
638                 if (fr_set_signal(SIGUSR1, fr_fault) < 0) return -1;
639 #endif
640
641 #ifdef SIGUSR2
642                 if (fr_set_signal(SIGUSR2, _fr_fault_mem_report) < 0) return -1;
643 #endif
644
645                 /*
646                  *  Setup the default logger
647                  */
648                 if (!fr_fault_log) fr_fault_set_log_fn(NULL);
649                 talloc_set_log_fn(_fr_talloc_log);
650
651                 /*
652                  *  Needed for memory reports
653                  */
654                 talloc_enable_null_tracking();
655         }
656         setup = true;
657
658         return 0;
659 }
660
661 /** Set a callback to be called before fr_fault()
662  *
663  * @param func to execute. If callback returns < 0
664  *      fr_fault will exit before running panic_action code.
665  */
666 void fr_fault_set_cb(fr_fault_cb_t func)
667 {
668         panic_cb = func;
669 };
670
671 /** Default logger, logs output to stderr
672  *
673  */
674 static void CC_HINT(format (printf, 1, 2)) _fr_fault_log(char const *msg, ...)
675 {
676         va_list ap;
677
678         va_start(ap, msg);
679         vfprintf(stderr, msg, ap);
680         va_end(ap);
681 }
682
683
684 /** Set a file descriptor to log panic_action output to.
685  *
686  * @param func to call to output log messages.
687  */
688 void fr_fault_set_log_fn(fr_fault_log_t func)
689 {
690         fr_fault_log = func ? func : _fr_fault_log;
691 }
692
693 /** Set a file descriptor to log memory reports to.
694  *
695  * @param fd to write output to.
696  */
697 void fr_fault_set_log_fd(int fd)
698 {
699         fr_fault_log_fd = fd;
700 }
701
702
703 #ifdef WITH_VERIFY_PTR
704
705 /*
706  *      Verify a VALUE_PAIR
707  */
708 inline void fr_verify_vp(VALUE_PAIR const *vp)
709 {
710         (void) talloc_get_type_abort(vp, VALUE_PAIR);
711
712         if (vp->data.ptr) switch (vp->da->type) {
713         case PW_TYPE_OCTETS:
714         case PW_TYPE_TLV:
715         {
716                 size_t len;
717
718                 if (!talloc_get_type(vp->data.ptr, uint8_t)) {
719                         fr_perror("Type check failed for attribute \"%s\"", vp->da->name);
720                         (void) talloc_get_type_abort(vp->data.ptr, uint8_t);
721                 }
722
723                 len = talloc_array_length(vp->vp_octets);
724                 if (vp->length > len) {
725                         fr_perror("VALUE_PAIR length %zu does not equal uint8_t buffer length %zu", vp->length, len);
726                         fr_assert(0);
727                         fr_exit_now(1);
728                 }
729         }
730                 break;
731
732         case PW_TYPE_STRING:
733         {
734                 size_t len;
735
736                 if (!talloc_get_type(vp->data.ptr, char)) {
737                         fr_perror("Type check failed for attribute \"%s\"", vp->da->name);
738                         (void) talloc_get_type_abort(vp->data.ptr, char);
739                 }
740
741                 len = (talloc_array_length(vp->vp_strvalue) - 1);
742                 if (vp->length > len) {
743                         fr_perror("VALUE_PAIR %s length %zu is too small for char buffer length %zu",
744                                   vp->da->name, vp->length, len);
745                         fr_assert(0);
746                         fr_exit_now(1);
747                 }
748                 if (vp->vp_strvalue[vp->length] != '\0') {
749                         fr_perror("VALUE_PAIR %s buffer not \\0 terminated", vp->da->name);
750                         fr_assert(0);
751                         fr_exit_now(1);
752                 }
753         }
754                 break;
755
756         default:
757                 break;
758         }
759 }
760
761 /*
762  *      Verify a pair list
763  */
764 void fr_verify_list(TALLOC_CTX *expected, VALUE_PAIR *vps)
765 {
766         vp_cursor_t cursor;
767         VALUE_PAIR *vp;
768         TALLOC_CTX *parent;
769
770         for (vp = fr_cursor_init(&cursor, &vps);
771              vp;
772              vp = fr_cursor_next(&cursor)) {
773                 VERIFY_VP(vp);
774
775                 parent = talloc_parent(vp);
776                 if (expected && (parent != expected)) {
777                         fr_perror("Expected VALUE_PAIR (%s) to be parented by %p (%s), "
778                                   "but parented by %p (%s)",
779                                   vp->da->name,
780                                   expected, talloc_get_name(expected),
781                                   parent, parent ? talloc_get_name(parent) : "NULL");
782
783                         fr_log_talloc_report(expected);
784                         if (parent) fr_log_talloc_report(parent);
785
786                         assert(0);
787                 }
788
789         }
790 }
791 #endif