/*
 * The Service/Daemon support (includes also signal/low-level error management)
 *
 * Copyright 2003 Giancarlo Niccolai <gian@niccolai.ws>
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program 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 this program; see the file LICENSE.txt.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

#include "hbapi.h"
#include "hbapiitm.h"
#include "hbapierr.h"
#include "hbapifs.h"
#include "hbvm.h"
#include "hbstack.h"
#include "hbserv.h"
#include "hbthread.h"

#include <stdio.h>

#if defined( HB_OS_WIN )
   #include <windows.h>
   #if defined( HB_OS_WIN_CE )
      #include "hbwince.h"
   #endif
#endif

/* These targets cannot compile this module */
#if ! defined( HB_OS_DOS ) && \
   ! defined( HB_OS_DARWIN_5 ) && \
   ! ( defined( HB_OS_WIN_CE ) && ( defined( __POCC__ ) || ( defined( _MSC_VER ) && ( _MSC_VER <= 1500 ) ) ) ) && \
   ! defined( HB_OS_WIN_64 ) && \
   ( ! defined( HB_OS_OS2 ) || defined( HB_OS_OS2_GCC ) ) && \
   ! defined( __HAIKU__ )
/* TODO: Haiku will supposedly do this later on, read /boot/develop/headers/posix/signal.h */

#if defined( HB_OS_UNIX ) || defined( HB_OS_OS2_GCC )
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#endif

#ifdef __LCC__
#define EXCEPTION_ILLEGAL_INSTRUCTION  STATUS_ILLEGAL_INSTRUCTION
#endif

/* Global definition, valid for all systems */

static void s_serviceSetHBSig( void );
static void s_serviceSetDflSig( void );
static void s_signalHandlersInit( void );

static PHB_ITEM sp_hooks       = NULL;
static HB_BOOL  bSignalEnabled = HB_TRUE;
static int      sb_isService   = 0;

/* There is a service mutex in multithreading */
static HB_CRITICAL_NEW( s_ServiceMutex );

/* This structure holds a translation to transform a certain OS level signal
   into abstract HB_SIGNAL; os specific implementation must provide the
   s_sigTable containing all the available translations */

typedef struct
{
   HB_UINT sig;
   HB_UINT subsig;
   HB_UINT translated;
} S_TUPLE;

static int s_translateSignal( HB_UINT sig, HB_UINT subsig );

/* Unix specific signal handling implementation
 *
 * This section has unix specific code to manage the
 * signals, both from kernel or from users.
 */

#if defined( HB_OS_UNIX ) || defined( HB_OS_OS2_GCC )

/* TODO: Register the old signal action to allow graceful fallback */
#if 0
static struct sigaction s_aOldAction[ SIGUSR2 + 1 ];
#endif

/* Implementation of the signal translation table */
static S_TUPLE s_sigTable[] =
{
   { SIGHUP,  0, HB_SIGNAL_REFRESH   },
   { SIGINT,  0, HB_SIGNAL_INTERRUPT },
   { SIGQUIT, 0, HB_SIGNAL_QUIT      },
   { SIGILL,  0, HB_SIGNAL_FAULT     },
   { SIGABRT, 0, HB_SIGNAL_QUIT      },
   { SIGFPE,  0, HB_SIGNAL_MATHERR   },
   { SIGSEGV, 0, HB_SIGNAL_FAULT     },
   { SIGTERM, 0, HB_SIGNAL_QUIT      },
   { SIGUSR1, 0, HB_SIGNAL_USER1     },
   { SIGUSR2, 0, HB_SIGNAL_USER2     },
   { 0,       0, 0                   }
};

