#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <cstring>

#ifdef __FreeBSD__
#include <sys/param.h>
#endif

#if !defined(__FreeBSD__) || __FreeBSD_version > 700024
#include <libelf.h>
#include <gelf.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>

#include <glib.h>
#include <glib/gstdio.h>

#include <gdk/gdk.h>
#include <gdk/gdkx.h>

#include <config.h>

#ifdef ENABLE_GOOGLE_BREAKPAD
#include "client/linux/handler/exception_handler.h"

using namespace google_breakpad;
#else
#include <string.h>
#endif

extern "C" int gtk_module_init (int *argc, char** argv[]);
static bool    run_bug_buddy   (const gchar *appname, pid_t pid, const gchar *minidump_path);
static bool    check_if_gdb    (void *callback_context);


static gchar *bugbuddy;

#ifdef ENABLE_GOOGLE_BREAKPAD
// Callback when minidump written.
static bool MinidumpCallback(const char *dump_path,
                             const char *minidump_id,
                             void *context,
                             bool succeeded) {
  gchar *appname;
  gchar *minidump_file;

	
  printf("%s is dumped\n", minidump_id);
  appname = g_get_prgname ();
  minidump_file = g_strdup_printf ("/tmp/%s.dmp", minidump_id);
  run_bug_buddy (appname, 0, minidump_file);

  g_unlink (minidump_file);
  g_free (minidump_file);
  _exit(0);
}
#else
static void
bugbuddy_segv_handle(int signum)
{
	static int in_segv = 0;
        struct sigaction sa;
	pid_t pid;
	
	sa.sa_handler = NULL;
	in_segv++;

        if (in_segv > 2) {
                /* The fprintf() was segfaulting, we are just totally hosed */
                _exit(1);
        } else if (in_segv > 1) {
                /* dialog display isn't working out */
                fprintf(stderr, "Multiple segmentation faults occurred; can't display error dialog\n");
                _exit(1);
        }

        /* Make sure we release grabs */
        gdk_pointer_ungrab(GDK_CURRENT_TIME);
        gdk_keyboard_ungrab(GDK_CURRENT_TIME);
        XUngrabServer (GDK_DISPLAY ());

        gdk_flush();

	check_if_gdb (NULL);

	/* If we are here is because gdb couldn't be run.
	 * FIXME: Show some error? */
        
        _exit(1);
}
#endif


#define N_TRIES 3

static gboolean 
find_in_debug_path (const char *filename, const char *debug_filename)
{
	char *dir;
	char *tries[N_TRIES];
	int i;

	dir = g_path_get_dirname (filename);
	tries[0] = g_build_filename (dir, debug_filename, NULL);
	tries[1] = g_build_filename (dir, ".debug", debug_filename, NULL);
	tries[2] = g_build_filename ("/usr", "lib", "debug", dir, debug_filename, NULL);

	g_free (dir);

	for (i = 0; i < N_TRIES; ++i) {
		if (g_file_test (tries[i], G_FILE_TEST_EXISTS))
			return true;
	}

	return false;
}
	

#if !defined(__FreeBSD__) || __FreeBSD_version > 700024
static gboolean
elf_has_debug_symbols (const char *filename)
{
       int fd;
       Elf *elf;
       GElf_Ehdr elf_header;
       Elf_Scn *section = 0;
       int number = 0;

       if (elf_version(EV_CURRENT) == EV_NONE ) {
               fprintf(stderr, "Elf library out of date!n");
                return false;
       }

       fd = open(filename, O_RDONLY);

       if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL){
               close (fd);
               return false;
       }
       gelf_getehdr (elf, &elf_header);
       while ((section = elf_nextscn(elf, section)) != 0) {
               char *name = 0;
               GElf_Shdr shdr;

		/* Check for stabs debug information */
               if (gelf_getshdr (section, &shdr) != 0) {
                       if (shdr.sh_type == SHT_SYMTAB) {
                               elf_end (elf);
                               return true;
                       }
               }

		/* Check for .gnu_debuglink separete debug file */
               if (shdr.sh_type == SHT_PROGBITS) {
                 char *name = elf_strptr(elf, elf_header.e_shstrndx, shdr.sh_name);
		 if (strcmp (name, ".gnu_debuglink") == 0) {
			Elf_Data *edata;

			edata = elf_getdata(section, NULL);
			if (edata != NULL && find_in_debug_path (filename, (const char*) edata->d_buf)) {
				elf_end (elf);
				return true;
			}
		}
              }
       }

       /* no symtab neither debug file present */
       elf_end (elf);
       return false;
}
#endif



static bool
release_grabs (void)
{
        /* Make sure we release grabs */
        gdk_pointer_ungrab(GDK_CURRENT_TIME);
        gdk_keyboard_ungrab(GDK_CURRENT_TIME);
        XUngrabServer (GDK_DISPLAY ());

        gdk_flush();

	return true;
}

