Backport panic_action
[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 <freeradius-devel/libradius.h>
25
26 /*
27  *      runtime backtrace functions are not POSIX but are included in
28  *      glibc, OSX >= 10.5 and various BSDs
29  */
30 #ifdef HAVE_EXECINFO_H
31 #  include <execinfo.h>
32 #  define MAX_BT_FRAMES 128
33 #endif
34
35 static char panic_action[512];
36
37 /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
38  *
39  * @param sig caught
40  */
41 static void NEVER_RETURNS _fr_fault(int sig)
42 {
43         char cmd[sizeof(panic_action) + 20];
44         char *out = cmd;
45         size_t left = sizeof(cmd), ret;
46
47         char const *p = panic_action;
48         char const *q;
49
50         int code;
51
52         fprintf(stderr, "FATAL SIGNAL: %s\n", strsignal(sig));
53
54         /*
55          *      Produce a simple backtrace - They've very basic but at least give us an
56          *      idea of the area of the code we hit the issue in.
57          */
58 #ifdef HAVE_EXECINFO_H
59         size_t frame_count, i;
60         void *stack[MAX_BT_FRAMES];
61         char **frames;
62
63         frame_count = backtrace(stack, MAX_BT_FRAMES);
64         frames = backtrace_symbols(stack, frame_count);
65
66         fprintf(stderr, "Backtrace of last %zu frames:\n", frame_count);
67         for (i = 0; i < frame_count; i++) {
68                 fprintf(stderr, "%s\n", frames[i]);
69                 /* Leak the backtrace strings, freeing may lead to undefined behaviour... */
70         }
71 #endif
72
73         /* No panic action set... */
74         if (panic_action[0] == '\0') {
75                 fprintf(stderr, "No panic action set\n");
76                 _exit(1);
77         }
78
79         /* Substitute %p for the current PID (useful for attaching a debugger) */
80         while ((q = strstr(p, "%p"))) {
81                 out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid());
82                 if (left <= ret) {
83                 oob:
84                         fprintf(stderr, "Panic action too long\n");
85                         _exit(1);
86                 }
87                 left -= ret;
88                 p = q + 2;
89         }
90         if (strlen(p) >= left) goto oob;
91         strlcpy(out, p, left);
92
93         fprintf(stderr, "Calling: %s\n", cmd);
94         code = system(cmd);
95         fprintf(stderr, "Panic action exited with %i\n", code);
96
97         _exit(1);
98 }
99
100 /** Registers signal handlers to execute panic_action on fatal signal
101  *
102  * May be called multiple time to change the panic_action/program.
103  *
104  * @param cmd to execute on fault. If present %p will be substituted
105  *        for the parent PID before the command is executed, and %e
106  *        will be substituted for the currently running program.
107  * @param program Name of program currently executing (argv[0]).
108  * @return 0 on success -1 on failure.
109  */
110 int fr_fault_setup(char const *cmd, char const *program)
111 {
112         static int setup = FALSE;
113
114         char *out = panic_action;
115         size_t left = sizeof(panic_action), ret;
116
117         char const *p = cmd;
118         char const *q;
119
120         if (cmd) {
121                 /* Substitute %e for the current program */
122                 while ((q = strstr(p, "%e"))) {
123                         out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : "");
124                         if (left <= ret) {
125                         oob:
126                                 fr_strerror_printf("Panic action too long");
127                                 return -1;
128                         }
129                         left -= ret;
130                         p = q + 2;
131                 }
132                 if (strlen(p) >= left) goto oob;
133                 strlcpy(out, p, left);
134         } else {
135                 *panic_action = '\0';
136         }
137
138         /* Unsure what the side effects of changing the signal handler mid execution might be */
139         if (!setup) {
140 #ifdef SIGSEGV
141                 if (fr_set_signal(SIGSEGV, _fr_fault) < 0) return -1;
142 #endif
143 #ifdef SIGBUS
144                 if (fr_set_signal(SIGBUS, _fr_fault) < 0) return -1;
145 #endif
146 #ifdef SIGABRT
147                 if (fr_set_signal(SIGABRT, _fr_fault) < 0) return -1;
148 #endif
149 #ifdef SIGFPE
150                 if (fr_set_signal(SIGFPE, _fr_fault) < 0) return -1;
151 #endif
152         }
153         setup = TRUE;
154
155         return 0;
156 }