#if defined( HB_OS_OS2_GCC ) || defined( __WATCOMC__ )
static void s_signalHandler( int sig )
#else
static void s_signalHandler( int sig, siginfo_t * info, void * v )
#endif
{
   HB_UINT  uiSig;
   HB_SIZE  nPos;

   #if ! ( defined( HB_OS_OS2_GCC ) || defined( __WATCOMC__ ) )
   HB_SYMBOL_UNUSED( v );
   #endif

   /* let's find the right signal handler. */
   hb_threadEnterCriticalSectionGC( &s_ServiceMutex );

   /* avoid working if PRG signal handling has been disabled */
   if( ! bSignalEnabled )
   {
      hb_threadLeaveCriticalSection( &s_ServiceMutex );
      return;
   }

   bSignalEnabled = HB_FALSE;
   nPos = hb_arrayLen( sp_hooks );
   /* subsig not necessary */
   uiSig = ( HB_UINT ) s_translateSignal( ( HB_UINT ) sig, 0 );

   while( nPos > 0 )
   {
      PHB_ITEM pFunction;
      HB_UINT  uiMask;

      pFunction = hb_arrayGetItemPtr( sp_hooks, nPos );
      uiMask    = ( HB_UINT ) hb_arrayGetNI( pFunction, 1 );
      if( uiMask & uiSig )
      {
         PHB_ITEM pExecArray, pRet;
         int iRet;

         /* we don't unlock the mutex now, even if it is
            a little dangerous. But we are in a signal hander...
            for now just 2 parameters */
         pExecArray = hb_itemArrayNew( 3 );
         hb_arraySet( pExecArray, 1, hb_arrayGetItemPtr( pFunction, 2 ) );
         hb_arraySetNI( pExecArray, 2, uiSig );

         /* the third parameter is an array: */

         pRet = hb_arrayGetItemPtr( pExecArray, 3 );
         #if defined( HB_OS_OS2_GCC ) || defined( __WATCOMC__ )
         hb_arrayNew( pRet, 1 );
         #elif defined( HB_OS_BSD )
         hb_arrayNew( pRet, info ? 6 : 1 );
         #else
         hb_arrayNew( pRet, 6 );
         #endif
         hb_arraySetNI( pRet, HB_SERVICE_OSSIGNAL, sig );
         #if ! ( defined( HB_OS_OS2_GCC ) || defined( __WATCOMC__ ) )
         #if defined( HB_OS_BSD )
         if( info )
         #endif
         {
            hb_arraySetNI( pRet, HB_SERVICE_OSSUBSIG, info->si_code );
            #if ! defined( HB_OS_VXWORKS )
            hb_arraySetNI( pRet, HB_SERVICE_OSERROR, info->si_errno );
            hb_arraySetPtr( pRet, HB_SERVICE_ADDRESS, ( void * ) info->si_addr );
            hb_arraySetNI( pRet, HB_SERVICE_PROCESS, info->si_pid );
            hb_arraySetNI( pRet, HB_SERVICE_UID, info->si_uid );
            #endif
         }
         #endif

         pRet = hb_itemDo( pExecArray, 0 );
         iRet = hb_itemGetNI( pRet );
         hb_itemRelease( pRet );
         hb_itemRelease( pExecArray );

         switch( iRet )
         {
            case HB_SERVICE_HANDLED:
               bSignalEnabled = HB_TRUE;
               hb_threadLeaveCriticalSection( &s_ServiceMutex );
               return;

            case HB_SERVICE_QUIT:
               bSignalEnabled = HB_FALSE;
               hb_threadLeaveCriticalSection( &s_ServiceMutex );
               /* TODO: A service cleanup routine */
               hb_vmRequestQuit();
               /* Allow signals to go through pthreads */
               s_serviceSetDflSig();
               /* NOTICE: should be pthread_exit(0), but a bug in linuxthread prevents it:
                  calling pthread exit from a signal handler will cause infinite wait for
                  restart signal.
                  This solution is rude, while the other would allow clean VM termination...
                  but it works.
                */
               exit( 0 );
         }
      }
      nPos--;
   }

   bSignalEnabled = HB_TRUE;
   #if 0
   s_serviceSetHBSig();
   #endif

   #if 0
   if( uiSig != HB_SIGNAL_UNKNOWN )
   {
      if( sa_oldAction[ sig ].sa_flags & SA_SIGINFO )
         sa_oldAction[ sig ].sa_sigaction( sig, info, v );
      else
         sa_oldAction[ sig ].sa_handler( sig );
   }
   #endif
}

/* 2003 - <maurilio.longo@libero.it>
   to fix as soon as thread support is ready on OS/2
 */
