* Began (but did not finish) applying GPL3+ declarations to each source file. This...
[citadel.git] / citadel / ecrash.c
1 /*
2  * $Id$
3  *
4  * author: David Frascone
5  * 
6  *  eCrash Implementation
7  *
8  *  eCrash will allow you to capture stack traces in the
9  *  event of a crash, and write those traces to disk, stdout,
10  *  or any other file handle.
11  *
12  *  modified to integrate closer into citadel by Wilfried Goesgens
13  *
14  * vim: ts=4
15  *
16  *  This program is free software; you can redistribute it and/or modify
17  *  it under the terms of the GNU General Public License as published by
18  *  the Free Software Foundation; either version 3 of the License, or
19  *  (at your option) any later version.
20  *
21  *  This program is distributed in the hope that it will be useful,
22  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  *  GNU General Public License for more details.
25  *
26  *  You should have received a copy of the GNU General Public License
27  *  along with this program; if not, write to the Free Software
28  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29  */
30
31 #include "sysdep.h"
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <string.h>
37 #include <fcntl.h>
38 #include <syslog.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <pthread.h>
42 #include <libcitadel.h>
43 #include "server.h"
44 #include "sysdep_decls.h"
45 #include "support.h"
46 #include "config.h"
47 #include "citserver.h"
48 #include "ecrash.h"
49
50 #define NIY()   printf("%s: Not Implemented Yet!\n", __FUNCTION__)
51 #ifdef HAVE_BACKTRACE
52 #include <execinfo.h>
53 static eCrashParameters gbl_params;
54
55 static int    gbl_backtraceEntries;
56 static void **gbl_backtraceBuffer;
57 static char **gbl_backtraceSymbols;
58 static int    gbl_backtraceDoneFlag = 0;
59
60 static void *stack_frames[50];
61 static size_t size, NThread;
62 static char **strings;
63 static char StaticBuf[SIZ];
64
65 /* 
66  * Private structures for our thread list
67  */
68 typedef struct thread_list_node{
69         char *threadName;
70         pthread_t thread;
71         int backtraceSignal;
72         sighandler_t oldHandler;
73         struct thread_list_node *Next;
74 } ThreadListNode;
75
76 static pthread_mutex_t ThreadListMutex = PTHREAD_MUTEX_INITIALIZER;
77 static ThreadListNode *ThreadList = NULL;
78
79 /*********************************************************************
80  *********************************************************************
81  **     P  R  I  V  A  T  E      F  U  N  C  T  I  O  N  S
82  *********************************************************************
83  ********************************************************************/
84
85
86 /*!
87  * Insert a node into our threadList
88  *
89  * @param name   Text string indicating our thread
90  * @param thread Our Thread Id
91  * @param signo  Signal to create backtrace with
92  * @param old_handler Our old handler for signo
93  *
94  * @returns zero on success
95  */
96 static int addThreadToList(char *name, pthread_t thread,int signo,
97                                            sighandler_t old_handler)
98 {
99         ThreadListNode *node;
100
101         node = malloc(sizeof(ThreadListNode));
102         if (!node) return -1;
103
104         DPRINTF(ECRASH_DEBUG_VERBOSE,
105                                         "Adding thread 0x%08x (%s)\n", (unsigned int)thread, name);
106         node->threadName = strdup(name);
107         node->thread = thread;
108         node->backtraceSignal = signo;
109         node->oldHandler = old_handler;
110
111         /* And, add it to the list */
112         pthread_mutex_lock(&ThreadListMutex);
113         node->Next = ThreadList;
114         ThreadList = node;
115         pthread_mutex_unlock(&ThreadListMutex);
116         
117         return 0;
118
119 } // addThreadToList
120
121 /*!
122  * Remove a node from our threadList
123  *
124  * @param thread Our Thread Id
125  *
126  * @returns zero on success
127  */
128 static int removeThreadFromList(pthread_t thread)
129 {
130         ThreadListNode *Probe, *Prev=NULL;
131         ThreadListNode *Removed = NULL;
132
133         DPRINTF(ECRASH_DEBUG_VERBOSE,
134                                         "Removing thread 0x%08x from list . . .\n", (unsigned int)thread);
135         pthread_mutex_lock(&ThreadListMutex);
136         for (Probe=ThreadList;Probe != NULL; Probe = Probe->Next) {
137                 if (Probe->thread == thread) {
138                         // We found it!  Unlink it and move on!
139                         Removed = Probe;
140                         if (Prev == NULL) { // head of list
141                                 ThreadList = Probe->Next;
142                         } else {
143                                 // Prev != null, so we need to link around ourselves.
144                                 Prev->Next = Probe->Next;
145                         }
146                         Removed->Next = NULL;
147                         break;
148                 }
149
150                 Prev = Probe;
151         }
152         pthread_mutex_unlock(&ThreadListMutex);
153
154         // Now, if something is in Removed, free it, and return success
155         if (Removed) {
156             DPRINTF(ECRASH_DEBUG_VERBOSE,
157                                                 "   Found %s -- removing\n", Removed->threadName);
158                 // Reset the signal handler
159                 signal(Removed->backtraceSignal, Removed->oldHandler);
160
161                 // And free the allocated memory
162                 free (Removed->threadName);
163                 free (Removed);
164
165                 return 0;
166         } else {
167             DPRINTF(ECRASH_DEBUG_VERBOSE,
168                                                 "   Not Found\n");
169                 return -1; // Not Found
170         }
171 } // removeThreadFromList
172
173 /*!
174  * Print out a line of output to all our destinations
175  *
176  * One by one, output a line of text to all of our output destinations.
177  *
178  * Return failure if we fail to output to any of them.
179  *
180  * @param format   Normal printf style vararg format
181  *
182  * @returns nothing// bytes written, or error on failure.
183  */
184 static void outputPrintf(char *format, ...)
185 {
186         va_list ap;
187
188         va_start(ap, format);
189
190         if (enable_syslog)
191         {
192                 snprintf (StaticBuf, SIZ, format, ap);
193                 syslog( LOG_CRIT|LOG_NDELAY|LOG_MAIL, StaticBuf);
194         }
195         else
196                 CtdlLogPrintf(CTDL_EMERG, format, ap);
197
198 } // outputPrintf
199
200
201
202 /*!
203  * Dump our backtrace into a global location
204  *
205  * This function will dump out our backtrace into our
206  * global holding area.
207  *
208  */
209 static void createGlobalBacktrace( void )
210 {
211
212         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
213         if (enable_syslog)
214                 for (NThread = 0; NThread < size; NThread++) 
215                 {
216                         snprintf (StaticBuf, SIZ, "RAW: %p  ", stack_frames[NThread]);
217                         syslog( LOG_CRIT|LOG_NDELAY|LOG_MAIL, StaticBuf);
218                 }
219         else 
220                 for (NThread = 0; NThread < size; NThread++) 
221                         CtdlLogPrintf(1, "RAW: %p\n", stack_frames[NThread]);
222         strings = backtrace_symbols(stack_frames, size);
223         for (NThread = 0; NThread < size; NThread++) {
224                 if (strings != NULL) {
225                         if (enable_syslog)
226                         {// vsyslogs printf compliance sucks.
227                                 snprintf (StaticBuf, SIZ, "RAW: %p  ", strings[NThread]);
228                                 syslog( LOG_CRIT|LOG_NDELAY|LOG_MAIL, StaticBuf);
229                         }
230                         else
231                                 CtdlLogPrintf(1, "%s\n", strings[NThread]);
232                 }
233         }
234 } /* createGlobalBacktrace */
235 static void outputRawtrace( void )
236 {
237
238         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
239         if (enable_syslog)
240                 for (NThread = 0; NThread < size; NThread++) 
241                 {
242                         snprintf (StaticBuf, SIZ, "RAW: %p  ", stack_frames[NThread]);
243                         syslog( LOG_CRIT|LOG_NDELAY|LOG_MAIL, StaticBuf);
244                 }
245         else 
246                 for (NThread = 0; NThread < size; NThread++) 
247                         CtdlLogPrintf(1, "RAW: %p\n", stack_frames[NThread]);
248 } /* createGlobalBacktrace */
249
250 /*!
251  * Print out (to all the fds, etc), or global backtrace
252  */
253 static void outputGlobalBacktrace ( void )
254 {
255         int i;
256
257         for (i=0; i < gbl_backtraceEntries; i++) {
258                 if (gbl_backtraceSymbols != FALSE) {
259                         outputPrintf("*      Frame %02x: %s\n",
260                                      i, gbl_backtraceSymbols[i]);
261                 } else {
262                         outputPrintf("*      Frame %02x: %p\n", i,
263                                      gbl_backtraceBuffer[i]);
264                 }
265         }
266 } // outputGlobalBacktrace
267
268 /*!
269  * Output our current stack's backtrace
270  */
271 static void outputBacktrace( void )
272 {
273         createGlobalBacktrace();
274         outputGlobalBacktrace();
275 } /* outputBacktrace */
276
277 static void outputBacktraceThreads( void )
278 {
279         ThreadListNode *probe;
280         int i;
281
282         // When we're backtracing, don't worry about the mutex . . hopefully
283         // we're in a safe place.
284
285         for (probe=ThreadList; probe; probe=probe->Next) {
286                 gbl_backtraceDoneFlag = 0;
287                 pthread_kill(probe->thread, probe->backtraceSignal);
288                 for (i=0; i < gbl_params.threadWaitTime; i++) {
289                         if (gbl_backtraceDoneFlag)
290                                 break;
291                         sleep(1);
292                 }
293                 if (gbl_backtraceDoneFlag) {
294                         outputPrintf("*  Backtrace of \"%s\" (0x%08x)\n", 
295                                                  probe->threadName, (unsigned int)probe->thread);
296                         outputGlobalBacktrace();
297                 } else {
298                         outputPrintf("*  Error: unable to get backtrace of \"%s\" (0x%08x)\n", 
299                                                  probe->threadName, (unsigned int)probe->thread);
300                 }
301                 outputPrintf("*\n");
302         }
303 } // outputBacktraceThreads
304
305
306 /*!
307  * Handle signals (crash signals)
308  *
309  * This function will catch all crash signals, and will output the
310  * crash dump.  
311  *
312  * It will physically write (and sync) the current thread's information
313  * before it attempts to send signals to other threads.
314  * 
315  * @param signum Signal received.
316  */
317 static void crash_handler(int signo)
318 {
319         outputRawtrace();
320         outputPrintf("*********************************************************\n");
321         outputPrintf("*               eCrash Crash Handler\n");
322         outputPrintf("*********************************************************\n");
323         outputPrintf("*\n");
324         outputPrintf("*  Got a crash! signo=%d\n", signo);
325         outputPrintf("*\n");
326         outputPrintf("*  Offending Thread's Backtrace:\n");
327         outputPrintf("*\n");
328         outputBacktrace();
329         outputPrintf("*\n");
330
331         if (gbl_params.dumpAllThreads != FALSE) {
332                 outputBacktraceThreads();
333         }
334
335         outputPrintf("*\n");
336         outputPrintf("*********************************************************\n");
337         outputPrintf("*               eCrash Crash Handler\n");
338         outputPrintf("*********************************************************\n");
339
340         exit(signo);
341 } // crash_handler
342
343 /*!
344  * Handle signals (bt signals)
345  *
346  * This function shoudl be called to generate a crashdump into our
347  * global area.  Once the dump has been completed, this function will
348  * return after tickling a global.  Since mutexes are not async
349  * signal safe, the main thread, after signaling us to generate our
350  * own backtrace, will sleep for a few seconds waiting for us to complete.
351  *
352  * @param signum Signal received.
353  */
354 static void bt_handler(int signo)
355 {
356         createGlobalBacktrace();
357         gbl_backtraceDoneFlag=1;
358 } // bt_handler
359
360 /*!
361  * Validate a passed-in symbol table
362  *
363  * For now, just print it out (if verbose), and make sure it's
364  * sorted and none of the pointers are zero.
365  */
366 static int ValidateSymbolTable( void )
367 {
368         int i;
369         int rc=0;
370         unsigned long lastAddress =0;
371
372         // Get out of here if the table is empty
373         if (!gbl_params.symbolTable) return 0;
374
375         // Dump it in verbose mode
376         DPRINTF(ECRASH_DEBUG_VERBOSE,
377                                         "Symbol Table Provided with %d symbols\n",
378                                         gbl_params.symbolTable->numSymbols);
379         for (i=0; i < gbl_params.symbolTable->numSymbols; i++){
380                 // Dump it in verbose mode
381                 DPRINTF(ECRASH_DEBUG_VERBOSE, 
382                                 "%-30s %p\n",
383                                 gbl_params.symbolTable->symbols[i].function,
384                                 gbl_params.symbolTable->symbols[i].address);
385                 if (lastAddress >
386                     (unsigned long)gbl_params.symbolTable->symbols[i].address) {
387                         DPRINTF(ECRASH_DEBUG_ERROR,
388                                         "Error: symbol table is not sorted (last=%p, current=%p)\n",
389                                         (void *)lastAddress,
390                                         gbl_params.symbolTable->symbols[i].address);
391                         rc = -1;
392                 }
393
394         } // for
395
396         return rc;
397         
398 } // ValidateSymbolTable
399
400 /*********************************************************************
401  *********************************************************************
402  **      P  U  B  L  I  C      F  U  N  C  T  I  O  N  S
403  *********************************************************************
404  ********************************************************************/
405
406 /*!
407  * Initialize eCrash.
408  * 
409  * This function must be called before calling any other eCrash
410  * functions.  It sets up the global behavior of the system, and
411  * registers the calling thread for crash dumps.
412  *
413  * @param params Our input parameters.  The passed in structure will be copied.
414  *
415  * @return Zero on success.
416  */
417 int eCrash_Init(eCrashParameters *params)
418 {
419         int sigIndex;
420         int ret = 0;
421 #ifdef DO_SIGNALS_RIGHT
422         sigset_t blocked;
423         struct sigaction act;
424 #endif
425
426         DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,"Init Starting params = %p\n", params);
427
428         // Allocate our backtrace area
429         gbl_backtraceBuffer = malloc(sizeof(void *) * (params->maxStackDepth+5));
430
431 #ifdef DO_SIGNALS_RIGHT
432         sigemptyset(&blocked);
433         act.sa_sigaction = crash_handler;
434         act.sa_mask = blocked;
435         act.sa_flags = SA_SIGINFO;
436 #endif
437
438         if (params != NULL) {
439                 // Make ourselves a global copy of params.
440                 gbl_params = *params;
441                 gbl_params.filename = strdup(params->filename);
442
443                 // Set our defaults, if they weren't specified
444                 if (gbl_params.maxStackDepth == 0 )
445                         gbl_params.maxStackDepth = ECRASH_DEFAULT_STACK_DEPTH;
446
447                 if (gbl_params.defaultBacktraceSignal == 0 )
448                         gbl_params.defaultBacktraceSignal = ECRASH_DEFAULT_BACKTRACE_SIGNAL;
449
450                 if (gbl_params.threadWaitTime == 0 )
451                         gbl_params.threadWaitTime = ECRASH_DEFAULT_THREAD_WAIT_TIME;
452
453                 if (gbl_params.debugLevel == 0 )
454                         gbl_params.debugLevel = ECRASH_DEBUG_DEFAULT;
455
456                 // Copy our symbol table
457                 if (gbl_params.symbolTable) {
458                     DPRINTF(ECRASH_DEBUG_VERBOSE,
459                                                         "symbolTable @ %p -- %d symbols\n", gbl_params.symbolTable,
460                                                 gbl_params.symbolTable->numSymbols);
461                         // Make a copy of our symbol table
462                         gbl_params.symbolTable = malloc(sizeof(eCrashSymbolTable));
463                         memcpy(gbl_params.symbolTable, params->symbolTable,
464                                    sizeof(eCrashSymbolTable));
465
466                         // Now allocate / copy the actual table.
467                         gbl_params.symbolTable->symbols = malloc(sizeof(eCrashSymbol) *
468                                                                      gbl_params.symbolTable->numSymbols);
469                         memcpy(gbl_params.symbolTable->symbols,
470                                    params->symbolTable->symbols,
471                                    sizeof(eCrashSymbol) * gbl_params.symbolTable->numSymbols);
472
473                         ValidateSymbolTable();
474                 }
475         
476                 // And, finally, register for our signals
477                 for (sigIndex=0; gbl_params.signals[sigIndex] != 0; sigIndex++) {
478                         DPRINTF(ECRASH_DEBUG_VERY_VERBOSE,
479                                                         "   Catching signal[%d] %d\n", sigIndex,
480                                         gbl_params.signals[sigIndex]);
481
482                         // I know there's a better way to catch signals with pthreads.
483                         // I'll do it later TODO
484                         signal(gbl_params.signals[sigIndex], crash_handler);
485                 }
486         } else {
487                 DPRINTF(ECRASH_DEBUG_ERROR, "   Error:  Null Params!\n");
488                 ret = -1;
489         }
490         DPRINTF(ECRASH_DEBUG_VERY_VERBOSE, "Init Complete ret=%d\n", ret);
491         return ret;
492 } /* eCrash_Init */
493
494 /*!
495  * UnInitialize eCrash.
496  * 
497  * This function may be called to de-activate eCrash, release the
498  * signal handlers, and free any memory allocated by eCrash.
499  *
500  * @return Zero on success.
501  */
502 int eCrash_Uninit( void )
503 {
504         NIY();
505
506         return 0;
507 } /* eCrash_Uninit */
508
509 /*!
510  * Register a thread for backtracing on crash.
511  * 
512  * This function must be called by any thread wanting it's stack
513  * dumped in the event of a crash.  The thread my specify what 
514  * signal should be used, or the default, SIGUSR1 will be used.
515  *
516  * @param signo Signal to use to generate dump (default: SIGUSR1)
517  *
518  * @return Zero on success.
519  */
520 int eCrash_RegisterThread(char *name, int signo)
521 {
522         sighandler_t old_handler;
523
524         // Register for our signal
525         if (signo == 0) {
526                 signo = gbl_params.defaultBacktraceSignal;
527         }
528
529         old_handler = signal(signo, bt_handler);
530         return addThreadToList(name, pthread_self(), signo, old_handler);
531
532 } /* eCrash_RegisterThread */
533
534 /*!
535  * Un-register a thread for stack dumps.
536  * 
537  * This function may be called to un-register any previously 
538  * registered thread.
539  *
540  * @return Zero on success.
541  */
542 int eCrash_UnregisterThread( void )
543 {
544         return removeThreadFromList(pthread_self());
545 } /* eCrash_UnregisterThread */
546
547 #endif