/*  SciGraphica - Scientific graphics and data manipulation
 *  Copyright (C) 2001 Adrian E. Feiguin <feiguin@ifir.edu.ar>
 *
 *  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.
 *
 *  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; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* If you need to do signal handling elsewhere in the code, PLEASE use sigaction(2)
 * instead of signal(3) for doing so. The effect of signal() is platform dependent
 * and can break the careful signalhandling as initialized here. */
 
#include <stdio.h>
#include <signal.h>
#include <glib.h>
#include "sg_project_rescue.h"
#include "sg.h"
#include "sg_dialogs.h"
#include "python/python_main.h"

/* defined will include additional debugging code */
#undef DEBUG_RESCUE


#define SG_WWW   "http://scigraphica.sourceforge.net"
#define SG_EMAIL "scigraphica-devel@lists.sourceforge.net"


/* set dummy values for undefined flags we use */
#ifndef SA_NODEFER
  #define SA_NODEFER 0
#endif
#ifndef SA_RESTART
  #define SA_RESTART 0
#endif
#ifndef SA_RESETHAND
  #define SA_RESETHAND 0
#endif


static void bailout(int signum);
static gint bailout_idle(gpointer data);
static void rescue(int signum);

static int connect_sigaction(int signum, void (*function)(int),
                             unsigned int flags, sigset_t mask);

#ifdef DEBUG_RESCUE
static void sigaction_info(void);
#endif


/* connect signals to new handlers, see sigaction(2) and
 * http://www.gnu.org/manual -> glibc -> Signal Handling */
