/***************************************************************************
                          (filename).cpp -  description
                             -------------------
    begin                : (Weekday) (Month) (day) 2008
    copyright            : (C) 2008 by (yourname)
    email                : (email address)
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "crashhandler.h"
#include "../kmessdebug.h"
#include "../config-kmess.h"

#include <sys/types.h>  // pid_t
#include <sys/wait.h>   // waitpid()
#include <unistd.h>     // getpid(), alarm()
#include <stdlib.h>     // abort()

#include <KCrash>
#include <KProcess>
#include <kdeversion.h>



// Static vars need to be declared too
// appName_ contains the exact name of the binary.
// is overwritten by setAppName() with the full path or argv[0].
QLatin1String CrashHandler::appName_("kmess");



/**
 * @brief Activate the handler.
 *
 * If the platform supports it (currently only X11), the custom crash handler will be activated.
 * This makes sure kmessCrashed() will be called when a crash (SIGSEGV, ABRT, etc..) occurs.
 */
void CrashHandler::activate()
{
#ifdef Q_WS_X11
  KCrash::setCrashHandler( CrashHandler::kmessCrashed );
#endif

#ifdef KMESSDEBUG_CRASHHANDLER
#ifdef Q_WS_X11
  kDebug() << "Enabled custom crash handler";
#else
  kDebug() << "NOTICE: Crash handler is not enabled for this platform!";
#endif
#endif
}



/**
 * @brief The crash handler C function.
 *
 * This function collects all relevant application data in a report to display.
 * It also tries to run <code>gdb</code> to get a useful backtrace.
 * Currently the backtrace is dumped at the console only.
 *
 * @param signal  The received signal.
 */
void CrashHandler::kmessCrashed( int signal )
{
  // We cannot use kDebug() here, it requires a KApplication!
  // Crashes when closing go even more wrong with it.

  Q_UNUSED( signal );

  // Console message first.
  qDebug() << "KMess crashed! -- This should not happen.\n"
              "Please submit a report at http://www.kmess.org/board/ or bugs@kmess.org .\n"
              "\n"
              "Application version: " KMESS_VERSION "\n"
              "Compiled at: KDE " KDE_VERSION_STRING ", Qt " QT_VERSION_STR "\n"
              "Running at:  KDE " + QLatin1String( KDE::versionString() ) + ", Qt " + QLatin1String( qVersion() ) + "\n";

#ifdef Q_OS_UNIX
  /*
   * The main guts of this function are based
   * on the Amarok crash handler by Max Howell.
   */

  // Avoid loops
  KCrash::setCrashHandler();

  // We need to fork to be able to get a decent backtrace.
  // No idea why, perhaps some kdeinit magic causing trouble?
  const pid_t pid = ::fork();
  if( pid < 0 )
  {
    // fork failed
  }
  else if( pid > 0 )
  {
    // the parent, waits for the child.
    ::alarm( 0 );
    ::waitpid( pid, NULL, 0 );
    ::abort();
  }

  // The child process
#ifdef KMESSDEBUG_CRASHHANDLER
  qDebug() << "Running gdb for binary " << appName_ << " (pid " << getppid() << ")";
#endif

  KProcess gdb;
  gdb.setOutputChannelMode( KProcess::SeparateChannels );

  gdb << "gdb" << "--quiet"                 // avoid banners at startup
               << "--batch"                 // exit after processing
               << "--nw"                    // no window interface
               << "--nx"                    // don't read the .gdbinit file
               << "--ex" << "set width 0"   // no terminal width
               << "--ex" << "set height 0"  // no terminal height
               << "--ex" << "echo \\n==== (gdb) bt ====\\n"
               << "--ex" << "bt"
               //<< "--ex" << "echo ==== (gdb) thread apply all bt ====\\n"
               //<< "--ex" << "thread apply all bt"
               << appName_ << QByteArray::number( ::getppid() );    // attach to the process

#ifdef KMESSDEBUG_CRASHHANDLER
  QString commandLine;
  QStringList fullCommand = gdb.program();
  foreach( const QString &arg, fullCommand )
  {
    if( ! commandLine.isEmpty() )
    {
      commandLine += " ";
    }

    commandLine += ( arg.contains(" ") ? "'" + arg + "'" : arg );
  }
  qDebug() << "GDB command:" << commandLine;
#endif

  // Start gdb.
  gdb.start();

  bool showKdeBacktrace = false;

  // Wait maximum 30 seconds for gdb to finish.
  if( ! gdb.waitForFinished( 30000 ) )
  {
    // Something went wrong
    int returnCode = gdb.exitCode();

    if( returnCode == -2 )
    {
      qDebug() << "No backtrace could be generated, gdb not found. Printing KDE backtrace.";
    }
    else if( returnCode == -1 )
    {
      qDebug() << "No backtrace could be generated, gdb crashed.";
    }
    else if( returnCode != 0 )
    {
      qDebug() << "No backtrace could be generated, gdb returned: " << returnCode;
    }
    else
    {
      qDebug() << "No backtrace could be generated, gdb timed out.";
    }

    showKdeBacktrace = true;
  }
  else
  {
    // gdb finished, read and check its output.
    QString backtrace( gdb.readAll() );

    if ( backtrace.contains( "No stack." )
      || backtrace.contains( "Backtrace stopped" ) )
    {
      showKdeBacktrace = true;
    }

    qDebug() << "KMess backtrace:" << backtrace;
  }

  if( showKdeBacktrace )
  {
    qDebug() << "KDE backtrace:" << kBacktrace();
  }
#else
  #warning Crash handler code is not implemented for this platform.
#endif

  //_exit() exits immediately, otherwise this
  //function is called repeatedly ad infinitum
  ::_exit( 255 );
}



/**
 * @brief Assign the app binary name.
 *
 * This function is called from the main() method to make sure the exact binary
 * (as passed to <code>argv[0]</code>) is known by the crash handler. This makes it possible
 * to run <code>gdb</code> properly. By default the appname is set to "kmess".
 *
 * @param  appName  Filename of the application binary.
 */
void CrashHandler::setAppName( const QLatin1String &appName )
{
  appName_ = appName;
}


