/***************************************************************************
                          dcdebug.cpp  -  description
                             -------------------
    begin                : Don Sep 25 2003
    copyright            : (C) 2003 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "dcdebug.h"

#ifndef WIN32
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#endif

#include <QStringList>
#include <QPushButton>
#include <QFile>
#include <QTextEdit>
#include <QMessageBox>
#include <QFileDialog>
#include <QByteArray>

#include "dcconfig.h"
#include <dclib/dclib.h>
#include <dclib/core/cxml.h>

#if defined(HAVE_CONFIG_H)
#include <config.h>
#endif

CString DCDebug::arg_0 = CString();
CString DCDebug::startup_dir = CString();

/** */
DCDebug::DCDebug( QWidget * parent ) : QDialog(parent)
{
	setupUi(this);
	
	m_pDebug = 0;

	connect( PushButton_EXIT, SIGNAL(clicked()), this, SLOT(accept()) );
	connect( PushButton_SAVE, SIGNAL(clicked()), this, SLOT(slotSaveBacktrace()) );
}

/** */
DCDebug::~DCDebug()
{
	if ( m_pDebug )
	{
		disconnect( m_pDebug, SIGNAL( finished(int,QProcess::ExitStatus) ), this, SLOT( slotDebuggerFinished(int,QProcess::ExitStatus) ) );
		m_pDebug->close();
		m_pDebug->kill();
		
		delete m_pDebug;
		m_pDebug = 0;
	}
}

/** */
bool DCDebug::Init( QString configpath, QString debugopt )
{
#ifndef WIN32
	TextEdit_LOG->setText(tr("Getting information about the crash..."));
	
	QString s = "thread apply all bt full\nq\n";
	QString filename = configpath+"/"+"gdbrc";
	QFile f(filename);
	QString s1,s2,s3;
	QStringList args;

	s1 = debugopt.section(",",0,0);
	s2 = debugopt.section(",",1,1);
	s3 = debugopt.section(",",2,2);

	printf("Init debug:\nConfigpath: '%s'\n%s %s %s\n",
			configpath.toAscii().constData(),
			s1.toAscii().constData(),
			s2.toAscii().constData(),
			s3.toAscii().constData() );

	f.open( QIODevice::WriteOnly | QIODevice::Truncate );
	f.write(s.toAscii());
	f.close();

	args << "--nw";
	args << "--nx";
	args << "--quiet";
	args << "--batch";
	args << "-x";
	args << filename;
	args << s3;
	args << s1;
	
	m_pDebug = new QProcess(this);
	connect( m_pDebug, SIGNAL( finished(int,QProcess::ExitStatus) ), this, SLOT( slotDebuggerFinished(int,QProcess::ExitStatus) ) );
	
	m_pDebug->start( "gdb", args, QIODevice::ReadOnly );
	
	/* We do not get either finished() or started() if the process fails to start */
	if ( m_pDebug->waitForStarted() == false )
	{
		disconnect( m_pDebug, SIGNAL( finished(int,QProcess::ExitStatus) ), this, SLOT( slotDebuggerFinished(int,QProcess::ExitStatus) ) );
		slotDebuggerFinished( -1, QProcess::CrashExit );
	}
	
	return true;
#else
	return false;
#endif
}

/** */
void DCDebug::slotDebuggerFinished( int exitCode, QProcess::ExitStatus exitStatus )
{
	QString s;
	
	// get version
	QFile f1("/proc/version");
	
	if ( f1.open( QIODevice::ReadOnly ) )
	{
		s += f1.readAll();
		f1.close();
		s += "\n";
	}
	
	// create output text
	s += "EiskaltDC: ";
	s += PACKAGE_VERSION;
	s += " (";
	s += EISKALTDC_BUILD_INFO;
	s += ")\nDCLIB: ";
	s += dclibVersion();
	s += " (";
	s += dclibBuildInfo();
	s += ")\nQt® compiled: ";
	s += QT_VERSION_STR;
	s += "\nQt® used: ";
	s += qVersion();
	s += "\nLIBXML compiled: ";
	s += CXml::Libxml2CompiledVersion();
	s += "\nLIBXML used: ";
	s += CXml::Libxml2RunningVersion();
	s += "\n\n";
	
	if ( exitStatus == QProcess::NormalExit )
	{
		QString buffer = m_pDebug->readAllStandardOutput();
		buffer += "\n";
		buffer += m_pDebug->readAllStandardError();
		buffer += "\n";

		TextEdit_LOG->setText( s + buffer );
	}
	else
	{
		QString failed = tr("Failed to get more information about the crash.");
		failed += "\n";
		failed += tr("The %1 program is required to get the information.").arg("gdb");
		failed += "\n";
		failed += tr("Exit code: %1").arg(exitCode);
		failed += "\n";
		failed += tr("Error messages:");
		failed += "\n";
		failed += m_pDebug->readAllStandardError();
		TextEdit_LOG->setText( s + failed );
	}
	
	m_pDebug->close();
	delete m_pDebug;
	m_pDebug = 0;
	
	PushButton_SAVE->setEnabled(true);
}

