/************************************************************************* * TinyFugue - programmable mud client * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys * * TinyFugue (aka "tf") is protected under the terms of the GNU * General Public License. See the file "COPYING" for details. ************************************************************************/ static const char RCSid[] = "$Id: signals.c,v 35004.70 2007/01/14 19:28:36 kkeys Exp $"; /* Signal handling, core dumps, job control, and interactive shells */ #include "tfconfig.h" #include <signal.h> #include <setjmp.h> #include "port.h" #if DISABLE_CORE # include <sys/time.h> # include <sys/resource.h> #endif #include <sys/stat.h> /* for debugger_dump() */ #include "tf.h" #include "util.h" #include "pattern.h" /* for tfio.h */ #include "search.h" /* for tfio.h */ #include "tfio.h" #include "world.h" /* for process.h */ #include "process.h" #include "tty.h" #include "output.h" #include "signals.h" #include "variable.h" #include "expand.h" /* current_command */ #ifdef TF_AIX_DECLS struct rusage *dummy_struct_rusage; union wait *dummy_union_wait; #endif /* POSIX.1 systems should define WIFEXITED and WEXITSTATUS, taking an |int| * parameter, in <sys/wait.h>. For posix systems, we use them. For non-posix * systems, we use our own. For systems which falsely claim to be posix, * but do not define the wait macros, we use our own. We can not detect * systems which falsely claim to be posix and incorrectly define the wait * macros as taking a |union wait| parameter. The workaround for such systems * is to change "#ifdef _POSIX_VERSION" to "#if 0" below. */ #include <sys/types.h> #if HAVE_SYS_WAIT_H # include <sys/wait.h> #else # undef WIFEXITED # undef WEXITSTATUS #endif #ifdef sequent /* the wait macros are known to be broken on Dynix */ # undef WIFEXITED # undef WEXITSTATUS #endif /* These macros can take an |int| or |union wait| parameter, but the posix * macros are preferred because these require specific knowledge of the * bit layout, which may not be correct on some systems (although most * unix-like systems do use this layout). */ #ifndef WIFEXITED # define WIFEXITED(w) (((*(int *)&(w)) & 0xFF) == 0) /* works most places */ #endif #ifndef WEXITSTATUS # define WEXITSTATUS(w) (((*(int *)&(w)) >> 8) & 0xFF) /* works most places */ #endif typedef RETSIGTYPE (SigHandler)(int sig); #if !HAVE_RAISE # if HAVE_KILL # define raise(sig) kill(getpid(), sig) # endif #endif #ifdef SIGABRT # define ABORT SIGABRT #else # ifdef SIGQUIT # define ABORT SIGQUIT # else # define ABORT SIGTERM # endif #endif /* Zero out undefined signals, so we don't have to #ifdef everything later. */ #ifndef SIGHUP # define SIGHUP 0 #endif #ifndef SIGTRAP # define SIGTRAP 0 #endif #ifndef SIGABRT # define SIGABRT 0 #endif #ifndef SIGBUS /* not defined in Linux */ # define SIGBUS 0 #endif #ifndef SIGPIPE # define SIGPIPE 0 #endif #ifndef SIGUSR1 # define SIGUSR1 0 #endif #ifndef SIGUSR2 # define SIGUSR2 0 #endif #ifndef SIGTSTP # define SIGTSTP 0 #endif #ifndef SIGWINCH # define SIGWINCH 0 #endif #ifndef NSIG /* Find an upper bound of the signals we use */ # define NSIG \ ((SIGHUP | SIGINT | SIGQUIT | SIGILL | SIGTRAP | SIGABRT | SIGFPE | \ SIGBUS | SIGSEGV | SIGPIPE | SIGTERM | SIGUSR1 | SIGUSR2 | SIGTSTP | \ SIGWINCH) + 1) #endif VEC_TYPEDEF(sig_set, (NSIG-1)); const int feature_core = 1 - DISABLE_CORE; static const char *argv0 = NULL; static int have_pending_signals = 0; static sig_set pending_signals; static RETSIGTYPE (*parent_tstp_handler)(int sig); static void handle_interrupt(void); static void terminate(int sig); static void coremsg(FILE *dumpfile); static int debugger_dump(void); static FILE *get_dumpfile(void); static RETSIGTYPE core_handler(int sig); static RETSIGTYPE signal_scheduler(int sig); static RETSIGTYPE signal_jumper(int sig); #ifndef SIG_IGN static RETSIGTYPE SIG_IGN(int sig); #endif static SigHandler *old_sighup_handler; static SigHandler *setsighandler(int sig, SigHandler *func); static jmp_buf jumpenv; static int fatal_signal = 0; /* HAVE_SIGACTION doesn't mean we NEED_sigaction. On some systems that have * it, struct sigaction will not get defined unless _POSIX_SOURCE or similar * is defined, so it's best to avoid it if we don't need it. */ #ifdef SA_RESTART # define NEED_sigaction #endif #ifdef SA_ACK # define NEED_sigaction #endif static SigHandler *setsighandler(int sig, SigHandler *func) { if (!sig) return NULL; #ifndef NEED_sigaction return signal(sig, func); #else { struct sigaction act; SigHandler *oldfunc; sigaction(sig, NULL, &act); oldfunc = act.sa_handler; # ifdef SA_RESTART /* Disable system call restarting, so select() is interruptable. */ act.sa_flags &= ~SA_RESTART; # endif # ifdef SA_ACK /* Disable OS2 SA_ACK, so signals can be re-installed POSIX-style. */ act.sa_flags &= ~SA_ACK; # endif act.sa_handler = func; sigaction(sig, &act, NULL); return oldfunc; } #endif /* HAVE_SIGACTION */ } /* Returns s, unless s is NULL, accessing s would cause a SIGBUS or SIGSEGV, * or s is too long, in which case it returns another valid string describing * the problem. */ const char *checkstring(const char *s) { SigHandler *old_sigsegv_handler, *old_sigbus_handler; const char *p; if (!s) return ""; fatal_signal = 0; old_sigsegv_handler = setsighandler(SIGSEGV, signal_jumper); old_sigbus_handler = setsighandler(SIGBUS, signal_jumper); if (setjmp(jumpenv)) { if (fatal_signal == SIGSEGV) s = "(invalid string: segmentation violation)"; else if (fatal_signal == SIGBUS) s = "(invalid string: bus error)"; else s = "(invalid string)"; goto exit; } for (p = s; *p; p++) { if (p - s > 255) { s = "(invalid string: too long)"; break; } } exit: setsighandler(SIGBUS, old_sigbus_handler); setsighandler(SIGSEGV, old_sigsegv_handler); return s; } void init_signals(void) { VEC_ZERO(&pending_signals); have_pending_signals = 0; old_sighup_handler = setsighandler(SIGHUP , signal_scheduler); setsighandler(SIGINT , signal_scheduler); setsighandler(SIGQUIT , core_handler); setsighandler(SIGILL , core_handler); setsighandler(SIGTRAP , core_handler); setsighandler(SIGABRT , core_handler); setsighandler(SIGFPE , SIG_IGN); setsighandler(SIGBUS , core_handler); setsighandler(SIGSEGV , core_handler); setsighandler(SIGPIPE , SIG_IGN); setsighandler(SIGTERM , signal_scheduler); setsighandler(SIGUSR1 , signal_scheduler); setsighandler(SIGUSR2 , signal_scheduler); parent_tstp_handler = setsighandler(SIGTSTP , signal_scheduler); setsighandler(SIGWINCH, signal_scheduler); #if DISABLE_CORE { struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = 0; setrlimit(RLIMIT_CORE, &rlim); } #endif } #ifndef SIG_IGN static RETSIGTYPE SIG_IGN(int sig) { setsighandler(sig, SIG_IGN); /* restore handler (POSIX) */ } #endif static void handle_interrupt(void) { int c; VEC_CLR(SIGINT, &pending_signals); /* so status line macros in setup_screen() aren't gratuitously killed */ if (!interactive) die("Interrupt, exiting.", 0); reset_kbnum(); fix_screen(); puts("C) continue tf; X) exit; T) disable triggers; P) kill processes\r"); fflush(stdout); c = igetchar(); if (ucase(c) == 'X') die("Interrupt, exiting.", 0); if (ucase(c) == 'T') { set_var_by_id(VAR_borg, 0); oputs("% Cyborg triggers disabled."); } else if (ucase(c) == 'P') { kill_procs(); oputs("% All processes killed."); } redraw(); } int suspend(void) { #if SIGTSTP if (argv0[0] != '-' && /* not a login shell */ parent_tstp_handler == SIG_DFL) /* parent process does job-control */ { check_mail(); fix_screen(); reset_tty(); raise(SIGSTOP); cbreak_noecho_mode(); get_window_size(); redraw(); check_mail(); return 1; } #endif oputs("% Job control not available."); return 0; } static RETSIGTYPE core_handler(int sig) { FILE *dumpfile; setsighandler(sig, core_handler); /* restore handler (POSIX) */ if (sig == SIGQUIT) { if (interactive) { fix_screen(); #if DISABLE_CORE puts("SIGQUIT received. Exit? (y/n)\r"); #else puts("SIGQUIT received. Dump core and exit? (y/n)\r"); #endif fflush(stdout); if (igetchar() != 'y') { redraw(); return; } } fputs("Abnormal termination - SIGQUIT\r\n", stderr); } setsighandler(sig, SIG_DFL); if (sig != SIGQUIT) { minimal_fix_screen(); dumpfile = get_dumpfile(); coremsg(dumpfile); fprintf(stderr, "> Abnormal termination - signal %d\r\n\n", sig); if (dumpfile != stderr) fprintf(dumpfile, "> Abnormal termination - signal %d\r\n\n", sig); if (dumpfile != stderr) fclose(dumpfile); if (!debugger_dump()) { #if DISABLE_CORE fputs("Also, if you can, reinstall tf with --enable-core, " "attempt to reproduce the\r\n", stderr); fputs("error, get a stack trace and send it to the author.\r\n", stderr); #else /* cores are enabled */ fputs("Also, if you can, include a stack trace in your email.\r\n", stderr); # ifdef PLATFORM_UNIX fputs("To get a stack trace, do this:\r\n", stderr); fputs("cd src\r\n", stderr); fputs("script\r\n", stderr); fputs("gdb -q tf ;# if gdb is unavailable, use 'dbx tf' " "instead.\r\n", stderr); fputs("run\r\n", stderr); fputs("(do whatever is needed to reproduce the core dump)\r\n", stderr); fputs("where\r\n", stderr); fputs("quit\r\n", stderr); fputs("exit\r\n", stderr); fputs("\r\n", stderr); fputs("Then include the \"typescript\" file in your email.\r\n", stderr); fputs("\n", stderr); # endif /* PLATFORM_UNIX */ #endif /* DISABLE_CORE */ } } if (interactive) { close_all(); fputs("\nPress any key.\r\n", stderr); fflush(stderr); igetchar(); } reset_tty(); raise(sig); } void crash(int internal, const char *fmt, const char *file, int line, long n) { FILE *dumpfile; setsighandler(SIGQUIT, SIG_DFL); minimal_fix_screen(); reset_tty(); dumpfile = get_dumpfile(); if (internal) coremsg(dumpfile); fprintf(dumpfile, "> %s: %s, line %d\r\n", internal ? "Internal error" : "Aborting", file, line); fputs("> ", dumpfile); fprintf(dumpfile, fmt, n); fputs("\r\n\n", dumpfile); if (dumpfile != stderr) fclose(dumpfile); debugger_dump(); raise(SIGQUIT); } static char dumpname[32] = "................................"; static char exebuf[PATH_MAX+1]; static const char *initial_path = NULL; static char initial_dir[PATH_MAX+1] = "."; /* default: many users never chdir */ static void coremsg(FILE *dumpfile) { fputs("Also describe what you were doing in tf when this\r\n", stderr); fputs("occured, and whether you can repeat it.\r\n\n", stderr); fprintf(dumpfile, "> %.512s\r\n", version); if (*sysname) fprintf(dumpfile, "> %.256s\r\n", sysname); fprintf(dumpfile, "> %.256s\r\n", featurestr->data); fprintf(dumpfile,"> virtscreen=%ld, visual=%ld, expnonvis=%ld, " "emulation=%ld, lp=%ld, sub=%ld\r\n", virtscreen, visual, expnonvis, emulation, lpflag, sub); #if SOCKS fprintf(dumpfile,"> SOCKS %d\r\n", SOCKS); #endif fprintf(dumpfile,"> TERM=\"%.32s\"\r\n", TERM ? TERM : "(NULL)"); fprintf(dumpfile,"> cmd=\"%.32s\"\r\n", current_command ? current_command : "(NULL)"); if (loadfile) { fprintf(dumpfile,"> line %d-%d of file \"%.32s\"\r\n", loadstart, loadline, loadfile->name ? loadfile->name : "(NULL)"); } } void init_exename(char *name) { argv0 = name; #if HAVE_GETCWD getcwd(initial_dir, PATH_MAX); #elif HAVE_GETWD getwd(initial_dir); #endif initial_path = getenv("PATH"); } static FILE *get_dumpfile(void) { FILE *file; sprintf(dumpname, "tf.dump.%d.txt", getpid()); file = fopen(dumpname, "w"); if (!file) { fputs("\r\n\nPlease report the following message to the bug reporting " "system at http://tinyfugue.sourceforge.net/\r\n" "or by email to kenkeys@users.sourceforge.net.\r\n", stderr); return stderr; } else { fprintf(stderr, "\r\n\nDumped debugging information to file '%s'.\r\n" "Please submit this file to the bug reporting system at\r\n" "http://tinyfugue.sourceforge.net/ or by email to kenkeys@users.sourceforge.net.\r\n", dumpname); fputs("# TinyFugue debugging information\n\n", file); return file; } } #if defined(PLATFORM_UNIX) && HAVE_WAITPID static const char *test_exename(const char *template, pid_t pid) { struct stat statbuf; sprintf(exebuf, template, pid); return (stat(exebuf, &statbuf) == 0) ? exebuf : NULL; } static const char *get_exename(pid_t pid) { const char *exename; const char *dir; size_t len; struct stat statbuf; /* a /proc entry is most reliable, if one exists */ if ((exename = test_exename("/proc/%d/file", pid)) || /* *BSD */ (exename = test_exename("/proc/%d/exe", pid)) || /* Linux */ (exename = test_exename("/proc/%d/object/a.out", pid))) /* Solaris */ { return exename; } /* else use argv[0]: if it starts with "/", use it directly; else if it contains "/", it's relative to initial working dir; else, search for it in initial $PATH */ if (!argv0) { return NULL; } if (argv0[0] == '/') { return argv0; } if (strchr(argv0, '/')) { sprintf(exebuf, "%s/%s", initial_dir, argv0); return exebuf; } if (!initial_path || !*initial_path) return NULL; dir = initial_path; while (1) { len = strcspn(dir, ":\0"); if (*dir == '/') sprintf(exebuf, "%.*s/%s", len, dir, argv0); else sprintf(exebuf, "%s/%.*s/%s", initial_dir, len, dir, argv0); if (stat(exebuf, &statbuf) == 0) return exebuf; if (!dir[len]) break; dir += len + 1; } return NULL; } /* Inspired by Jeff Brown */ static int debugger_dump(void) { pid_t tf_pid = getpid(); const char *exename; if ((exename = get_exename(tf_pid))) { pid_t child_pid; child_pid = fork(); if (child_pid < 0) { /* error */ fprintf(stderr, "fork: %s\r\n", strerror(errno)); } else if (child_pid > 0) { /* parent */ pid_t wait_pid = 0; int status = 0; wait_pid = waitpid(child_pid, &status, 0); if (shell_status(status) == 0) { return 1; } else { unlink(dumpname); } } else { /* child */ char inname[1024]; char cmd[2048]; int retval; sprintf(inname, "%.1000s/tf.gdb", TFLIBDIR); sprintf(cmd, "chmod go-rwx %s; gdb -n -batch -x %s '%s' %d " ">>%s 2>&1", dumpname, inname, exename, tf_pid, dumpname); retval = system(cmd); exit(shell_status(retval) == 0 ? 0 : 1); } } return 0; } #else /* !PLATFORM_UNIX */ static int debugger_dump(void) { return 0; } #endif /* PLATFORM_UNIX */ static void terminate(int sig) { setsighandler(sig, SIG_DFL); fix_screen(); reset_tty(); fprintf(stderr, "Terminating - signal %d\r\n", sig); raise(sig); } static RETSIGTYPE signal_scheduler(int sig) { setsighandler(sig, signal_scheduler); /* restore handler (POSIX) */ VEC_SET(sig, &pending_signals); /* set flag to deal with it later */ have_pending_signals++; } static RETSIGTYPE signal_jumper(int sig) { fatal_signal = sig; longjmp(jumpenv, 1); /* don't need to restore handler */ } void process_signals(void) { if (!have_pending_signals) return; if (VEC_ISSET(SIGINT, &pending_signals)) handle_interrupt(); if (VEC_ISSET(SIGTSTP, &pending_signals)) suspend(); if (VEC_ISSET(SIGWINCH, &pending_signals)) if (!get_window_size()) operror("TIOCGWINSZ ioctl"); if (VEC_ISSET(SIGHUP, &pending_signals)) do_hook(H_SIGHUP, NULL, ""); if (VEC_ISSET(SIGTERM, &pending_signals)) do_hook(H_SIGTERM, NULL, ""); if (VEC_ISSET(SIGUSR1, &pending_signals)) do_hook(H_SIGUSR1, NULL, ""); if (VEC_ISSET(SIGUSR2, &pending_signals)) do_hook(H_SIGUSR2, NULL, ""); if (VEC_ISSET(SIGHUP, &pending_signals) && old_sighup_handler == SIG_DFL) terminate(SIGHUP); if (VEC_ISSET(SIGTERM, &pending_signals)) terminate(SIGTERM); have_pending_signals = 0; VEC_ZERO(&pending_signals); } int interrupted(void) { return VEC_ISSET(SIGINT, &pending_signals); } int shell_status(int result) { /* If the next line causes errors like "request for member `w_S' in * something not a structure or union", then <sys/wait.h> must have * defined WIFEXITED and WEXITSTATUS incorrectly (violating Posix.1). * The workaround is to not #include <sys/wait.h> at the top of this * file, so we can use our own definitions. */ return (WIFEXITED(result)) ? WEXITSTATUS(result) : -1; } int shell(const char *cmd) { int result; check_mail(); fix_screen(); reset_tty(); setsighandler(SIGTSTP, parent_tstp_handler); result = system(cmd); setsighandler(SIGTSTP, signal_scheduler); cbreak_noecho_mode(); if (result == -1) { eprintf("%s", strerror(errno)); } else if (shpause && interactive) { puts("\r\n% Press any key to continue tf.\r"); igetchar(); } get_window_size(); redraw(); if (result == -1) return result; check_mail(); #ifdef PLATFORM_OS2 return result; #else /* UNIX */ return shell_status(result); #endif }