#if defined( HB_THREAD_SUPPORT ) && ! defined( HB_OS_OS2 )
static void * s_signalListener( void * my_stack )
{
   static HB_BOOL bFirst = HB_TRUE;

   sigset_t   passall;
   HB_STACK * pStack = ( HB_STACK * ) my_stack;

#if defined( HB_OS_BSD )
   int sig;
#else
   siginfo_t sinfo;
#endif

#ifdef HB_THREAD_TLS_KEYWORD
   hb_thread_stack = my_stack;
#else
   pthread_setspecific( hb_pkCurrentStack, my_stack );
#endif

   pStack->th_id = HB_CURRENT_THREAD();
   hb_threadLinkStack( pStack );
   HB_STACK_LOCK;

   /* and now accepts all signals */
   sigemptyset( &passall );

   /* and wait for all signals */
   sigaddset( &passall, SIGHUP );
   sigaddset( &passall, SIGQUIT );
   sigaddset( &passall, SIGILL );
   sigaddset( &passall, SIGABRT );
   sigaddset( &passall, SIGFPE );
   sigaddset( &passall, SIGSEGV );
   sigaddset( &passall, SIGTERM );
   sigaddset( &passall, SIGUSR1 );
   sigaddset( &passall, SIGUSR2 );
   sigaddset( &passall, SIGHUP );

   pthread_cleanup_push( hb_threadTerminator, my_stack );
   pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, NULL );
   pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL );

   for( ;; )
   {
      /* allow safe cancelation */
      HB_STACK_UNLOCK;

      /* reset signal handling; this is done here (so I don't
         mangle with pthread_ calls, and I don't hold mutexes),
         and just once (doing it twice would be useless). */
      if( bFirst )
      {
         pthread_sigmask( SIG_SETMASK, &passall, NULL );
         bFirst = HB_FALSE;
      }

      /* This is also a cancelation point. When the main thread
         is done, it will kill all the threads having a stack
         including this one.
         ATM we don't care very much about signal handling during
         termination: no handler is set for them, so the DFL
         action is taken (and that should be fine). */
#if defined( HB_OS_BSD )
      sigwait( &passall, &sig );
#else
      sigwaitinfo( &passall, &sinfo );
#endif

      /* lock stack before passing the ball to VM. */
      HB_STACK_LOCK;
#if defined( HB_OS_BSD )
      s_signalHandler( sig, NULL, NULL );
#else
      s_signalHandler( sinfo.si_signo, &sinfo, NULL );
#endif
   }

   pthread_cleanup_pop( 1 );
   return 0;
}
#endif
#endif

/* Windows specific exception filter system.
 *
 * Windows will only catch exceptions; It is necessary to rely on the
 * hb_ServiceLoop() to receive user generated messages.
 */

#ifdef HB_OS_WIN
static void s_serviceSetHBSig( void );

/* message filter hook for user generated signals */
static HHOOK s_hMsgHook = NULL;

/* old error mode */
static UINT s_uiErrorMode = 0;

/* implementation of the signal translation table
   Under windows, 0 is a system exception, while 1 is a user message
 */
static S_TUPLE s_sigTable[] = {

   /* memory/processor fault exception */
   { 0, EXCEPTION_ACCESS_VIOLATION,      HB_SIGNAL_FAULT     },
   { 0, EXCEPTION_ILLEGAL_INSTRUCTION,   HB_SIGNAL_FAULT     },
   { 0, EXCEPTION_IN_PAGE_ERROR,         HB_SIGNAL_FAULT     },
   { 0, EXCEPTION_STACK_OVERFLOW,        HB_SIGNAL_FAULT     },
   { 0, EXCEPTION_PRIV_INSTRUCTION,      HB_SIGNAL_FAULT     },
   { 0, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, HB_SIGNAL_FAULT     },
   { 0, EXCEPTION_DATATYPE_MISALIGNMENT, HB_SIGNAL_FAULT     },

   /* Math exceptions */
   { 0, EXCEPTION_FLT_DENORMAL_OPERAND,  HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_FLT_INVALID_OPERATION, HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_FLT_INEXACT_RESULT,    HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_FLT_DIVIDE_BY_ZERO,    HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_FLT_OVERFLOW,          HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_FLT_STACK_CHECK,       HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_FLT_UNDERFLOW,         HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_INT_DIVIDE_BY_ZERO,    HB_SIGNAL_MATHERR   },
   { 0, EXCEPTION_INT_OVERFLOW,          HB_SIGNAL_MATHERR   },

   /* User requests */
   { 1, WM_USER,                         HB_SIGNAL_USER1     },
   { 1, WM_USER + 1,                     HB_SIGNAL_USER2     },
   { 1, WM_USER + 2,                     HB_SIGNAL_REFRESH   },
   { 1, WM_USER + 3,                     HB_SIGNAL_INTERRUPT },
   { 1, WM_QUIT,                         HB_SIGNAL_QUIT      },

   /* Console handler */
   { 2, CTRL_C_EVENT,                    HB_SIGNAL_INTERRUPT },
   { 2, CTRL_BREAK_EVENT,                HB_SIGNAL_INTERRUPT },
   { 2, CTRL_CLOSE_EVENT,                HB_SIGNAL_QUIT      },
   { 2, CTRL_LOGOFF_EVENT,               HB_SIGNAL_QUIT      },
   { 2, CTRL_SHUTDOWN_EVENT,             HB_SIGNAL_QUIT      },

   { 0, 0,                               0                   }
};

