/*
 * sigcatcher.c --- print a backtrace on a SIGSEGV, et. al
 *
 * Copyright (C) 2011 Theodore Ts'o.
 *
 * %Begin-Header%
 * This file may be redistributed under the terms of the GNU Public
 * License.
 * %End-Header%
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif

#include "e2fsck.h"

struct str_table {
	int	num;
	const char	*name;
};

#define DEFINE_ENTRY(SYM)	{ SYM, #SYM },
#define END_TABLE		{ 0, 0 }

static struct str_table sig_table[] = {
#ifdef SIGHUP
	DEFINE_ENTRY(SIGHUP)
#endif
#ifdef SIGINT
	DEFINE_ENTRY(SIGINT)
#endif
#ifdef SIGQUIT
	DEFINE_ENTRY(SIGQUIT)
#endif
#ifdef SIGILL
	DEFINE_ENTRY(SIGILL)
#endif
#ifdef SIGTRAP
	DEFINE_ENTRY(SIGTRAP)
#endif
#ifdef SIGABRT
	DEFINE_ENTRY(SIGABRT)
#endif
#ifdef SIGIOT
	DEFINE_ENTRY(SIGIOT)
#endif
#ifdef SIGBUS
	DEFINE_ENTRY(SIGBUS)
#endif
#ifdef SIGFPE
	DEFINE_ENTRY(SIGFPE)
#endif
#ifdef SIGKILL
	DEFINE_ENTRY(SIGKILL)
#endif
#ifdef SIGUSR1
	DEFINE_ENTRY(SIGUSR1)
#endif
#ifdef SIGSEGV
	DEFINE_ENTRY(SIGSEGV)
#endif
#ifdef SIGUSR2
	DEFINE_ENTRY(SIGUSR2)
#endif
#ifdef SIGPIPE
	DEFINE_ENTRY(SIGPIPE)
#endif
#ifdef SIGALRM
	DEFINE_ENTRY(SIGALRM)
#endif
#ifdef SIGTERM
	DEFINE_ENTRY(SIGTERM)
#endif
#ifdef SIGSTKFLT
	DEFINE_ENTRY(SIGSTKFLT)
#endif
#ifdef SIGCHLD
	DEFINE_ENTRY(SIGCHLD)
#endif
#ifdef SIGCONT
	DEFINE_ENTRY(SIGCONT)
#endif
#ifdef SIGSTOP
	DEFINE_ENTRY(SIGSTOP)
#endif
#ifdef SIGTSTP
	DEFINE_ENTRY(SIGTSTP)
#endif
#ifdef SIGTTIN
	DEFINE_ENTRY(SIGTTIN)
#endif
#ifdef SIGTTOU
	DEFINE_ENTRY(SIGTTOU)
#endif
#ifdef SIGURG
	DEFINE_ENTRY(SIGURG)
#endif
#ifdef SIGXCPU
	DEFINE_ENTRY(SIGXCPU)
#endif
#ifdef SIGXFSZ
	DEFINE_ENTRY(SIGXFSZ)
#endif
#ifdef SIGVTALRM
	DEFINE_ENTRY(SIGVTALRM)
#endif
#ifdef SIGPROF
	DEFINE_ENTRY(SIGPROF)
#endif
#ifdef SIGWINCH
	DEFINE_ENTRY(SIGWINCH)
#endif
#ifdef SIGIO
	DEFINE_ENTRY(SIGIO)
#endif
#ifdef SIGPOLL
	DEFINE_ENTRY(SIGPOLL)
#endif
#ifdef SIGPWR
	DEFINE_ENTRY(SIGPWR)
#endif
#ifdef SIGSYS
	DEFINE_ENTRY(SIGSYS)
#endif
	END_TABLE
};

static struct str_table generic_code_table[] = {
#ifdef SI_ASYNCNL
	DEFINE_ENTRY(SI_ASYNCNL)
#endif
#ifdef SI_TKILL
	DEFINE_ENTRY(SI_TKILL)
#endif
#ifdef SI_SIGIO
	DEFINE_ENTRY(SI_SIGIO)
#endif
#ifdef SI_ASYNCIO
	DEFINE_ENTRY(SI_ASYNCIO)
#endif
#ifdef SI_MESGQ
	DEFINE_ENTRY(SI_MESGQ)
#endif
#ifdef SI_TIMER
	DEFINE_ENTRY(SI_TIMER)
#endif
#ifdef SI_QUEUE
	DEFINE_ENTRY(SI_QUEUE)
#endif
#ifdef SI_USER
	DEFINE_ENTRY(SI_USER)
#endif
#ifdef SI_KERNEL
	DEFINE_ENTRY(SI_KERNEL)
#endif
	END_TABLE
};

static struct str_table sigill_code_table[] = {
#ifdef ILL_ILLOPC
	DEFINE_ENTRY(ILL_ILLOPC)
#endif
#ifdef ILL_ILLOPN
	DEFINE_ENTRY(ILL_ILLOPN)
#endif
#ifdef ILL_ILLADR
	DEFINE_ENTRY(ILL_ILLADR)
#endif
#ifdef ILL_ILLTRP
	DEFINE_ENTRY(ILL_ILLTRP)
#endif
#ifdef ILL_PRVOPC
	DEFINE_ENTRY(ILL_PRVOPC)
#endif
#ifdef ILL_PRVREG
	DEFINE_ENTRY(ILL_PRVREG)
#endif
#ifdef ILL_COPROC
	DEFINE_ENTRY(ILL_COPROC)
#endif
#ifdef ILL_BADSTK
	DEFINE_ENTRY(ILL_BADSTK)
#endif
#ifdef BUS_ADRALN
	DEFINE_ENTRY(BUS_ADRALN)
#endif
#ifdef BUS_ADRERR
	DEFINE_ENTRY(BUS_ADRERR)
#endif
#ifdef BUS_OBJERR
	DEFINE_ENTRY(BUS_OBJERR)
#endif
	END_TABLE
};

static struct str_table sigfpe_code_table[] = {
#ifdef FPE_INTDIV
	DEFINE_ENTRY(FPE_INTDIV)
#endif
#ifdef FPE_INTOVF
	DEFINE_ENTRY(FPE_INTOVF)
#endif
#ifdef FPE_FLTDIV
	DEFINE_ENTRY(FPE_FLTDIV)
#endif
#ifdef FPE_FLTOVF
	DEFINE_ENTRY(FPE_FLTOVF)
#endif
#ifdef FPE_FLTUND
	DEFINE_ENTRY(FPE_FLTUND)
#endif
#ifdef FPE_FLTRES
	DEFINE_ENTRY(FPE_FLTRES)
#endif
#ifdef FPE_FLTINV
	DEFINE_ENTRY(FPE_FLTINV)
#endif
#ifdef FPE_FLTSUB
	DEFINE_ENTRY(FPE_FLTSUB)
#endif
	END_TABLE
};

static struct str_table sigsegv_code_table[] = {
#ifdef SEGV_MAPERR
	DEFINE_ENTRY(SEGV_MAPERR)
#endif
#ifdef SEGV_ACCERR
	DEFINE_ENTRY(SEGV_ACCERR)
#endif
	END_TABLE
};


static struct str_table sigbus_code_table[] = {
#ifdef BUS_ADRALN
	DEFINE_ENTRY(BUS_ADRALN)
#endif
#ifdef BUS_ADRERR
	DEFINE_ENTRY(BUS_ADRERR)
#endif
#ifdef BUS_OBJERR
	DEFINE_ENTRY(BUS_OBJERR)
#endif
	END_TABLE
};

#if 0 /* should this be hooked in somewhere? */
static struct str_table sigstrap_code_table[] = {
#ifdef TRAP_BRKPT
	DEFINE_ENTRY(TRAP_BRKPT)
#endif
#ifdef TRAP_TRACE
	DEFINE_ENTRY(TRAP_TRACE)
#endif
	END_TABLE
};
#endif

