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

#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 <QProcess>
#include <QStringList>
#include <qpushbutton.h>
#include <qfile.h>
#include <QTextEdit>
#include <qmessagebox.h>
#include <QFileDialog>

#include "dcconfig.h"
#include <dclib/dcos.h>
#include <dclib/dclib.h>
#include <dclib/chttp.h>
#include <dclib/core/cstring.h>

#include "dcdebug.h"

CString DCDebug::arg_0 = "";
CString DCDebug::startup_dir = "";

/** */
DCDebug::DCDebug( QWidget *parent, const char *name ) : QDialog(parent,name)
{
	setupUi(this);
	
	m_pHttp = 0;

	InitDocument();
}

/** */
DCDebug::~DCDebug()
{
	m_Timer.stop();

	if ( m_pHttp )
	{
		delete m_pHttp;
	}
}

/** */
void DCDebug::InitDocument()
{
	connect( PushButton_EXIT, SIGNAL(clicked()), this, SLOT(accept()) );
	connect( PushButton_SEND, SIGNAL(clicked()), this, SLOT(slotSendBacktrace()) );
	connect( PushButton_SAVE, SIGNAL(clicked()), this, SLOT(slotSaveBacktrace()) );

	// enable timer
	connect( &m_Timer, SIGNAL(timeout()), this, SLOT(timerDone()) );
}

/** */
bool DCDebug::Init( QString configpath, QString debugopt )
{
	bool res = FALSE;
#ifndef WIN32
	QString s = "bt full\nthread apply all bt\nq\n";
	QString filename = configpath+"/"+"gdbrc";
	QFile f(filename);
	QString s1,s2,s3;
	QProcess pr;
	QStringList args;

	QString buffer = "";

	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().data(),
			s1.toAscii().data(),
			s2.toAscii().data(),
			s3.toAscii().data() ); fflush(stdout);

	f.open( QIODevice::WriteOnly | QIODevice::Truncate );
	f.writeBlock(s,s.length());
	f.close();

	args << "--nw";
	args << "--nx";
	args << "--quiet";
	args << "--batch";
	args << "-x";
	args << filename;
	args << s3;
	args << s1;
	
	pr.start( "gdb", args );
	pr.waitForFinished();

	if ( pr.exitStatus() == QProcess::NormalExit )
	{
		buffer += pr.readAllStandardOutput() + "\n";
		buffer += pr.readAllStandardError() + "\n";

		QString s = "";

		// get version
		QFile f1("/proc/version");

		if ( f1.open( QIODevice::ReadOnly ) )
		{
			s += f1.readAll();

			f1.close();

			s += "\n";
		}

		// create output text
		s += "Valknut: ";
		s += VERSION;
		s += "\n";
		s += "DCLIB: ";
		s += dclibVersion();
		s += "\n";
		s += "QT compiled: ";
		s += QT_VERSION_STR;
		s += "\n";
		s += "QT used: ";
		s += qVersion();
		s += "\n";
		s += "LIBXML compiled: ";
		s += LIBXML_VERSION_STRING;
		s += "\n";
		s += "LIBXML used: ";
		s += xmlParserVersion;
		s += "\n\n";

		TextEdit_LOG->setText(s + buffer);

		res = TRUE;
	}
	else
	{
		// TODO: error message
		printf("gdb returned bad exit status\n"); fflush(stdout);
	}
#endif
	return res;
}

/** */
void DCDebug::timerDone()
{
	if ( m_bTransfer == FALSE )
	{
		if ( m_pHttp )
		{
			if ( m_pHttp->GetHttpError() != 200 )
			{
				QMessageBox::critical( this, tr("Send error"),
		                	tr("Could not send the data. Error '%1'.").arg(m_pHttp->GetHttpError()) );

				PushButton_SEND->setEnabled(TRUE);
			}
			else
			{
				QMessageBox::critical( this, tr("Send"),
		                	tr("Thanks for your help.") );
			}
		}

		PushButton_SAVE->setEnabled(TRUE);
		PushButton_EXIT->setEnabled(TRUE);
	}
	else
	{
		m_Timer.start( 500, TRUE );
	}
}

/** */
void DCDebug::slotSendBacktrace()
{
	QString text;

	if ( m_pHttp )
		delete m_pHttp;

	PushButton_SAVE->setEnabled(FALSE);
	PushButton_SEND->setEnabled(FALSE);
	PushButton_EXIT->setEnabled(FALSE);

	m_bTransfer = TRUE;
	m_Timer.start( 500, TRUE );

	m_pHttp = new CHttp();
	m_pHttp->SetCallBackFunction( new CCallback<DCDebug>( this, &DCDebug::HttpCallBack ) );

	text  = "--- Comment\n\n";
	text += TextEdit_COMMENT->text() + "\n";
	text += "--- Backtrace\n\n";
	text += TextEdit_LOG->text() + "\n";
	text += "--- End\n";

	m_pHttp->GetUrl("http://dcgui.berlios.de/crash.php",text.toAscii().data());
}

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

	if ( s.isNull() )
	{
		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
	{
		file->writeBlock( TextEdit_LOG->text().toAscii().data(), TextEdit_LOG->text().length() ); 
		file->close();
	}

	delete file;
}

/** http callback function */
int DCDebug::HttpCallBack( CObject *, CObject * object )
{
	switch(((CDCMessage*)object)->m_eType)
	{
		case DC_MESSAGE_CONNECTION_STATE:
		{
			CMessageConnectionState *msg = (CMessageConnectionState*)object;

			if ( msg->m_eState == estDISCONNECTED )
			{
				m_bTransfer = FALSE;
			}

			break;
		}

		case DC_MESSAGE_TRANSFER:
		{
			break;
		}

		default:
		{
			break;
		}
	}

	if ( object )
	{
		delete object;
	}

	return 0;
}

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

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

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

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

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

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

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

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

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

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

		default:
		{
			printf("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 )
	{
		printf("sigaction returned -1 ");
	}
	else if ( query_action.sa_handler == SIG_IGN )
	{
		printf("signal is being ignored ");
	}
	else if ( query_action.sa_handler == SIG_DFL )
	{
		// print_signal_name(signum);
		// printf(" query_action.sa_handler=%d SIG_DFL=%d\n", query_action.sa_handler, SIG_DFL);
		ok = TRUE;
	}
	else
	{
		printf("signal already handled ");
	}
	
	if ( ok == FALSE )
	{
		printf("not handling signal ");
		print_signal_name(signum);
		printf("\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(0);
	}
	/*
	 * besides guarding entrancy it's probably also better 
	 * to mask off signals
	 */
	if (crashed_) _exit(253);

	crashed_++;

	printf("CRASH CRASH CRASH \n");fflush(stdout);

	CString cp = g_pConfig->GetConfigPath().Data();

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

		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(253);
	}

	_exit(253);
}

/*!
 *\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 == TRUE )
	{
		sigaddset( &new_action.sa_mask, SIGSEGV );
	}
#endif
	
#ifdef SIGFPE
	fpe = can_we_handle_signal( SIGFPE );
	if ( fpe == TRUE )
	{
		sigaddset( &new_action.sa_mask, SIGFPE );
	}
#endif

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

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

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

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

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

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

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

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