gint
sg_project_rescue_init( void )
{
 int i;
 int return_value = 0;

 sigset_t bailout_signals;
 sigset_t rescue_signals;
 sigset_t default_mask;

 const unsigned int default_flags = SA_RESTART;
 const unsigned int rescue_flags = SA_RESTART | SA_NODEFER;

 /* clear all signal masks */
 sigemptyset(&default_mask);
 sigemptyset(&bailout_signals);
 sigemptyset(&rescue_signals);
 
#define CONNECT_SIGNAL_TO_RESCUE(SIGNAL) \
        return_value += connect_sigaction(SIGNAL,rescue,rescue_flags,default_mask); \
	sigaddset(&rescue_signals, SIGNAL);
#define CONNECT_SIGNAL_TO_BAILOUT(SIGNAL) \
        return_value += connect_sigaction(SIGNAL,bailout,default_flags,default_mask); \
	sigaddset(&bailout_signals, SIGNAL);
#define CONNECT_SIGNAL_TO_DFL(SIGNAL) \
        return_value += connect_sigaction(SIGNAL,SIG_DFL,default_flags,default_mask);
#define CONNECT_SIGNAL_TO_IGN(SIGNAL) \
        return_value += connect_sigaction(SIGNAL,SIG_IGN,default_flags,default_mask);

#ifdef DEBUG_RESCUE
 printf("Before sg_project_rescue_init:\n");
 sigaction_info();
#endif

 /* Program Error Signals
  * These signals are generated when a serious program error is detected
  * by the operating system or the computer itself. In general, all of
  * these signals are indications that the program is seriously broken
  * in some way, and there's usually no way to continue the computation
  * which encountered the error.
  * The default action for all of these signals is to cause the process
  * to terminate.
  */
 #ifdef SIGFPE
  CONNECT_SIGNAL_TO_RESCUE( SIGFPE )
 #endif
 #ifdef SIGILL
  CONNECT_SIGNAL_TO_RESCUE( SIGILL )
 #endif
 #ifdef SIGSEGV
  CONNECT_SIGNAL_TO_RESCUE( SIGSEGV )
 #endif
 #ifdef SIGBUS
  CONNECT_SIGNAL_TO_RESCUE( SIGBUS )
 #endif
 #ifdef SIGABRT
  CONNECT_SIGNAL_TO_RESCUE( SIGABRT )
 #endif
 #ifdef SIGIOT
  CONNECT_SIGNAL_TO_RESCUE( SIGIOT )
 #endif
 #ifdef SIGTRAP
  CONNECT_SIGNAL_TO_RESCUE( SIGTRAP )
 #endif
 #ifdef SIGEMT
  CONNECT_SIGNAL_TO_RESCUE( SIGEMT )
 #endif
 #ifdef SIGSYS
  CONNECT_SIGNAL_TO_RESCUE( SIGSYS )
 #endif

 /* Termination Signals
  * These signals are all used to tell a process to terminate, in one way
  * or another.
  * The (obvious) default action for all of these signals is to cause the
  * process to terminate.
  */
 #ifdef SIGTERM
  CONNECT_SIGNAL_TO_BAILOUT( SIGTERM )
 #endif
 #ifdef SIGINT
  CONNECT_SIGNAL_TO_BAILOUT( SIGINT )
 #endif
 #ifdef SIGQUIT
  CONNECT_SIGNAL_TO_BAILOUT( SIGQUIT )
 #endif
 #ifdef SIGKILL
  /* SIGKILL can not be caught or ignored */
 #endif
 #ifdef SIGHUP
  CONNECT_SIGNAL_TO_BAILOUT( SIGHUP )
 #endif

 /* Alarm Signals
  * These signals are used to indicate the expiration of timers.
  * The default behavior for these signals is to cause program termination.
  */
 #ifdef SIGALRM
  CONNECT_SIGNAL_TO_DFL( SIGALRM )
 #endif
 #ifdef SIGVTALRM
  CONNECT_SIGNAL_TO_DFL( SIGVTALRM )
 #endif
 #ifdef SIGPROF
  CONNECT_SIGNAL_TO_DFL( SIGPROF )
 #endif

 /* Asynchronous I/O Signals
  * These signals are used in conjunction with asynchronous I/O facilities.
  * The default action for these signals is to ignore them.
  */
 #ifdef SIGIO
  CONNECT_SIGNAL_TO_DFL( SIGIO )
 #endif
 #ifdef SIGURG
  CONNECT_SIGNAL_TO_DFL( SIGURG )
 #endif
 #ifdef SIGPOLL
  CONNECT_SIGNAL_TO_DFL( SIGPOLL )
 #endif

 /* Job Control Signals
  * These signals are used to support job control. If your system
  * doesn't support job control, then these macros are defined but
  * the signals themselves can't be raised or handled.
  */
 #ifdef SIGCHLD
  CONNECT_SIGNAL_TO_DFL( SIGCHLD )
 #endif
 #ifdef SIGCLD
  CONNECT_SIGNAL_TO_DFL( SIGCLD )
 #endif
 #ifdef SIGCONT
  CONNECT_SIGNAL_TO_DFL( SIGCONT )
 #endif
 #ifdef SIGSTOP
  /* SIGSTOP can not be caught or ignored */
 #endif
 #ifdef SIGTSTP
  CONNECT_SIGNAL_TO_DFL( SIGTSTP )
 #endif
 #ifdef SIGTTIN
  CONNECT_SIGNAL_TO_DFL( SIGTTIN )
 #endif
 #ifdef SIGTTOU
  CONNECT_SIGNAL_TO_DFL( SIGTTOU )
 #endif

 /* Operation Error Signals
  * These signals are used to report various errors generated by an
  * operation done by the program. They do not necessarily indicate a
  * programming error in the program, but an error that prevents an
  * operating system call from completing.
  * The default action for all of them is to cause the process to terminate.
  */
 #ifdef SIGPIPE
  CONNECT_SIGNAL_TO_BAILOUT( SIGPIPE )
 #endif
 #ifdef SIGLOST
  CONNECT_SIGNAL_TO_BAILOUT( SIGLOST )
 #endif
 #ifdef SIGXCPU
  CONNECT_SIGNAL_TO_BAILOUT( SIGXCPU )
 #endif
 #ifdef SIGXFSZ
  CONNECT_SIGNAL_TO_BAILOUT( SIGXFSZ )
 #endif

 /* Miscellaneous Signals These signals are used for various other purposes.
  * In general, they will not affect your program unless it explicitly uses
  * them for something.
  */
 #ifdef SIGUSR1
  CONNECT_SIGNAL_TO_DFL( SIGUSR1 )
 #endif
 #ifdef SIGUSR2
  CONNECT_SIGNAL_TO_DFL( SIGUSR2 )
 #endif
 #ifdef SIGWINCH
  CONNECT_SIGNAL_TO_DFL( SIGWINCH )
 #endif
 #ifdef SIGINFO
  CONNECT_SIGNAL_TO_DFL( SIGINFO )
 #endif

 /* Other Signals
  * Stack fault on coprocessor / Power fail
  * No information available.
  */
 #ifdef SIGSTKFLT
  CONNECT_SIGNAL_TO_DFL( SIGSTKFLT )
 #endif
 #ifdef SIGPWR
  CONNECT_SIGNAL_TO_DFL( SIGPWR )
 #endif

#undef CONNECT_SIGNAL_TO_RESCUE
#undef CONNECT_SIGNAL_TO_BAILOUT
#undef CONNECT_SIGNAL_TO_DFL
#undef CONNECT_SIGNAL_TO_IGN

 /* add all bailout signals to the mask of each rescue signal 
  * this blocks bailout signals during the rescue */
 for (i = 1; i < NSIG; i++)
    if (sigismember(&rescue_signals, i))
       {int n;
        struct sigaction rescue_action;
        sigaction(i, NULL, &rescue_action);
	for (n = 1; n < NSIG; n++)
	   if (sigismember(&bailout_signals, n))
	     sigaddset(&rescue_action.sa_mask, n);
        return_value += sigaction(i, &rescue_action, NULL);
       }

#ifdef DEBUG_RESCUE
 printf("After sg_project_rescue_init:\n");
 sigaction_info();
#endif

 if (return_value) return -1;
 else return 0;
}

