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

// Copyright 2008, 2009, 2010, 2011, 2012 Guy Voncken
//
// This file is part of libguytools.
//
// libguytools 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.
//
// libguytools 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libguytools. If not, see <http://www.gnu.org/licenses/>.

#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"

// ------------------------------------
//          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;

   /* Code below is no longer needed. According to tests on amd64 and i386 on Ubuntu 14.04, the address of the 
      place the where exception occured nowadays is correctly included in the array data returned by backtrace(). 
      It thus seems to be no longer necessary to separately fetch and add the instruction pointer corresponding 
      to the segmentation fault.
      This wasn't the case on older systems, see for example http://www.linuxjournal.com/article/6391?page=0,1 .

      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;
         #elif defined(__arm__)
            pIP = (void*) pUContext->uc_mcontext.arm_pc;
         #elif defined(__aarch64__)
            pIP = (void*) pUContext->uc_mcontext.pc;
         #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" */, // See remarks above
                               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;   */  // See remarks above
         ppMessages  = backtrace_symbols (TraceArr, TraceSize);

         ToolSignalLogEntry (true, __FFL__, "Backtrace execution path");
         ToolSignalLogEntry (true, __FFL__, "The first two entries are normally related to the signal handler.");
         ToolSignalLogEntry (true, __FFL__, "The faulty code generally is referenced by the 3rd line in the listing below.");
         for (i=0; 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;
}

