// ****************************************************************************
//  Project:        libguytools
// ****************************************************************************
//  Programmer:     Guy Voncken
//                  Police Grand-Ducale
//                  Service de Police Judiciaire
//                  Section Nouvelles Technologies
// ****************************************************************************
//  Module:         Signal handler with backtrace
// ****************************************************************************

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
//#define __USE_GNU
#include <string.h>
#include <unistd.h>
//#define __USE_XOPEN
#include <signal.h>
#include <execinfo.h>
#include <pthread.h>

#include "tooltypes.h"
#include "toolglobalid.h"
#include "toolsignal.h"
#include "toolerror.h"

// ------------------------------------
//              Constants
// ------------------------------------

#define __FFL__ (char *) __FILE__, (char *)__FUNCTION__, __LINE__

// ------------------------------------
//          Type definitions
// ------------------------------------

typedef struct
{
   t_pToolSignalLogFn   pLogFn;
   t_pToolSignalHandler pSignalHandlerFn;
   struct sigaction      SignalActionDefault;
} t_ToolSignalLocal;


// ------------------------------------
//          Module variables
// ------------------------------------

bool ToolSignalInitialised = false;

t_ToolSignalLocal ToolSignalLocal;

// ------------------------------------
//           Log functions
// ------------------------------------

static int ToolSignalLogEntry (bool Error, const char *pFileName, const char *pFunctionName, int LineNr, const char *pFormat, ...)
{
   va_list VaList;

   va_start(VaList, pFormat);
   if (ToolSignalLocal.pLogFn)
   {
      (*ToolSignalLocal.pLogFn)(Error, pthread_self(), pFileName, pFunctionName, LineNr, pFormat, VaList);
   }
   else
   {
      printf ("\n");
      vprintf (pFormat, VaList);
   }
   va_end(VaList);

   return NO_ERROR;
}

// ------------------------------------
//           Main functions
// ------------------------------------

static void ToolSignalStandardSet (sigset_t *pSignalSet)
{
   sigfillset (pSignalSet);           // Add all known signals to set, except the ones listed below:
   sigdelset  (pSignalSet, SIGSEGV);  //    - handled by our backtrace function, see below
   sigdelset  (pSignalSet, SIGPIPE);  //    - which would cause problems in ceratin situations when our program deals with pipes
   sigdelset  (pSignalSet, SIGWINCH); //    - emitted on window resize
   sigdelset  (pSignalSet, SIGCHLD);  //    - QProcess relies on receiving this one in order to know when a child process finishes
}                                     // These settings will be valid for the current thread and all its children threads. That's the reason, why
                                      // ToolSignalInit should be called in the application main thread before starting any other thread.

static void *ToolSignalThread (void *p)
{
   sigset_t SignalSet;
   int      Signal;
   bool     Exit;
   char   *pSignalName;

   p=p; // Dummy statement to avoid compiler warning about unused p
   ToolSignalLogEntry (false, __FFL__, "Signal guard thread started with id %d", getpid());

   Exit = false;
   while (!Exit)
   {
      ToolSignalStandardSet (&SignalSet);
      sigwait (&SignalSet, &Signal);    // wait for any signal in given set

      // when we get here, we've caught a signal
      // ---------------------------------------
      pSignalName = strsignal(Signal);
      ToolSignalLogEntry (true, __FFL__, "Thread (%d-%d): Caught signal: %s", getpid(), pthread_self(), pSignalName);
      if (ToolSignalLocal.pSignalHandlerFn)
         (*ToolSignalLocal.pSignalHandlerFn) (Signal);

      switch (Signal)
      {
         case SIGTERM:
         case SIGINT :  Exit = true; break;
         default:                    break;
      }
   }
   ToolSignalLogEntry (true, __FFL__, "Stopping signal guard thread.");
   return NULL;
}

