/* SciGraphica - Scientific graphics and data manipulation * Copyright (C) 2001 Adrian E. Feiguin * * 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 #include #include #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 */