/* Manager of signals for windows */
static LONG s_signalHandler( int type, int sig, PEXCEPTION_RECORD exc )
{
   HB_SIZE  nPos;
   HB_UINT  uiSig;

   /* let's find the right signal handler. */
   hb_threadEnterCriticalSectionGC( &s_ServiceMutex );

   /* avoid working if PRG signal handling has been disabled */
   if( ! bSignalEnabled )
   {
      hb_threadLeaveCriticalSection( &s_ServiceMutex );
      return EXCEPTION_EXECUTE_HANDLER;
   }

   bSignalEnabled = HB_FALSE;
   nPos = hb_arrayLen( sp_hooks );
   /* subsig not necessary */
   uiSig = ( HB_UINT ) s_translateSignal( ( HB_UINT ) type, ( HB_UINT ) sig );

   while( nPos > 0 )
   {
      PHB_ITEM pFunction;
      HB_UINT  uiMask;

      pFunction = hb_arrayGetItemPtr( sp_hooks, nPos );
      uiMask    = ( HB_UINT ) hb_arrayGetNI( pFunction, 1 );
      if( ( uiMask & uiSig ) == uiSig )
      {
         PHB_ITEM pExecArray, pRet;
         int      iRet;

         /* we don't unlock the mutex now, even if it is
            a little dangerous. But we are in a signal hander...
            for now just 2 parameters */
         pExecArray = hb_itemArrayNew( 3 );
         hb_arraySetForward( pExecArray, 1, hb_arrayGetItemPtr( pFunction, 2 ) );
         hb_arraySetNI( pExecArray, 2, uiSig );

         /* the third parameter is an array:
          * 1: low-level signal
          * 2: low-level subsignal
          * 3: low-level system error
          * 4: address that rose the signal
          * 5: process id of the signal riser
          * 6: UID of the riser
          */

         pRet = hb_arrayGetItemPtr( pExecArray, 3 );
         hb_arrayNew( pRet, 6 );

         hb_arraySetNI( pRet, HB_SERVICE_OSSIGNAL, type );
         hb_arraySetNI( pRet, HB_SERVICE_OSSUBSIG, sig );
         /* could be meaningless, but does not matter here */
         hb_arraySetNI( pRet, HB_SERVICE_OSERROR, GetLastError() );

         if( type == 0 ) /* exception */
            hb_arraySetPtr( pRet, HB_SERVICE_ADDRESS, ( void * ) exc->ExceptionAddress );
         else
            hb_arraySetPtr( pRet, HB_SERVICE_ADDRESS, NULL );

         /* TODO: */
         hb_arraySetNI( pRet, HB_SERVICE_PROCESS, GetCurrentThreadId() );
         /* TODO: */
         hb_arraySetNI( pRet, HB_SERVICE_UID, 0 );

         pRet = hb_itemDo( pExecArray, 0 );
         iRet = hb_itemGetNI( pRet );
         hb_itemRelease( pRet );
         hb_itemRelease( pExecArray );

         switch( iRet )
         {
            case HB_SERVICE_HANDLED:
               bSignalEnabled = HB_TRUE;
               hb_threadLeaveCriticalSection( &s_ServiceMutex );
               return EXCEPTION_CONTINUE_EXECUTION;

            case HB_SERVICE_QUIT:
               bSignalEnabled = HB_FALSE;
               hb_threadLeaveCriticalSection( &s_ServiceMutex );
               hb_vmRequestQuit();
#ifndef HB_THREAD_SUPPORT
               hb_vmQuit();
               exit( 0 );
#else
               hb_threadCancelInternal();
#endif
         }
      }
      nPos--;
   }

   bSignalEnabled = HB_TRUE;
   return EXCEPTION_EXECUTE_HANDLER;
}


static LRESULT CALLBACK s_exceptionFilter( PEXCEPTION_POINTERS exInfo )
{
   return s_signalHandler( 0, exInfo->ExceptionRecord->ExceptionCode, exInfo->ExceptionRecord );
}

static LRESULT CALLBACK s_MsgFilterFunc( int nCode, WPARAM wParam, LPARAM lParam )
{
   PMSG msg;

   if( nCode < 0 )
      return CallNextHookEx( s_hMsgHook, nCode, wParam, lParam );

   msg = ( PMSG ) lParam;

   switch( msg->message )
   {
      case WM_USER:
      case WM_USER + 1:
      case WM_USER + 2:
      case WM_USER + 3:
      case WM_QUIT:
         /* we'll ignore the request here.
            the application must still receive the message */
         s_signalHandler( 1, msg->message, NULL );
   }

   /* return next hook anyway */
   return CallNextHookEx( s_hMsgHook, nCode, wParam, lParam );
}

#ifdef HB_THREAD_SUPPORT
extern DWORD hb_dwCurrentStack;
#endif