static int
connect_sigaction(int signum, void (*function)(int), unsigned int flags, sigset_t mask)
{
 struct sigaction action;

 action.sa_handler = function;
 action.sa_flags = flags;
 action.sa_mask = mask;

 return sigaction(signum, &action, NULL);
}


/* bailout does the real work as an idle process, when gtk is "unbusy".
 * it only catches signals that are non-fatal, so ask whether
 * to lose the unsaved changes or to keep the application running */

static gint bailout_idle_id = 0;

static void
bailout( int signum )
{
 static int static_signum;

#ifdef DEBUG_RESCUE
 printf("Inside bailout: (%d) %s\n", signum, strsignal(signum));
 sigaction_info();
#endif
  
 /* ignore consecutive bailout signals */
 if (bailout_idle_id) return;

 if (project_changed == FALSE) bailout_idle(NULL);

 static_signum = signum;
 bailout_idle_id = gtk_idle_add(bailout_idle, &static_signum);
}

static gint
bailout_idle( gpointer data )
{
 if (data)
    {char message[80];

     gtk_idle_remove(bailout_idle_id);

     snprintf(message, 80, "%s\nExit losing unsaved changes?", strsignal( *((int*)data)) );
     if (sg_accept_dialog(message, 1) != YES_CLICKED) return (bailout_idle_id = 0);
    } 

 /* remove autosave file */ 
 sg_project_autosave_set(0);
 
 /* kill python terminal */ 
 child_died(0);

 gtk_exit(0);
}


/* rescue tries to save the current project and exits
 * memory is possibly corrupted, so rely on static storage only */
static void
rescue(int signum)
{
 static char message[250] = "";
 static int signal_count = 0;

 /* do what SA_RESETHAND and SA_NODEFER flags supposed to do here automatically
  * why are these signalflags ignored? Due to Python, Imlib, Gtk? -Rob- */
 {static struct sigaction action;
  sigaction(signum, NULL, &action);
  if (SA_RESETHAND) action.sa_handler = (action.sa_flags & SA_RESETHAND ? SIG_DFL : rescue);
  sigaction(signum, &action, NULL);

  sigemptyset(&action.sa_mask);
  sigaddset(&action.sa_mask, signum);
  if (SA_NODEFER) sigprocmask(action.sa_flags & SA_NODEFER ? SIG_UNBLOCK : SIG_BLOCK, &action.sa_mask, NULL);
 }
 
#ifdef DEBUG_RESCUE
 printf("Inside rescue: (%d) %s [signal_count = %d]\n", signum, strsignal(signum), signal_count);
 sigaction_info();
#endif

 if (signal_count++ == 0)
 {static char rescue_file[250];
  static int rescue_OK = FALSE;

  /* prepare for the worst and keep fingers crossed */
  snprintf(message, 250, "%s\n\n%s\n%s\n%s\n%s", strsignal(signum),
                                                 "\nSorry, failed to rescue the project",
                                                 "\nPlease submit a bug report to",
                                                 SG_EMAIL, "or  " SG_WWW);

  /* save project to its original location */
   snprintf(rescue_file, 250, "%s/" RESCUE_PREFIX "%s", last_project_path, last_project_filename);
   rescue_OK = sg_project_file_export_xml(rescue_file);
  if (rescue_OK == FALSE)
  {/* failed! try saving project to the current working directory */
   snprintf(rescue_file, 250, RESCUE_PREFIX "%s", last_project_filename);
   rescue_OK = sg_project_file_export_xml(rescue_file);
  }
  if (rescue_OK == FALSE && g_get_home_dir())
  {/* failed again; as a last resort save to home directory */
   snprintf(rescue_file, 250, "%s/" RESCUE_PREFIX "%s", g_get_home_dir(), last_project_filename);
   rescue_OK = sg_project_file_export_xml(rescue_file);
  }

  if (rescue_OK) /* were we lucky? */
     snprintf(message, 250, "%s\n\n%s\n%c%s%c\n%s\n%s\n%s", strsignal(signum),
                                                            "Project successfully saved to",
                                                            '"', rescue_file, '"',
                                                            "\nPlease submit a bug report to ",
                                                            SG_EMAIL, "or  " SG_WWW);
 }
 else if ( signal_count < 10 )
    return; /* ignore up to 10 consecutive rescue signals */

/* when a signal is generated externally by "kill -[SIG] [SG-pid]",
 * the following dialog-call will cause a stream of GLib warnings:
 *    g_main_iterate(): main loop already active in another thread
 * don't know what to do about that -Rob- */
 sg_message_dialog(message, 0);

 /* kill python terminal */
 child_died(0);

 /* continue with the default handler and exit accordingly */
 signal(signum, SIG_DFL);
 raise(signum);
}