static struct str_table sigcld_code_table[] = {
#ifdef CLD_EXITED
	DEFINE_ENTRY(CLD_EXITED)
#endif
#ifdef CLD_KILLED
	DEFINE_ENTRY(CLD_KILLED)
#endif
#ifdef CLD_DUMPED
	DEFINE_ENTRY(CLD_DUMPED)
#endif
#ifdef CLD_TRAPPED
	DEFINE_ENTRY(CLD_TRAPPED)
#endif
#ifdef CLD_STOPPED
	DEFINE_ENTRY(CLD_STOPPED)
#endif
#ifdef CLD_CONTINUED
	DEFINE_ENTRY(CLD_CONTINUED)
#endif
	END_TABLE
};

#if 0 /* should this be hooked in somewhere? */
static struct str_table sigpoll_code_table[] = {
#ifdef POLL_IN
	DEFINE_ENTRY(POLL_IN)
#endif
#ifdef POLL_OUT
	DEFINE_ENTRY(POLL_OUT)
#endif
#ifdef POLL_MSG
	DEFINE_ENTRY(POLL_MSG)
#endif
#ifdef POLL_ERR
	DEFINE_ENTRY(POLL_ERR)
#endif
#ifdef POLL_PRI
	DEFINE_ENTRY(POLL_PRI)
#endif
#ifdef POLL_HUP
	DEFINE_ENTRY(POLL_HUP)
#endif
	END_TABLE
};
#endif