BOOL WINAPI s_ConsoleHandlerRoutine( DWORD dwCtrlType )
{
#ifdef HB_THREAD_SUPPORT
   HB_STACK * pStack = NULL;

   /* we need a new stack: this is NOT an hb thread. */

   if( TlsGetValue( hb_dwCurrentStack ) == 0 )
   {
      pStack       = hb_threadCreateStack( GetCurrentThreadId() );
      pStack->th_h = GetCurrentThread();
      TlsSetValue( hb_dwCurrentStack, ( void * ) pStack );
   }
#endif

   s_signalHandler( 2, dwCtrlType, NULL );

#ifdef HB_THREAD_SUPPORT
   if( pStack )
      hb_threadDestroyStack( pStack );
#endif
   /* We have handled it */
   return TRUE;
}

#endif

/**
 * Filter/handlers setup/shutdown
 * This utility functions are meant to abstract the process of declare and
 * remove the signal handlers, and do it in a mutlti-platform fashion. Use this
 * to implement new platform signal/exception handlers.
 */

/* Set the signal handlers to our program interceptors. */

static void s_serviceSetHBSig( void )
{
#if defined( HB_OS_UNIX ) || defined( HB_OS_OS2_GCC )
   struct sigaction act;

#if defined( HB_THREAD_SUPPORT ) && ! defined( HB_OS_OS2 )
   sigset_t blockall;
   /* set signal mask */
   sigemptyset( &blockall );
   sigaddset( &blockall, SIGHUP );
   sigaddset( &blockall, SIGQUIT );
   sigaddset( &blockall, SIGILL );
   sigaddset( &blockall, SIGABRT );
   sigaddset( &blockall, SIGFPE );
   sigaddset( &blockall, SIGSEGV );
   sigaddset( &blockall, SIGTERM );
   sigaddset( &blockall, SIGUSR1 );
   sigaddset( &blockall, SIGUSR2 );
   sigaddset( &blockall, SIGHUP );

   pthread_sigmask( SIG_SETMASK, &blockall, NULL );
#endif

   /* to avoid problems with differ sigaction structures and uninitialized
      fields */
   memset( &act, 0, sizeof( act ) );

   #if defined( HB_OS_OS2_GCC ) || defined( __WATCOMC__ )
   act.sa_handler = s_signalHandler;
   #else
   /* using more descriptive sa_action instead of sa_handler */
   act.sa_handler   = NULL;            /* if act.sa.. is a union, we just clean this */
   act.sa_sigaction = s_signalHandler; /* this is what matters */
   /* block al signals, we don't want to be interrupted. */
   #if 0
   sigfillset( &act.sa_mask );
   #endif
   #endif


   #ifdef HB_OS_OS2_GCC
   act.sa_flags = SA_NOCLDSTOP;
   #else
   act.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
   #endif

   sigaction( SIGHUP, &act, NULL );
   sigaction( SIGQUIT, &act, NULL );
   sigaction( SIGILL, &act, NULL );
   sigaction( SIGABRT, &act, NULL );
   sigaction( SIGFPE, &act, NULL );
   sigaction( SIGSEGV, &act, NULL );
   sigaction( SIGTERM, &act, NULL );
   sigaction( SIGUSR1, &act, NULL );
   sigaction( SIGUSR2, &act, NULL );

   /* IGNORE pipe */
   signal( SIGPIPE, SIG_IGN );
#endif

#ifdef HB_OS_WIN
   /* disable all os-level error boxes */
   s_uiErrorMode = SetErrorMode(
      SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX |
      SEM_NOOPENFILEERRORBOX );

   SetUnhandledExceptionFilter( s_exceptionFilter );
   s_hMsgHook = SetWindowsHookEx( WH_GETMESSAGE, ( HOOKPROC ) s_MsgFilterFunc, NULL, GetCurrentThreadId() );
   SetConsoleCtrlHandler( s_ConsoleHandlerRoutine, TRUE );
#endif
}

/* Reset the signal handlers to the default OS value */

static void s_serviceSetDflSig( void )
{
#ifdef HB_OS_UNIX
   signal( SIGHUP, SIG_DFL );
   signal( SIGQUIT, SIG_DFL );
   signal( SIGILL, SIG_DFL );
   signal( SIGABRT, SIG_DFL );
   signal( SIGFPE, SIG_DFL );
   signal( SIGSEGV, SIG_DFL );
   signal( SIGTERM, SIG_DFL );
   signal( SIGUSR1, SIG_DFL );
   signal( SIGUSR2, SIG_DFL );
   signal( SIGPIPE, SIG_DFL );
#endif

#ifdef HB_OS_WIN
   SetUnhandledExceptionFilter( NULL );
   if( s_hMsgHook != NULL )
   {
      UnhookWindowsHookEx( s_hMsgHook );
      s_hMsgHook = NULL;
   }
   SetErrorMode( s_uiErrorMode );
   SetConsoleCtrlHandler( s_ConsoleHandlerRoutine, FALSE );
#endif
}


