/**
 *
 * Beryl crash handler plugin
 *
 * crashhandler.c
 *
 * Copyright : (C) 2006 by Dennis Kasprzyk
 * E-mail    : onestone@beryl-project.org
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 **/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#include <beryl.h>

#define GET_CRASHHANDLER_DISPLAY(d)                                  \
    ((CrashhandlerDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define CRASHHANDLER_DISPLAY(d)                      \
    CrashhandlerDisplay *cd = GET_CRASHHANDLER_DISPLAY (d)

#define CRASHHANDLER_DISPLAY_OPTION_ENABLED                 0
#define CRASHHANDLER_DISPLAY_OPTION_START_WM                1
#define CRASHHANDLER_DISPLAY_OPTION_WM                      2
#define CRASHHANDLER_DISPLAY_OPTION_NUM                     3

#define CRASHHANDLER_DISPLAY_OPTION_ENABLED_DEFAULT         TRUE
#define CRASHHANDLER_DISPLAY_OPTION_START_WM_DEFAULT        FALSE
#define CRASHHANDLER_DISPLAY_OPTION_WM_DEFAULT              ""

#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))

static int displayPrivateIndex = 0;
static CompDisplay *cDisplay;

typedef struct _CrashhandlerDisplay
{
	int screenPrivateIndex;
	CompOption opt[CRASHHANDLER_DISPLAY_OPTION_NUM];
} CrashhandlerDisplay;

static void crash_handler(int sig)
{
	if (sig == SIGSEGV || sig == SIGFPE || sig == SIGILL || sig == SIGABRT)
	{
		CRASHHANDLER_DISPLAY(cDisplay);
		static int count = 0;

		if (++count > 1)
			exit(1);
		// backtrace
		char cmd[1024];

		sprintf(cmd,
				"echo -e \"set prompt\nthread apply all bt full\necho \\\\\\n\necho \\\\\\n\nbt\nquit\" > /tmp/gdb.tmp; gdb -q %s %i < /tmp/gdb.tmp | grep -v \"No symbol table\" | tee /tmp/beryl_crash-%i.out; rm -f /tmp/gdb.tmp; echo \"\n[CRASH_HANDLER]: \\\"/tmp/beryl_crash-%i.out\\\" created!\n\"",
				getProgramName(), getpid(), getpid(), getpid());
		system(cmd);

		if (cd->opt[CRASHHANDLER_DISPLAY_OPTION_START_WM].value.b)
		{
			if (fork() == 0)
			{
				setsid();
				putenv(cDisplay->displayString);
				execl("/bin/sh", "/bin/sh", "-c",
					  cd->opt[CRASHHANDLER_DISPLAY_OPTION_WM].value.s, NULL);
				exit(0);
			}
		}

		exit(1);
	}
}

static Bool
crashhandlerSetDisplayOption(CompDisplay * display, char *name,
							 CompOptionValue * value)
{
	CompOption *o;
	int index;

	CRASHHANDLER_DISPLAY(display);

	o = compFindOption(cd->opt, NUM_OPTIONS(cd), name, &index);
	if (!o)
		return FALSE;

	switch (index)
	{
	case CRASHHANDLER_DISPLAY_OPTION_ENABLED:
		if (compSetBoolOption(o, value))
		{
			if (value->b)
			{
				// enable crash handler
				signal(SIGSEGV, crash_handler);
				signal(SIGFPE, crash_handler);
				signal(SIGILL, crash_handler);
				signal(SIGABRT, crash_handler);
			}
			else
			{
				// disable crash handler
				signal(SIGSEGV, SIG_DFL);
				signal(SIGFPE, SIG_DFL);
				signal(SIGILL, SIG_DFL);
				signal(SIGABRT, SIG_DFL);
			}
			return TRUE;
		}
		break;
	case CRASHHANDLER_DISPLAY_OPTION_START_WM:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case CRASHHANDLER_DISPLAY_OPTION_WM:
		if (compSetStringOption(o, value))
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static void crashhandlerDisplayInitOptions(CrashhandlerDisplay * cd)
{
	CompOption *o;

	o = &cd->opt[CRASHHANDLER_DISPLAY_OPTION_ENABLED];
	o->advanced = False;
	o->name = "enabled";
	o->group = N_("Misc. settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Enable Crash Handler");
	o->longDesc = N_("Activate Crash Handler.");
	o->type = CompOptionTypeBool;
	o->value.b = CRASHHANDLER_DISPLAY_OPTION_ENABLED_DEFAULT;

	o = &cd->opt[CRASHHANDLER_DISPLAY_OPTION_START_WM];
	o->advanced = False;
	o->name = "start_window_manager";
	o->group = N_("Misc. settings");
	o->subGroup = N_("");
	o->displayHints = "";
	o->shortDesc = N_("Start Other Window Manager");
	o->longDesc = N_("Start other window manager on Crash.");
	o->type = CompOptionTypeBool;
	o->value.b = CRASHHANDLER_DISPLAY_OPTION_START_WM_DEFAULT;

	o = &cd->opt[CRASHHANDLER_DISPLAY_OPTION_WM];
	o->advanced = False;
	o->name = "window_manager_command_line";
	o->group = N_("Misc. settings");
	o->subGroup = N_("");
	o->displayHints = "command;";
	o->shortDesc = N_("Window Manager Command Line");
	o->longDesc =
			N_("Window manager command line. DO NOT ENTER BERYL HERE!!!");
	o->type = CompOptionTypeString;
	o->value.s = strdup(CRASHHANDLER_DISPLAY_OPTION_WM_DEFAULT);
	o->rest.s.string = 0;
	o->rest.s.nString = 0;

}


static CompOption *crashhandlerGetDisplayOptions(CompDisplay * display,
												 int *count)
{
	if (display)
	{
		CRASHHANDLER_DISPLAY(display);

		*count = NUM_OPTIONS(cd);
		return cd->opt;
	}
	else
	{
		CrashhandlerDisplay *cd = malloc(sizeof(CrashhandlerDisplay));

		crashhandlerDisplayInitOptions(cd);
		*count = NUM_OPTIONS(cd);
		return cd->opt;
	}
}


static Bool crashhandlerInitDisplay(CompPlugin * p, CompDisplay * d)
{
	//Generate a bench display
	CrashhandlerDisplay *cd =
			(CrashhandlerDisplay *) malloc(sizeof(CrashhandlerDisplay));
	//Allocate a private index
	cd->screenPrivateIndex = allocateScreenPrivateIndex(d);
	//Check if its valid
	if (cd->screenPrivateIndex < 0)
	{
		//Its invalid so free memory and return
		free(cd);
		return FALSE;
	}
	crashhandlerDisplayInitOptions(cd);
	cDisplay = d;

	if (cd->opt[CRASHHANDLER_DISPLAY_OPTION_ENABLED].value.b)
	{
		// segmentation fault
		signal(SIGSEGV, crash_handler);
		// floating point exception
		signal(SIGFPE, crash_handler);
		// illegal instruction
		signal(SIGILL, crash_handler);
		// abort
		signal(SIGABRT, crash_handler);
	}


	//Record the display
	d->privates[displayPrivateIndex].ptr = cd;
	return TRUE;
}

static void crashhandlerFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	signal(SIGSEGV, SIG_DFL);
	CRASHHANDLER_DISPLAY(d);
	//Free the private index
	freeScreenPrivateIndex(d, cd->screenPrivateIndex);
	//Free the pointer
	free(cd);
}



static Bool crashhandlerInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();

	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

static void crashhandlerFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}


CompPluginVTable crashhandlerVTable = {
	"crashhandler",
	N_("Crash handler"),
	N_("Beryl crash handler plugin"),
	crashhandlerInit,
	crashhandlerFini,
	crashhandlerInitDisplay,
	crashhandlerFiniDisplay,
	0,
	0,
	0,
	0,
	crashhandlerGetDisplayOptions,
	crashhandlerSetDisplayOption,
	0,
	0,
	0,
	0,
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"devel",
	0,
	0,
	False,
};

CompPluginVTable *getCompPluginInfo(void)
{
	return &crashhandlerVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1