/** */
void DCDebug::slotSaveBacktrace()
{
	QString s = QFileDialog::getSaveFileName(
	            this,
		    tr("Choose a filename to save under"),
                    QString(),
                    tr("Text (*.txt)")
        );

	if ( s.isEmpty() )
	{
		return;
	}

	QFile *file = new QFile(s);

	if ( !file->open( QIODevice::WriteOnly ) )
	{
        	QMessageBox::critical( this, tr("Save error"),
                	tr("Can't open file '%1' for writing.").arg(s) );
	}
	else
	{
		QString comment = TextEdit_COMMENT->toPlainText();
		
		if ( !comment.isEmpty() )
		{
			file->write( comment.toAscii() );
			
			QByteArray separator;
			separator.resize(80);
			separator.fill('-');
			separator.prepend("\n\n");
			separator.append("\n\n");
			
			file->write( separator );
		}
		
		file->write( TextEdit_LOG->toPlainText().toAscii() );
		file->close();
	}

	delete file;
}

#ifndef WIN32
/** */
void print_signal_name( const int signum )
{
	switch ( signum )
	{
#ifdef SIGPIPE
		case SIGPIPE:
		{
			DPRINTF("SIGPIPE");
			break;
		}
#endif

#ifdef SIGTERM
		case SIGTERM:
		{
			DPRINTF("SIGTERM");
			break;
		}
#endif

#ifdef SIGHUP
		case SIGHUP:
		{
			DPRINTF("SIGHUP");
			break;
		}
#endif

#ifdef SIGINT
		case SIGINT:
		{
			DPRINTF("SIGINT");
			break;
		}
#endif

#ifdef SIGQUIT
		case SIGQUIT:
		{
			DPRINTF("SIGQUIT");
			break;
		}
#endif

#ifdef SIGSEGV
		case SIGSEGV:
		{
			DPRINTF("SIGSEGV");
			break;
		}
#endif

#ifdef SIGFPE
		case SIGFPE:
		{
			DPRINTF("SIGFPE");
			break;
		}
#endif

#ifdef SIGILL
		case SIGILL:
		{
			DPRINTF("SIGILL");
			break;
		}
#endif

#ifdef SIGABRT
		case SIGABRT:
		{
			DPRINTF("SIGABRT");
			break;
		}
#endif

#ifdef SIGTRAP
		case SIGTRAP:
		{
			DPRINTF("SIGTRAP");
			break;
		}
#endif

		default:
		{
			DPRINTF("signal %d", signum);
			break;
		}
	}
}

/** */
bool can_we_handle_signal( const int signum )
{
	bool ok = false;
	
	struct sigaction query_action;
	
	if ( sigaction(signum,NULL,&query_action) == -1 )
	{
		DPRINTF("sigaction returned -1 ");
	}
	else if ( query_action.sa_handler == SIG_IGN )
	{
		DPRINTF("signal is being ignored ");
	}
	else if ( query_action.sa_handler == SIG_DFL )
	{
		// print_signal_name(signum);
		// DPRINTF(" query_action.sa_handler=%d SIG_DFL=%d\n", query_action.sa_handler, SIG_DFL);
		ok = true;
	}
	else
	{
		DPRINTF("signal already handled ");
	}
	
	if ( ok == false )
	{
		DPRINTF("not handling signal ");
		print_signal_name(signum);
		DPRINTF("\n");
	}
	
	return ok;
}