/* This translates a signal into abstract HB_SIGNAL
   from os specific representation */

static int s_translateSignal( HB_UINT sig, HB_UINT subsig )
{
   int i = 0;

   while( s_sigTable[ i ].sig != 0 || s_sigTable[ i ].subsig != 0 || s_sigTable[ i ].translated != 0 )
   {
      if( s_sigTable[ i ].sig == sig &&
          ( s_sigTable[ i ].subsig == subsig || s_sigTable[ i ].subsig == 0 ) )
      {
         return s_sigTable[ i ].translated;
      }
      i++;
   }
   return HB_SIGNAL_UNKNOWN;
}

static void hb_service_exit( void * cargo )
{
   HB_SYMBOL_UNUSED( cargo );

   hb_serviceExit();
}

/**
 * Initializes signal handler system
 */

static void s_signalHandlersInit()
{
#if defined( HB_THREAD_SUPPORT ) && ( defined( HB_OS_UNIX ) || defined( HB_OS_UNIX ) )
   pthread_t  res;
   HB_STACK * pStack;

   s_serviceSetHBSig();

   pStack = hb_threadCreateStack( 0 );
   pthread_create( &res, NULL, s_signalListener, pStack );
#else
   s_serviceSetHBSig();
#endif

   sp_hooks = hb_itemNew( NULL );
   hb_arrayNew( sp_hooks, 0 );
   hb_vmAtQuit( hb_service_exit, NULL );
}

/**
 * hb_*Service routines
 * This is the core of the service system.
 */

/**
 * Starts the service system.
 * Initializes the needed variables.
 * On unix: if the parameter is .T., puts the server in daemonic mode, detaching
 * the main thread from the console and terminating it.
 */
HB_FUNC( HB_STARTSERVICE )
{
   #ifdef HB_THREAD_SUPPORT
   int iCount = hb_threadCountStacks();
   if( iCount > 2 || ( sp_hooks == NULL && iCount > 1 ) )
   {
      /* TODO: Right error code here */
      hb_errRT_BASE_SubstR( EG_ARG, 3012, "Service must be started before starting threads", NULL, 0 );
      return;
   }
   #endif

   #if defined( HB_OS_UNIX ) && ! defined( HB_OS_VXWORKS )
   {
      /* Iconic? */
      if( hb_parl( 1 ) )
      {
         int pid = fork();

         if( pid != 0 )
         {
            hb_vmRequestQuit();
            return;
         }
#ifdef HB_THREAD_SUPPORT
   #ifdef HB_THREAD_TLS_KEYWORD
         hb_thread_stack = &hb_stackMT;
   #else
         pthread_setspecific( hb_pkCurrentStack, ( void * ) &hb_stackMT );
   #endif
#endif
      }
   }
   #endif

   /* let's begin */
   sb_isService = HB_TRUE;

   /* in windows, we just detach from console */
   #ifdef HB_OS_WIN
   if( hb_parl( 1 ) )
      FreeConsole();
   #endif

   /* Initialize only if the service has not yet been initialized */
   if( sp_hooks == NULL )
      s_signalHandlersInit();
}

/**
 * Returns true if the current program is a service, that is if hb_StartService() has
 * Been called. C version useful for internal api
 */
HB_BOOL hb_isService( void )
{
   return sb_isService;
}

/**
 * Clean up when system exits
 * Called from hb_vmQuit()
 */
void hb_serviceExit( void )
{
   if( sp_hooks != NULL )
   {
      /* reset default signal handling */
      s_serviceSetDflSig();
      hb_itemRelease( sp_hooks );
      sp_hooks = NULL;
   }
}


/**
 * Returns true if the current program is a service, that is if hb_StartService() has
 * Been called.
 */
HB_FUNC( HB_ISSERVICE )
{
   hb_retl( sb_isService );
}

/**
 * This is -at least- an helper functions that implements the main loop for
 * the service/daemon system.
 * The minimal thing to do is a hb_gcCollectAll(), because, generally, servers
 * are not interactive, so they tend to have garbage to collect.
 * Under windows, it peeks the pending messages and send the relevant ones
 * (quit, user+1 and user+2) to our handling functions.
 */
HB_FUNC( HB_SERVICELOOP )
{
#ifdef HB_OS_WIN
   MSG msg;
   /* This is just here to trigger our internal hook routine, if the
      final application does not any message handling.
    */
   if( ! PeekMessage( &msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE ) )
   {
      PeekMessage( &msg, NULL, WM_USER, WM_USER + 3, PM_REMOVE );
   }
#endif

   hb_gcCollectAll( HB_FALSE );
}

