/* File: sigsubs.c * Author: Fred Wobus (fw@sanger.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1999 * ------------------------------------------------------------------- * Acedb 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (Sanger Centre, UK) rd@sanger.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@crbm.cnrs-mop.fr * * Description: signal catching, initialisation etc. * currently not tested for Windows or Mac * Exported functions: * signalCatchInit() * HISTORY: * Last edited: Sep 10 09:47 2002 (edgrif) * Created: Fri Apr 30 13:24:42 1999 (fw) *------------------------------------------------------------------- */ #if defined(WIN32) || defined(MACINTOSH) /* Just a trivial stub, who knows if these systems actually do signals... */ void signalCatchInit (BOOL initCtrlC) { return ; } #else /* i.e. UNIX */ /************************************************************/ #include #include #if defined(ALPHA) || defined(LINUX) || defined(SOLARIS) #include /* for user stack size check. */ #endif #include #include #include /************************************************************/ static void signalSet(SignalHandler signal_handler, BOOL initCtrlC, char *fail_msg) ; static void signalHandler(int sig) ; static char *getSignalText(int sig_num) ; /************************************************************/ /* By default signal catching is on, we need to remember whether its on or */ /* off for menu purposes. */ static BOOL sig_catching_G = TRUE ; static char *panicBuffer = 0; /************************************************************/ /* Initialise signal handling, set up the signal mask and the handler. */ /* For windowing apps (e.g. xace) we don't want Cntl-C set up here, we */ /* handle it elsewhere via the windowing code. */ /* */ void signalCatchInit(BOOL initCtrlC, BOOL no_catch) { panicBuffer = malloc(5*1024); /* N.b. we don't need to do anything if signals are not going to be caught.*/ if (no_catch) sig_catching_G = FALSE ; else { signalSet(signalHandler, initCtrlC, "Signal handler could not be turned on") ; sig_catching_G = TRUE ; } return; } /* signalCatchInit */ /************************************************************/ /* Simply return current status of signal catching. */ BOOL signalCatchStatus(void) { return sig_catching_G ; } /************************************************************/ /* Turn on all signal catching, read/write locks will be cleared up for most */ /* signals. */ /* */ void signalCatchOn(BOOL initCtrlC) { signalSet(signalHandler, initCtrlC, "Signal handler could not be turned off") ; sig_catching_G = TRUE ; return; } /************************************************************/ /* Turn off all signal catching, the time we need this is if we have a */ /* persistent problem such as seg faulting and need a dump. */ /* NOTE that once signals are turned off then if the program exits, the */ /* readlocks will not be cleared up. */ /* */ void signalCatchOff(BOOL initCtrlC) { signalSet(SIG_DFL, initCtrlC, "Signal handler could not be turned off") ; sig_catching_G = FALSE ; return; } /************************************************************/ /* Set signal handling on/off, if signal_handler == SIG_DFL then signal */ /* handling will in effect be turned off by returning it to the default. */ /* */ /* NOTE: */ /* There are some signals that its good to try and clean up after such as */ /* SIGTERM, in our signal handler we try to shut down the database */ /* correctly. */ /* */ /* There are other signals after which NO CLEAN UP should be attempted and */ /* we should NOT try to catch, the classic example is SIGABRT. If we get */ /* sent SIGABRT, it is because the user WANTS a core dump of where the */ /* process is NOW...not where it is after we try to clean up !!!!!!! */ /* */ static void signalSet(SignalHandler signal_handler, BOOL initCtrlC, char *fail_msg) { struct sigaction sa; signalInitSigAction(&sa, signal_handler) ; /* signals that cause the program to terminate gracefully */ if (sigaction(SIGHUP, &sa, 0) != 0 || /* hangup, generated when terminal disconnects */ sigaction(SIGTERM, &sa, 0) != 0 || /* software termination signal */ sigaction(SIGBUS, &sa, 0) != 0 || /* (*) bus error (specification exception) */ sigaction(SIGFPE, &sa, 0) != 0 || /* (*) floating point exception */ sigaction(SIGSEGV, &sa, 0) != 0 || /* (*) segmentation violation */ /* interrupt signals */ sigaction(SIGQUIT, &sa, 0) != 0) /* Ctrl-\ */ { messerror ("%s (%s)", fail_msg, messSysErrorText()); } /* do this seperately as graphical applications deal with Ctrl-C * in a special way */ if (initCtrlC) if (sigaction(SIGINT, &sa, 0) != 0) /* Ctrl-C */ messerror ("%s (%s)", fail_msg, messSysErrorText()); return; } /* signalCatchInit */ /************************************************************/ /* Our signal handling function that attempts to clean up after a signal. */ /* */ static void signalHandler (int sig) { struct messContextStruct nullContext = { NULL, NULL }; if (sig == SIGINT) /* usually caused by Ctrl-C */ { BOOL result = TRUE; /* I don't know why Fred did all this stuff with the terminal, it */ /* actually makes the interface between this code and for instance */ /* the tace prompt code a bit yuch. Things don't behave as you might */ /* expect. I have improved it a bit, but I'm not sure why we couldn't */ /* just use readline here, or perhaps fgets() and fputs() */ /* sometime I'll try them. */ if (isatty(fileno(stdin)) > 0) /* makes sure stdin that isatty() */ { struct termios term, copy; int c; if (tcgetattr(fileno(stdin), &term) < 0) messcrash("signalHandler() - can't get STDIN attributes"); /* set terminal mode to non-canonical - this means * "read requests are satisfied directly from the input queue. * A read does not return until at least MIN bytes have been * received or the timeout value TIME has expired between bytes." * from "Advanced Programming in the UNIX Environment" * by W. Richard Stevens (c) 1993, page 339 */ copy = term; /* switch to raw tty mode */ copy.c_lflag &= ~(ICANON | IEXTEN | ISIG | NOFLSH); /* canonical mode off, * extended input processing off, * signal chars off */ copy.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /* no SIGINT on BREAK, * CR-to-NL off, * input parity check off, * don't strip 8th bit on input * output flow control off */ copy.c_cflag &= ~(CSIZE | PARENB); /* clear size bits, * parity checking off */ copy.c_cflag |= CS8; /* set 8 bits per char */ #if defined(DARWIN) copy.c_oflag &= ~(OPOST); #else copy.c_oflag &= ~(OPOST| ONOCR); #endif /* output processing off, * so we don't get a RETURN char after * every input, which keeps the STDIN * input stream free */ copy.c_cc[VMIN] = 1; copy.c_cc[VTIME] = 0; if (tcsetattr(fileno(stdin), TCSAFLUSH, ©) < 0) messcrash("signalHandler() - can't set STDIN " "attributes to raw tty-mode"); fprintf (stdout, "Do you want to exit the program? (y or n) "); fflush (stdout); /* read(fileno(stdin), &c, 1);*/ c = getc(stdin); if ((char)c == 'y' || (char)c == 'Y') result = TRUE; else result = FALSE; /* set terminal mode back to what it was */ if (tcsetattr(fileno(stdin), TCSAFLUSH, &term) < 0) messcrash("signalHandler() - can't set STDIN " "attributes back to original mode"); clearerr (stdin); /* clear EOF on stdin */ #ifdef ED_G_NEVER_INCLUDE_THIS_CODE /* I think this screws up the terminal: if the user now */ /* types Cntl-C again then this doesn't work as it should, we */ /* don't see the 'y', that's for sure, probably because there is */ /* a spare ' ' lying around. */ ungetc (' ', stdin); #endif /* ED_G_NEVER_INCLUDE_THIS_CODE */ /* There is a problem here, we need to emit a newline otherwise */ /* subsequent error messages etc. get mangled with the above exit */ /* message. But note that if the user entered 'n', then they still */ /* have to press "return" before they get the acedb prompt back, */ /* I'm not really sure why... */ if (result) { fprintf (stdout, "\n") ; fflush (stdout); } } if (result) messExit ("User initiated abort"); /* else - just continue program execution */ } else if (sig == SIGQUIT) /* caused by Ctrl-\ */ { messExitRegister(nullContext) ; messExit("User initiated abort") ; } else if (sig == SIGTERM) /* caused by kill command */ { ServerSessionCB serverCBs = NULL ; if ((serverCBs = sessionGetServerCB()) != NULL) { serverCBs->sigterm.funcs.func(serverCBs->sigterm.data) ; } else { messExitRegister(nullContext) ; messExit("Program killed...") ; } } else if (sig == SIGHUP) /* hang up */ { ServerSessionCB serverCBs = NULL ; if ((serverCBs = sessionGetServerCB()) != NULL) { serverCBs->sighup.funcs.func(serverCBs->sighup.data) ; } else { messExitRegister(nullContext); messExit(messprintf("ABORT: Signal %d caught", sig)); } } else /* other, more serious, signals that we catch */ { char *user; char *signal_text = NULL ; /* I don't understand why we don't just try to do the normal clean up */ /* by calling messcrash, the below just confuses things by having yet */ /* another clean up routine. This is silly.... */ if (panicBuffer) /* try to reclaim some memory * for the cleanup procedure */ { free (panicBuffer); panicBuffer = 0; } writeAccessChangeUnRegister(); /* don't do anything fancy anymore */ sessionReleaseWriteAccess(); readlockDeleteFile (); user = getLogin(TRUE) ; signal_text = getSignalText(sig) ; messdump ("FATAL ERROR, program received signal %d, \"%s\" - %s %s %s", sig, signal_text, aceGetVersionString(), aceGetLinkDateString(), user); fprintf (stderr, "\n// ABORT : Fatal error, program received signal %d, \"%s\", " "from operating system.\n" "//\n", sig, signal_text) ; #if defined(RLIMIT_DATA) /* DEC at least delivers a SIGSEGV if stack space is exhausted, but */ /* other systems may use a different one, so execute for all signals. */ { struct rlimit limit ; enum {MIN_STACK_SIZE = 1572864} ; /* Warning generally has proved unhelpful, so I'm only doing it for */ /* really low stack limits from now on... */ /* We look at RLIMIT_STACK to see if users stack size can be raised, */ /* and warn them. */ /* To be really fancy we could call getrusage to look at actual */ /* stack usage, but this might not be portable since some signal */ /* handlers are not placed on the normal stack. */ if (getrlimit(RLIMIT_STACK, &limit) != 0) { fprintf(stderr, "\n// Fatal error in %s at line %d:\n" "// call to getrlimit() failed.", __FILE__, __LINE__) ; exit(EXIT_FAILURE) ; } else { if (limit.rlim_cur < MIN_STACK_SIZE) { fprintf(stderr, "// A possible cause of the error is that your stack size" " (currently %lu bytes)\n" "// is very low, you could raise it to %lu bytes%s using\n" "// the limit/unlimit or ulimit shell commands and then rerun %s.\n" "//\n", limit.rlim_cur, limit.rlim_max, limit.rlim_max == RLIM_INFINITY ? " (max. possible)" : "", messGetErrorProgram()) ; /* This fprintf() and the next one are alternative starts to */ /* the beginning of the main fprintf() below. */ fprintf(stderr, "// If this fails, then this ") ; } else fprintf(stderr, "// This ") ; } } #else fprintf(stderr, "// This ") ; #endif /* continues from last fprintfs in the section above. */ fprintf (stderr, "problem _may_ be caused by an error in the code and\n" "// the Acedb developers would like to know about it.\n" "//\n" "// Please email as full report as possible to acedb@sanger.ac.uk\n" "//\n" "// Try to include a description of your database and\n" "// details of the actions you were trying to perform\n" "// when this error occured. \n" "// Details on the input data that was used and \n" "// information about the operating system of your computer\n" "// may also help to analyse this problem.\n" "//\n"); #ifdef ED_G_NEVER_INCLUDE_THIS_CODE /* Theres a glib function to print a stack trace but its not that good,*/ /* function just hangs after printing the stack trace or if */ /* it can't fork gdb, it would need to be recoded to use sigalrm and */ /* maybe sigchld so that it exitted rather then hanging as it does now.*/ /* Not worth the hassle currently. */ fprintf (stderr, "// The program will now try to print a record of which routine failed.\n" "// If should stop with a line that reads:\n" "// \" in main (argc=2, argv=0x11fffd728) \"\n") : g_on_error_stack_trace(messGetErrorProgram()) ; #endif /* ED_G_NEVER_INCLUDE_THIS_CODE */ /* We used to call abort() here to try to get a core file but this */ /* simply doesn't work on many systems as the signal handling is on a */ /* separate stack and this makes the core useless. Better is to get */ /* tell the user to turn off signal handling and repeat the problem */ /* to get a pucker dump. */ fprintf (stderr, "// If you can, restart the program using the \"-nosigcatch\" option,\n" "// e.g. \"tace -nosigcatch \", and reproduce the problem.\n" "// This will produce a \"core\" file in your current directory,\n" "// please keep it as the developers can use it for debugging.\n" "//\n\n"); exit(EXIT_FAILURE) ; } return; } /* signalHandler */ /* Getting hold of the text corresponding to a signal number is highly non- */ /* portable sadly. It shouldn't be, but it is. */ /* */ /* IT'S A SAFE BET THAT THIS WILL FAIL TO COMPILE ON MANY SYSTEMS, ADD CODE */ /* AS REQUIRED. */ /* */ /* (note that some linux systems provide a routine (strsignal) to do this, */ /* when this routine becomes more widespread, it could simply replace this */ /* routine completely. */ /* */ static char *getSignalText(int sig_num) { #if defined(__CYGWIN__) || defined(HP) || defined(IBM) return "unknown"; #else char *sig_text = NULL ; #ifdef ALPHA char **signal_textlist = &(__sys_siglist[0]) ; int signal_max = __sys_nsig ; #elif defined(DARWIN) char **signal_textlist = &(sys_siglist[0]) ; int signal_max = NSIG ; #else char **signal_textlist = &(_sys_siglist[0]) ; #if defined(LINUX) || defined(HP) int signal_max = _NSIG ; #else int signal_max = _sys_nsig ; #endif #endif if (sig_num > signal_max) { fprintf(stderr, "\n// Fatal internal programming error in %s at line %d:\n" "// getSignalText() passed illegal signal number: %d (max sig = %d).\n" "// Program will now exit immediately.\n", __FILE__, __LINE__, sig_num, signal_max) ; exit(EXIT_FAILURE) ; } sig_text = signal_textlist[sig_num] ; return sig_text ; #endif /* !CYGWIN */ } /************************************************************/ /* Some small signal utilities. */ /* Initialise a sigaction structure with a signal handler and an empty mask. */ /* */ void signalInitSigAction(struct sigaction *sigact, SignalHandler sig_func) { sigact->sa_handler = sig_func ; sigemptyset(&(sigact->sa_mask)) ; sigact->sa_flags = 0 ; return ; } /************************************************************/ /* Disable signal catching by our signal handler for a specific signal, you */ /* might want to do this prior to issuing an abort() in a clean up routine */ /* to avoid recursing. */ /* N.B. even thought the sigNNN calls made here can only fail with EINVAL */ /* implying this routine is in error, we don't crash, this is because we */ /* could be called from a clean up routine which was called because there */ /* has already been been a crash. */ /* */ BOOL signalSetNoCatch(int sig) { BOOL result = TRUE ; struct sigaction sigact ; /* Create a new signal set with all signals set to the default action, */ /* this will ensure we do not catch the signal. */ signalInitSigAction(&sigact, SIG_DFL) ; /* Add the callers signal to the set of signals blocked while processing */ /* the callers signal. (I DON'T THINK WE NEED TO DO THIS...) */ if (result && sigaddset(&(sigact.sa_mask), sig) < 0) result = FALSE ; /* Install the action for the callers signal, this will mean that the */ /* operating system will carry out the default action for this signal, */ /* the application will not catch it. */ if (result && sigaction(sig, &sigact, NULL) < 0) result = FALSE ; return result ; } #endif /* Unix */