static const char *lookup_table(int num, struct str_table *table)
{
	struct str_table *p;

	for (p=table; p->name; p++)
		if (num == p->num)
			return(p->name);
	return NULL;
}

static const char *lookup_table_fallback(int num, struct str_table *table)
{
	static char buf[32];
	const char *ret = lookup_table(num, table);

	if (ret)
		return ret;
	snprintf(buf, sizeof(buf), "%d", num);
	buf[sizeof(buf)-1] = 0;
	return buf;
}

static void die_signal_handler(int signum, siginfo_t *siginfo,
			       void *context EXT2FS_ATTR((unused)))
{
       void *stack_syms[32];
       int frames;
       const char *cp;

       fprintf(stderr, "Signal (%d) %s ", signum,
	       lookup_table_fallback(signum, sig_table));
       if (siginfo->si_code == SI_USER)
	       fprintf(stderr, "(sent from pid %u) ", siginfo->si_pid);
       cp = lookup_table(siginfo->si_code, generic_code_table);
       if (cp)
	       fprintf(stderr, "si_code=%s ", cp);
       else if (signum == SIGILL)
	       fprintf(stderr, "si_code=%s ",
		       lookup_table_fallback(siginfo->si_code,
					     sigill_code_table));
       else if (signum == SIGFPE)
	       fprintf(stderr, "si_code=%s ",
		       lookup_table_fallback(siginfo->si_code,
					     sigfpe_code_table));
       else if (signum == SIGSEGV)
	       fprintf(stderr, "si_code=%s ",
		       lookup_table_fallback(siginfo->si_code,
					     sigsegv_code_table));
       else if (signum == SIGBUS)
	       fprintf(stderr, "si_code=%s ",
		       lookup_table_fallback(siginfo->si_code,
					     sigbus_code_table));
       else if (signum == SIGCHLD)
	       fprintf(stderr, "si_code=%s ",
		       lookup_table_fallback(siginfo->si_code,
					     sigcld_code_table));
       else
	       fprintf(stderr, "si code=%d ", siginfo->si_code);
       if ((siginfo->si_code != SI_USER) &&
	   (signum == SIGILL || signum == SIGFPE ||
	    signum == SIGSEGV || signum == SIGBUS))
	       fprintf(stderr, "fault addr=%p", siginfo->si_addr);
       fprintf(stderr, "\n");

#if defined(HAVE_BACKTRACE) && !defined(DISABLE_BACKTRACE)
       frames = backtrace(stack_syms, 32);
       backtrace_symbols_fd(stack_syms, frames, 2);
#endif
       exit(FSCK_ERROR);
}

void sigcatcher_setup(void)
{
	struct sigaction	sa;
	
	memset(&sa, 0, sizeof(struct sigaction));
	sa.sa_sigaction = die_signal_handler;
	sa.sa_flags = SA_SIGINFO;

	sigaction(SIGFPE, &sa, 0);
	sigaction(SIGILL, &sa, 0);
	sigaction(SIGBUS, &sa, 0);
	sigaction(SIGSEGV, &sa, 0);
}	


#ifdef DEBUG
#include <getopt.h>

void usage(void)
{
	fprintf(stderr, "tst_sigcatcher: [-akfn]\n");
	exit(1);
}

int main(int argc, char** argv)
{
	struct sigaction	sa;
	char			*p = 0;
	int 			i, c;
	volatile		x=0;

	memset(&sa, 0, sizeof(struct sigaction));
	sa.sa_sigaction = die_signal_handler;
	sa.sa_flags = SA_SIGINFO;
	for (i=1; i < 31; i++)
		sigaction(i, &sa, 0);

	while ((c = getopt (argc, argv, "afkn")) != EOF)
		switch (c) {
		case 'a':
			abort();
			break;
		case 'f':
			printf("%d\n", 42/x);
		case 'k':
			kill(getpid(), SIGTERM);
			break;
		case 'n':
			*p = 42;
		default:
			usage ();
		}

	printf("Sleeping for 10 seconds, send kill signal to pid %u...\n",
	       getpid());
	fflush(stdout);
	sleep(10);
	exit(0);
}
#endif