HB_FUNC( HB_PUSHSIGNALHANDLER )
{
   int      iMask = hb_parni( 1 );
   PHB_ITEM pFunc = hb_param( 2, HB_IT_ANY ), pHandEntry;

   if( pFunc == NULL || iMask == 0 ||
       ( ! HB_IS_POINTER( pFunc ) && ! HB_IS_STRING( pFunc ) && ! HB_IS_BLOCK( pFunc ) )
       )
   {
      hb_errRT_BASE_SubstR( EG_ARG, 3012, "Wrong parameter count/type", NULL,
                            2, hb_param( 1, HB_IT_ANY ), hb_param( 2, HB_IT_ANY ) );
      return;
   }

   pHandEntry = hb_itemArrayNew( 2 );
   hb_arraySetNI( pHandEntry, 1, iMask );
   hb_arraySet( pHandEntry, 2, pFunc );

   /* if the hook is not initialized, initialize it */
   if( sp_hooks == NULL )
      s_signalHandlersInit();

   hb_threadEnterCriticalSectionGC( &s_ServiceMutex );

   hb_arrayAddForward( sp_hooks, pHandEntry );

   hb_threadLeaveCriticalSection( &s_ServiceMutex );

   hb_itemRelease( pHandEntry );
}


HB_FUNC( HB_POPSIGNALHANDLER )
{
   int nLen;

   if( sp_hooks != NULL )
   {
      hb_threadEnterCriticalSectionGC( &s_ServiceMutex );

      nLen = hb_arrayLen( sp_hooks );
      if( nLen > 0 )
      {
         hb_arrayDel( sp_hooks, nLen );
         hb_arrayDel( sp_hooks, nLen - 1 );
         hb_arraySize( sp_hooks, nLen - 2 );
         hb_retl( HB_TRUE );
         if( hb_arrayLen( sp_hooks ) == 0 )
         {
            hb_itemRelease( sp_hooks );
            sp_hooks = NULL;              /* So it can be reinitilized */
         }
      }
      else
         hb_retl( HB_FALSE );

      hb_threadLeaveCriticalSection( &s_ServiceMutex );
   }
   else
      hb_retl( HB_FALSE );
}

/**
 * Return a character description of the low-level signal that has been
 * issued to signal handling routines. This is system dependent.
 * TODO: Make it international through the xHarbour standard message system.
 */