static bool
run_bug_buddy (const gchar *appname, pid_t pid, const gchar *minidump_path)
{
	gchar *exec_str;
	gboolean res;
	GError *error = NULL;

	if (pid != 0) 
		exec_str = g_strdup_printf("bug-buddy "
				   	"--appname=\"%s\" "
				  	 "--pid=%d",
				   	appname,(int) pid);
	else if (minidump_path != NULL)
		exec_str = g_strdup_printf("bug-buddy "
				   	"--appname=\"%s\" "
				  	 "--minidump=%s",
				   	appname, minidump_path);
	else
		return false;


	res = g_spawn_command_line_sync (exec_str, NULL, NULL,
					 NULL, &error);
	g_free(exec_str);
	if (!res) {
		g_warning("Couldn't run bug-buddy\n");
		return false;
	}

	return true;
}

static bool
run_gdb (const gchar *appname, pid_t pid)
{
	gchar *exec_str;
	gchar *title;
	gboolean res;
	GError *error = NULL;

	title = g_strdup_printf ("Debugging %s", appname);

	exec_str = g_strdup_printf("gnome-terminal "
				 "--title=\"%s\" "
				 "--disable-factory "
				 "--command=\"gdb %s %d\"",
				title, appname, (int)pid);
				g_free (title);
	res = g_spawn_command_line_sync (exec_str, NULL, NULL,
					 NULL, &error);
	g_free(exec_str);
	if (!res) {
		g_warning("Couldn't run debugger\n");
		return false;
	}

	return true;
}

static bool
check_if_gdb (void *callback_context)
{
	char mypath[255];
	gchar *gdb;
	bool has_debug_symbols;
	char *filename;
	gchar *appname;
	pid_t pid;
	gboolean res;

	release_grabs ();

	if (g_getenv ("GNOME_DISABLE_CRASH_DIALOG"))
		_exit(0);

	appname = g_get_prgname ();
	pid = getpid ();
	gdb = g_find_program_in_path ("gdb");

	if (gdb && g_getenv("GNOME_HACKER")) {
		res = run_gdb (appname, pid);
		if (!res)
			_exit (1);
		_exit(0);
	}
	
#if !defined(__FreeBSD__) || __FreeBSD_version > 700024
	memset(mypath, 0, sizeof(mypath));
#ifndef __FreeBSD__
	readlink ("/proc/self/exe", mypath, sizeof(mypath));
#else
	readlink ("/proc/curproc/file", mypath, sizeof(mypath));
#endif
	has_debug_symbols = elf_has_debug_symbols (mypath);
#else
	has_debug_symbols = TRUE;
#endif


	if (bugbuddy && gdb && has_debug_symbols) {
		res = run_bug_buddy (appname, pid, NULL);
		if (!res)
			_exit (1);
		_exit(0);
	}

	return true;
}
		
int
gtk_module_init (int *argc, char** argv[])
{
	bugbuddy = g_find_program_in_path ("bug-buddy");
	
	if (bugbuddy && !g_getenv ("GNOME_DISABLE_CRASH_DIALOG")) {
#ifdef ENABLE_GOOGLE_BREAKPAD
        	static struct sigaction old_action;

		sigaction(SIGSEGV, NULL, &old_action);
		if (old_action.sa_handler != SIG_DFL)
			return 0;

                sigaction(SIGABRT, NULL, &old_action);
		if (old_action.sa_handler != SIG_DFL)
			return 0;

                sigaction(SIGTRAP, NULL, &old_action);
		if (old_action.sa_handler != SIG_DFL)
			return 0;

                sigaction(SIGFPE, NULL, &old_action);
		if (old_action.sa_handler != SIG_DFL)
			return 0;

                sigaction(SIGBUS, NULL, &old_action);
		if (old_action.sa_handler != SIG_DFL)
			return 0;

  		static ExceptionHandler handler("/tmp", check_if_gdb,
						MinidumpCallback, NULL, true);
#else
        	static struct sigaction *setptr;
        	static struct sigaction old_action;
        	struct sigaction sa;
                memset(&sa, 0, sizeof(sa));
                setptr = &sa;

                sa.sa_handler = bugbuddy_segv_handle;

                sigaction(SIGSEGV, NULL, &old_action);
		if (old_action.sa_handler == SIG_DFL)
                	sigaction(SIGSEGV, setptr, NULL);

                sigaction(SIGABRT, NULL, &old_action);
		if (old_action.sa_handler == SIG_DFL)
                	sigaction(SIGABRT, setptr, NULL);

                sigaction(SIGTRAP, NULL, &old_action);
		if (old_action.sa_handler == SIG_DFL)
                	sigaction(SIGTRAP, setptr, NULL);

                sigaction(SIGFPE, NULL, &old_action);
		if (old_action.sa_handler == SIG_DFL)
                	sigaction(SIGFPE, setptr, NULL);

                sigaction(SIGBUS, NULL, &old_action);
		if (old_action.sa_handler == SIG_DFL)
                	sigaction(SIGBUS, setptr, NULL);
#endif
	}
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1