// ToolSignalBacktraceHandler is called by any
// thread in the program causing a segment violation.
static void ToolSignalBacktraceHandler (int Signal, siginfo_t *pSignalInfo, void *pSecret)
{
   const int     TraceArrLen = 50;
   char      **ppMessages;
   void         *TraceArr[TraceArrLen];
   int           TraceSize;
   int           i;
   static int    RecursiveCallDetection = 0;
   void *pIP = NULL;

   #if defined(__sparc__)
      struct sigcontext* pSigContext = (struct sigcontext*) pSecret;
      #if __WORDSIZE == 64
         pIP = (void*) pSigContext->sigc_regs.tpc ;
      #else
         pIP = (void*) pSigContext->si_regs.pc ;
      #endif
   #else
      ucontext_t* pUContext = (ucontext_t*) pSecret;
      #if   defined(__i386__)
         pIP = (void*) pUContext->uc_mcontext.gregs[REG_EIP];
      #elif defined(__x86_64__)
         pIP = (void*) pUContext->uc_mcontext.gregs[REG_RIP];
      #elif defined(__hppa__)
         pIP = (void*) pUContext->uc_mcontext.sc_iaoq[0] & ~0x3UL;
      #elif (defined (__ppc__)) || (defined (__powerpc__))
         pIP = (void*) pUContext->uc_mcontext.regs->nip;
      #endif
    #endif

   RecursiveCallDetection++;
   switch (RecursiveCallDetection)
   {
      case 1:
         ppMessages = NULL;

         ToolSignalLogEntry (true, __FFL__, "----------------------------------------------------------------------");
         if (Signal == SIGSEGV)
         {
            ToolSignalLogEntry (true, __FFL__, "Thread (%d-%d): Got signal '%s' (%d), faulty address is %p, from %p",
                               getpid(), pthread_self(), strsignal(Signal), Signal,
                               pSignalInfo->si_addr, pIP);
         }
         else
         {
            ToolSignalLogEntry (true, __FFL__, "Thread (%d-%d): Got signal '%s' (%d) -- strange, function should only be called on SIGSEGV.",
                               getpid(), pthread_self(), strsignal(Signal), Signal);
         }
         TraceSize = backtrace (TraceArr, TraceArrLen);
         TraceArr[1] = pIP;
         ppMessages  = backtrace_symbols (TraceArr, TraceSize);

         ToolSignalLogEntry (true, __FFL__, "Backtrace execution path:");
         for (i=1; i<TraceSize; ++i)
            ToolSignalLogEntry (true, __FFL__, "[Backtrace] %s", ppMessages[i]);

         if (ToolSignalLocal.pSignalHandlerFn)
            (*ToolSignalLocal.pSignalHandlerFn) (SIGSEGV);

         ToolSignalLogEntry (true, __FFL__, "Calling original SIGSEGV handler");
         ToolSignalLogEntry (true, __FFL__, "----------------------------------------------------------------------");
         sigaction (SIGSEGV, &ToolSignalLocal.SignalActionDefault, NULL);                    // Switch back to original handler, if ever another esception
         (*ToolSignalLocal.SignalActionDefault.sa_sigaction) (Signal, pSignalInfo, pSecret); // occurs (nobody knows, what the original handler is doing..)
         break;

      case 2:
         ToolSignalLogEntry (true, __FFL__, "Recursive call of backtrace handler detected, calling orignal handler now.");
         sigaction (SIGSEGV, &ToolSignalLocal.SignalActionDefault, NULL);                    // Switch back to original handler, if ever another esception
         (*ToolSignalLocal.SignalActionDefault.sa_sigaction) (Signal, pSignalInfo, pSecret); // occurs (nobody knows, what the original handler is doing..)
         break;

      default:
         ToolSignalLogEntry (true, __FFL__, "Recursive call of backtrace handler detected, iteration %d. Exiting now.", RecursiveCallDetection);
         break;
   }
   exit (10);
}

// ------------------------------
//     Module initialisation
// ------------------------------

int ToolSignalInit (t_pToolSignalLogFn pFnLog, t_pToolSignalHandler pFnSig, int *pThreadId)
{
   struct sigaction SignalAction;
   sigset_t         SignalSet;
   int              StartRc;

   if (ToolSignalInitialised)
      return ERROR_TOOLSIGNAL_ALREADY_INITIALISED;

   TOOL_CHK (TOOL_ERROR_REGISTER_CODE (ERROR_BASE_TOOL_SIGNAL))
   TOOL_CHK (TOOL_ERROR_REGISTER_CODE (ERROR_TOOLSIGNAL_ALREADY_INITIALISED))
   TOOL_CHK (TOOL_ERROR_REGISTER_CODE (ERROR_TOOLSIGNAL_STARTTHREAD_FAILED))

   ToolSignalLocal.pLogFn           = pFnLog;
   ToolSignalLocal.pSignalHandlerFn = pFnSig;

   // Install segment violation signal handler
   // ----------------------------------------
   sigemptyset (&SignalAction.sa_mask);
   SignalAction.sa_sigaction = ToolSignalBacktraceHandler;  // ToolSignalBacktraceSignalHandler will be called
   SignalAction.sa_flags     = SA_RESTART | SA_SIGINFO;     // by any thread having a segment violation
   sigaction (SIGSEGV, &SignalAction, &ToolSignalLocal.SignalActionDefault);
   if (pThreadId)
      *pThreadId = pthread_self();

   // Set all other signals to "block"
   // --------------------------------
   ToolSignalStandardSet (&SignalSet);
   pthread_sigmask (SIG_BLOCK, &SignalSet, NULL);   // reason, why ToolSignalInit should be called in the application main thread before

   // Start the signal handler thread
   // -------------------------------
   pthread_t ThreadStruct;

   StartRc = pthread_create (&ThreadStruct, 0, ToolSignalThread, NULL);
   if (StartRc != 0)
      return ERROR_TOOLSIGNAL_STARTTHREAD_FAILED;

   ToolSignalInitialised = true;

   return NO_ERROR;
}

int ToolSignalDeInit (void)
{
   sigset_t SignalSet;

   if (!ToolSignalInitialised)
      return ERROR_TOOLSIGNAL_NOT_INITIALISED;

   // Unblock the signals again
   // -------------------------
   ToolSignalStandardSet (&SignalSet);                // Unblocking is only done for this thread; signals in child
   pthread_sigmask (SIG_UNBLOCK, &SignalSet, NULL);   // threads started from this thread remain blocked.

   // Switch back to default SIGSEV signal handler
   // --------------------------------------------
   sigaction (SIGSEGV, &ToolSignalLocal.SignalActionDefault, NULL);

   ToolSignalInitialised = false;
   return NO_ERROR;
}