HB_FUNC( HB_SIGNALDESC )
{
#if defined( HB_OS_UNIX ) || defined( HB_OS_OS2_GCC )

   int iSig    = hb_parni( 1 );
   int iSubSig = hb_parni( 2 );

   switch( iSig )
   {
      case SIGSEGV: switch( iSubSig )
         {
         #if ! defined( HB_OS_BSD ) && ! defined( HB_OS_OS2_GCC ) && ! defined( __WATCOMC__ )
            case SEGV_MAPERR: hb_retc_const( "Segmentation fault: address not mapped to object" ); return;
            case SEGV_ACCERR: hb_retc_const( "Segmentation fault: invalid permissions for mapped object" ); return;
         #endif
            default: hb_retc_const( "Segmentation fault" ); return;
         }

      case SIGILL: switch( iSubSig )
         {
         #if ! defined( HB_OS_BSD ) && ! defined( HB_OS_OS2_GCC ) && ! defined( __WATCOMC__ )
            case ILL_ILLOPC: hb_retc_const( "Illegal operation: illegal opcode" ); return;
            case ILL_ILLOPN: hb_retc_const( "Illegal operation: illegal operand" ); return;
            case ILL_ILLADR: hb_retc_const( "Illegal operation: illegal addressing mode" ); return;
            case ILL_ILLTRP: hb_retc_const( "Illegal operation: illegal trap" ); return;
            case ILL_PRVOPC: hb_retc_const( "Illegal operation: privileged opcode" ); return;
            case ILL_PRVREG: hb_retc_const( "Illegal operation: privileged register" ); return;
            case ILL_COPROC: hb_retc_const( "Illegal operation: coprocessor error" ); return;
            case ILL_BADSTK: hb_retc_const( "Illegal operation: internal stack error" ); return;
         #endif
            default: hb_retc_const( "Illegal operation" ); return;
         }

      case SIGFPE: switch( iSubSig )
         {
         #if ! defined( HB_OS_OS2_GCC ) && ! defined( __WATCOMC__ )
         #if ! defined( HB_OS_DARWIN )
            case FPE_INTDIV: hb_retc_const( "Floating point: integer divide by zero" ); return;
            case FPE_INTOVF: hb_retc_const( "Floating point: integer overflow" ); return;
         #endif
            case FPE_FLTDIV: hb_retc_const( "Floating point: floating point divide by zero" ); return;
            case FPE_FLTOVF: hb_retc_const( "Floating point: floating point overflow" ); return;
            case FPE_FLTUND: hb_retc_const( "Floating point: floating point underflow" ); return;
            case FPE_FLTRES: hb_retc_const( "Floating point: floating point inexact result" ); return;
         #if ! defined( HB_OS_VXWORKS )
            case FPE_FLTINV: hb_retc_const( "Floating point: floating point invalid operation" ); return;
         #endif
         #if ! defined( HB_OS_DARWIN )
            case FPE_FLTSUB: hb_retc_const( "Floating point: subscript out of range" ); return;
         #endif
         #endif
            default: hb_retc_const( "Floating point" ); return;
         }

      case SIGQUIT:
         hb_retc_const( "Quit" );
         return;

      case SIGHUP:
         hb_retc_const( "Update" );
         return;

      case SIGINT:
         hb_retc_const( "Interrupt" );
         return;

      case SIGPIPE:
         hb_retc_const( "Broken pipe" );
         return;

      case SIGTERM:
         hb_retc_const( "Terminate process" );
         return;

      case SIGABRT:
         hb_retc_const( "Abort" );
         return;

      case SIGUSR1:
         hb_retc_const( "User defined" );
         return;

      case SIGUSR2:
         hb_retc_const( "User defined (secondary)" );
         return;
   }

#elif defined( HB_OS_WIN )

   int iSig    = hb_parni( 1 );

   if( iSig == 0 ) /* exception */
   {
      DWORD dwSubSig = ( DWORD ) hb_parnl( 2 );

      switch( dwSubSig )
      {
         case EXCEPTION_ACCESS_VIOLATION:
            hb_retc_const( "Memory read/write access violation" ); return;

         case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
            hb_retc_const( "Array out of bounds" ); return;

         case EXCEPTION_DATATYPE_MISALIGNMENT:
            hb_retc_const( "Data misaligned" ); return;

         case EXCEPTION_FLT_DENORMAL_OPERAND:
            hb_retc_const( "Denormal operand in Floating-point operation" ); return;

         case EXCEPTION_FLT_DIVIDE_BY_ZERO:
            hb_retc_const( "Floating-point division by zero" ); return;

         case EXCEPTION_FLT_INEXACT_RESULT:
            hb_retc_const( "Inexact floating-point operation result" ); return;

         case EXCEPTION_FLT_INVALID_OPERATION:
            hb_retc_const( "Invalid floating-point operation" ); return;

         case EXCEPTION_FLT_OVERFLOW:
            hb_retc_const( "Floating-point numeric overflow" ); return;

         case EXCEPTION_FLT_STACK_CHECK:
            hb_retc_const( "Floating-point out of stack" ); return;

         case EXCEPTION_FLT_UNDERFLOW:
            hb_retc_const( "Floating-point numeric underflow" ); return;

         case EXCEPTION_ILLEGAL_INSTRUCTION:
            hb_retc_const( "Illegal instruction" ); return;

         case EXCEPTION_IN_PAGE_ERROR:
            hb_retc_const( "Paging error" ); return;

         case EXCEPTION_INT_DIVIDE_BY_ZERO:
            hb_retc_const( "Integer division by zero" ); return;

         case EXCEPTION_INT_OVERFLOW:
            hb_retc_const( "Integer numeric overflow" ); return;

         case EXCEPTION_PRIV_INSTRUCTION:
            hb_retc_const( "Illegal instruction for current machine mode" ); return;

         case EXCEPTION_STACK_OVERFLOW:
            hb_retc_const( "Stack overflow" ); return;
      }
   }

#endif

   hb_retc_const( "Unrecognized signal" );
}


/*****************************************************************************
 * Debug help: generates a fault or a math error to see if signal catching
 * is working
 **************************************/

HB_FUNC( HB_SERVICEGENERATEFAULT )
{
   int * pGPF = NULL;

   *pGPF = 0;
   /* if it doesn't cause GPF (on some platforms it's possible) try this */
   *( --pGPF ) = 0;
}

HB_FUNC( HB_SERVICEGENERATEFPE )
{
   static double a = 100.0, b = 0.0;

   a = a / b;
}

#else

HB_FUNC( HB_STARTSERVICE ) {}
HB_FUNC( HB_ISSERVICE ) {}
HB_FUNC( HB_SERVICELOOP ) {}
HB_FUNC( HB_PUSHSIGNALHANDLER ) {}
HB_FUNC( HB_POPSIGNALHANDLER ) {}
HB_FUNC( HB_SIGNALDESC ) {}
HB_FUNC( HB_SERVICEGENERATEFAULT ) {}
HB_FUNC( HB_SERVICEGENERATEFPE ) {}

#endif