/* ----------------------------------------------------- */
/* -------  Facilities for signal-debugging only ------- */
/* ----------------------------------------------------- */
#ifdef DEBUG_RESCUE

#ifndef SA_SIGINFO
  #define SA_SIGINFO 0
#endif

static const struct {
      unsigned int value;
      char name[10];
      } flag[]={
              #ifdef SA_ONSTACK
                {SA_ONSTACK,   "  ONSTACK"},
              #endif
              #ifdef SA_RESTART
                {SA_RESTART,   "  RESTART"},
              #endif
              #ifdef SA_NODEFER
                {SA_NODEFER,   "  NODEFER"},
              #endif
              #ifdef SA_RESETHAND
                {SA_RESETHAND, "RESETHAND"},
              #endif
              #ifdef SA_NOCLDSTOP
                {SA_NOCLDSTOP, "NOCLDSTOP"},
              #endif
              #ifdef SA_NOCLDWAIT
                {SA_NOCLDWAIT, "NOCLDWAIT"},
              #endif
	      #ifdef SA_WAITSIG
	        {SA_WAITSIG,   "  WAITSIG"},
	      #endif
              #ifdef SA_SIGINFO
                {SA_SIGINFO,   "  SIGINFO"},
              #endif
	       };

static const int number_of_flags = sizeof(flag)/sizeof(flag[0]);

static char*
strmask(sigset_t mask, char *result)
{
 int i;
 for (result[0]= '\0', i = 1; i < NSIG; i++)
     {if ((i-1)%10==0 && i!=1) strcat(result, ".");
      strcat(result, (sigismember(&mask, i) ? "1" : "0"));
     }
 return result;
}
    
/* sigaction_info prints to standard out the complete sigaction
 * information table: flags - mask - handler - signal number/string */
static void
sigaction_info(void)
{
 int n, m;
 char mask[NSIG + NSIG/10];
 struct sigaction action;

 /* print the flag descriptions vertically */
 for (printf("\n"), n = 0; n < strlen(flag[0].name); n++, printf("\n"))
    for (m = 0; m < number_of_flags; m++)
       printf(" %c", flag[m].name[n]);

 /* print the sigaction information for signal number 1 to NSIG */
 for (n = 1; n < NSIG; n++)
     {char flags[1 + 2*number_of_flags],
           handler[10];

      /* print a separator line every 10 signal lines */
      if ((n-1)%10 == 0)
         {char c = (n == 1 ? '-' : '+');
	  for (m = 0; m < 2*number_of_flags; m++) printf("-");
	  for (printf("-%c-", c), m = 0; m < NSIG + NSIG/10; m++) printf("-");
	  printf("%c-----------%c------\n", c, c);
	 }
	  
      sigaction(n, NULL, &action);

      /* convert the flags information into a 0/1 string set */
      for (flags[0] = '\0', m = 0; m < number_of_flags; m++)
         if (flag[m].value) strcat(flags, (action.sa_flags & flag[m].value ? " 1" : " 0") );
	 else strcat(flags, " X");

      /* convert the handler information into readable form */
      if (action.sa_flags & SA_SIGINFO)
         {if (action.sa_sigaction == NULL) sprintf(handler, "NULL     ");
          else sprintf(handler, "%p", action.sa_sigaction);
         }
      else
         {if (action.sa_handler == SIG_DFL)      sprintf(handler, "SIG_DFL  ");
          else if (action.sa_handler == SIG_IGN) sprintf(handler, "SIG_IGN  ");
          else if (action.sa_handler == bailout) sprintf(handler, "bailout  ");
          else if (action.sa_handler == rescue)  sprintf(handler, "rescue   ");
          else if (action.sa_handler == NULL)    sprintf(handler, "NULL     ");
          else sprintf(handler, "%p", action.sa_handler);
         }

      /* print the sigaction information as a single line */	 
      printf("%s | %s | %s | (%2d) %s\n", flags, strmask(action.sa_mask, mask),
	 				  handler, n, strsignal(n));
     }

 sigprocmask(0, NULL, &action.sa_mask);
 for (n = 0; n < 2*number_of_flags - strlen("sigprocmask"); n++) printf(" ");
 printf("sigprocmask = %s\n\n\n", strmask(action.sa_mask, mask));
}
#endif /* DEBUG_RESCUE */


syntax highlighted by Code2HTML, v. 0.9.1