/*
 * Copyright (C) 2003 Stefan Westerfeld
 * 
 * This code is based on code from the KDE Libraries
 * Copyright (C) 2000 Timo Hummel <timo.hummel@sap.com>
 *                    Tom Braun <braunt@fh-konstanz.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/*
 * This file is used to catch signals which would normally
 * crash the application (like segmentation fault, floating
 * point exception and such).
 */

#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include "crashhandler.h"
#include "audiosubsys.h"
#include "debug.h"
#include "config.h"

#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

#ifdef HAVE_REALTIME_SCHED
#include <sched.h>
#endif

using namespace Arts;

CrashHandler::HandlerType CrashHandler::_crashHandler = 0;
const char *CrashHandler::appName = 0;
const char *CrashHandler::appPath = 0;
const char *CrashHandler::appVersion = 0;
const char *CrashHandler::programName = 0;
const char *CrashHandler::bugAddress = 0;
const char *CrashHandler::crashApp = 0;

// This function sets the function which should be responsible for
// the application crash handling.
void
CrashHandler::setCrashHandler (HandlerType handler)
{
  if (!handler)
    handler = SIG_DFL;

  sigset_t mask;
  sigemptyset(&mask);

#ifdef SIGSEGV
  signal (SIGSEGV, handler);
  sigaddset(&mask, SIGSEGV);
#endif
#ifdef SIGFPE
  signal (SIGFPE, handler);
  sigaddset(&mask, SIGFPE);
#endif
#ifdef SIGILL
  signal (SIGILL, handler);
  sigaddset(&mask, SIGILL);
#endif
#ifdef SIGABRT
  signal (SIGABRT, handler);
  sigaddset(&mask, SIGABRT);
#endif

  sigprocmask(SIG_UNBLOCK, &mask, 0);

  _crashHandler = handler;
}

void
CrashHandler::defaultCrashHandler (int sig)
{
  // WABA: Do NOT use kdDebug() in this function because it is much too risky!
  // Handle possible recursions
  static int crashRecursionCounter = 0;
  crashRecursionCounter++; // Nothing before this, please !

  signal(SIGALRM, SIG_DFL);
  alarm(3); // Kill me... (in case we deadlock in malloc)

  if (crashRecursionCounter < 2) {
	Arts::AudioSubSystem *a = Arts::AudioSubSystem::the();
	if(a) a->emergencyCleanup();

#ifdef HAVE_REALTIME_SCHED
	struct sched_param sp;
	sp.sched_priority = 0;
	if (sched_setscheduler(0, SCHED_OTHER, &sp) != 0)
		fprintf(stderr, "CrashHandler: can't stop running as realtime process (%s)\n", strerror(errno));
#endif

    crashRecursionCounter++; //
  }

  // Close all remaining file descriptors
  struct rlimit rlp;
  getrlimit(RLIMIT_NOFILE, &rlp);
  for (int i = 0; i < (int)rlp.rlim_cur; i++)
    close(i);

  bool shuttingDown = false;

  // don't load drkonqi during shutdown
  if ( !shuttingDown )
  {
    if (crashRecursionCounter < 3)
    {
      if (appName)
      {
#ifndef NDEBUG
        fprintf(stderr, "CrashHandler: crashing... crashRecursionCounter = %d\n", crashRecursionCounter);
        fprintf(stderr, "CrashHandler: Application Name = %s path = %s pid = %d\n", appName ? appName : "<unknown>" , appPath ? appPath : "<unknown>", getpid());
#else
        fprintf(stderr, "CrashHandler: Application '%s' crashing...\n", appName ? appName : "<unknown>");
#endif

        pid_t pid = fork();

        if (pid <= 0) {
          // this code is leaking, but this should not hurt cause we will do a
          // exec() afterwards. exec() is supposed to clean up.
          char * argv[18]; // don't forget to update this
          int i = 0;

          // argument 0 has to be drkonqi
          argv[i++] = strdup(crashApp);

		  char *display = getenv("DISPLAY");
		  
		  if (display) {
          // start up on the correct display
          	argv[i++] = strdup("-display");
            argv[i++] = display;
		  }

          // we have already tested this
          argv[i++] = strdup("--appname");
          argv[i++] = strdup(appName);

          // only add apppath if it's not NULL
          if (appPath) {
            argv[i++] = strdup("--apppath");
            argv[i++] = strdup(appPath);
          }

          // signal number -- will never be NULL
          argv[i++] = strdup("--signal");
          argv[i++] = arts_strdup_printf("%d", sig);

          // pid number -- only include if this is the child
          // the debug stuff will be disabled if we was not able to fork
          if (pid == 0) {
            argv[i++] = strdup("--pid");
            argv[i++] = arts_strdup_printf("%d", getppid());
          }

		  if (appVersion) {
			argv[i++] = strdup("--appversion");
			argv[i++] = strdup(appVersion);
		  }

          if (programName) {
            argv[i++] = strdup("--programname");
            argv[i++] = strdup(programName);
          }

          if (bugAddress) {
            argv[i++] = strdup("--bugaddress");
            argv[i++] = strdup(bugAddress);
          }

          // NULL terminated list
          argv[i++] = NULL;

          setgid(getgid());
          if (getuid() != geteuid())
            setuid(getuid());
          if (getuid() != geteuid()) {
	    perror("setuid()");
            exit(255);
          }

          execvp(crashApp, argv);

          // we could clean up here
          // i = 0;
          // while (argv[i])
          //   free(argv[i++]);
        }
        else
        {

          alarm(0); // Seems we made it....

 		  fprintf(stderr, "ArtsCrash: ... waiting for child to exit\n");
          // wait for child to exit
          waitpid(pid, NULL, 0);
          _exit(253);
        }
      }
      else {
        fprintf(stderr, "Unknown appname\n");
      }
    }

    if (crashRecursionCounter < 4)
    {
      fprintf(stderr, "Unable to start %s\n", crashApp);
    }
  }

  _exit(255);
}