/*!
 *\brief	this handler will probably evolve into 
 *		something better.
 */
static void crash_handler(int sig)
{
	pid_t pid;
	static volatile unsigned long crashed_ = 0;
                             
	/*
	 * let's hope startup_dir and argv0 aren't trashed.
	 * both are defined in main.c.
	 */
	if (sig < 0 || sig > 32) { /* what's that ? */
//		char *buf = g_strdup_printf(
//				_("Caught strange signal (%d). This is probably\n"
//				  "due to a broken compilation; Exiting."), sig);
//		ay_do_error( _("Error"), buf );
//		g_free(buf);
//		printf("SIGNAL_HANDLER: unknown signal %d\n",sig);
		_exit(EXIT_FAILURE);
	}
	/*
	 * besides guarding entrancy it's probably also better 
	 * to mask off signals
	 */
	if (crashed_) _exit(EXIT_FAILURE);

	crashed_++;

//	printf("CRASH CRASH CRASH \n");

	if (0 == (pid = fork()))
	{
		char buf[256];
		char * args[6];
		CString cp = g_pConfig->GetConfigPath();

		args[0] = DCDebug::arg_0.Data(); 
		args[1] = "-c";
		args[2] = cp.Data();
		args[3] = "-C";
		snprintf(buf, sizeof(buf), "%d,%d,%s", getppid(), sig, DCDebug::arg_0.Data());
		args[4] = buf;
		args[5] = NULL;

		chdir(DCDebug::startup_dir.Data());
		setgid(getgid());
		setuid(getuid());
		execvp(DCDebug::arg_0.Data(), args);
	} else {
		waitpid(pid, NULL, 0);
		_exit(EXIT_FAILURE);
	}

	_exit(EXIT_FAILURE);
}

/*!
 *\brief	install crash handlers
 */
void crash_install_handlers(void)
{
	bool segv = false, fpe = false, ill = false, abrt = false, trap = false;
	
	struct sigaction new_action;
	new_action.sa_handler = crash_handler;
	sigemptyset( &new_action.sa_mask );
	new_action.sa_flags = 0;
	
#ifdef SIGSEGV
	segv = can_we_handle_signal( SIGSEGV );
	if ( segv )
	{
		sigaddset( &new_action.sa_mask, SIGSEGV );
	}
#endif
	
#ifdef SIGFPE
	fpe = can_we_handle_signal( SIGFPE );
	if ( fpe )
	{
		sigaddset( &new_action.sa_mask, SIGFPE );
	}
#endif

#ifdef SIGILL
	ill = can_we_handle_signal( SIGILL );
	if ( ill )
	{
		sigaddset( &new_action.sa_mask, SIGILL );
	}
#endif

#ifdef SIGABRT
	abrt = can_we_handle_signal( SIGABRT );
	if ( abrt )
	{
		sigaddset( &new_action.sa_mask, SIGABRT );
	}
#endif

#ifdef SIGTRAP
	trap = can_we_handle_signal( SIGTRAP );
	if ( trap )
	{
		sigaddset( &new_action.sa_mask, SIGTRAP );
	}
#endif

#ifdef SIGSEGV
	if ( segv )
	{
		if ( sigaction(SIGSEGV,&new_action,NULL) == -1 )
		{
			printf("Error installing SIGSEGV handler\n");
		}
	}
#endif

#ifdef SIGFPE
	if ( fpe )
	{
		if ( sigaction(SIGFPE,&new_action,NULL) == -1 )
		{
			printf("Error installing SIGFPE handler\n");
		}
	}
#endif

#ifdef SIGILL
	if ( ill )
	{
		if ( sigaction(SIGILL,&new_action,NULL) == -1 )
		{
			printf("Error installing SIGILL handler\n");
		}
	}
#endif

#ifdef SIGABRT
	if ( abrt )
	{
		if ( sigaction(SIGABRT,&new_action,NULL) == -1 )
		{
			printf("Error installing SIGABRT handler\n");
		}
	}
#endif

#ifdef SIGTRAP
	if ( trap )
	{
		if ( sigaction(SIGTRAP,&new_action,NULL) == -1 )
		{
			printf("Error installing SIGTRAP handler\n");
		}
	}
#endif
	//sigprocmask(SIG_UNBLOCK, &mask, 0);
}
#endif

