/* signals.c: signal handling */ /* 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" volatile int command_is_dead = FALSE; int commands_exit_status = 0; int filter_is_dead = FALSE; int filters_exit_status = 0; int deferred_adapt_commands_window_size = FALSE; /* whether we have to adapt clients winsize when accepting a line */ int signal_handlers_were_installed = FALSE; int received_sigALRM = FALSE; static void change_signalmask(int, int *); static void child_died(int); static void pass_on_signal(int); static void handle_sigTSTP(int); int adapt_tty_winsize(int, int); static void wipe_textarea(struct winsize *old_size); static int signals_program_error(int signal); static int signals_to_be_passed_on[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGCONT, SIGUSR1, SIGUSR2, SIGWINCH, 0 }; #ifndef MAX_SIG /* is this POSIX? couldn't find it, so: */ #define MAX_SIG 100 #endif #ifdef DEBUG static void log_named_signal(int); #else static void handle_program_error_signal(int); #endif int sigterm_received = FALSE; void mysignal(int sig, sighandler_type handler, const char *handler_name) { #ifdef HAVE_SIGACTION struct sigaction action; if (handler == SIG_DFL) DPRINTF2(DEBUG_SIGNALS,"Re-setting handler for signal %d (%s) to its default", sig, signal_name(sig)); else if (handler == SIG_IGN) DPRINTF2(DEBUG_SIGNALS,"Ignoring signal %d (%s)", sig, signal_name(sig)); else DPRINTF3(DEBUG_SIGNALS,"Setting handler for signal %d (%s) to %s()", sig, signal_name(sig), handler_name); action.sa_handler = handler; sigfillset(&action.sa_mask); /* don't bother making our signal handlers re-entrant (they aren't) */ action.sa_flags = (sig == SIGCHLD ? SA_NOCLDSTOP : 0); /* no SA_RESTART */ if (sigaction(sig, &action, NULL) != 0) # else /* rlwrap running in Ye Olde Computer Museum?? */ if (signal(sig, handler) == SIG_ERR) # endif if(handler != SIG_DFL) /* allow e.g. KILL to be set to its default */ myerror(FATAL|USE_ERRNO, "Failed setting handler for signal %d (%s)", sig, signal_name(sig)); } void install_signal_handlers() { int i; mysignal(SIGCHLD, HANDLER(child_died)); mysignal(SIGTSTP, HANDLER(handle_sigTSTP)); #ifndef DEBUG /* we want core dumps when debugging, no polite excuses! */ for (i = 1; i %d, ws.row: %d -> %d", old_winsize.ws_col, winsize.ws_col, old_winsize.ws_row, winsize.ws_row); if (always_readline &&!dont_wrap_command_waits()) /* if --always_readline option is set, the client will probably spew a */ deferred_adapt_commands_window_size = TRUE; /* volley of control chars at us when its terminal is resized. Hence we don't do it now */ else { ret = ioctl(to_fd, TIOCSWINSZ, &winsize); DPRINTF1(DEBUG_SIGNALS, "ioctl (..., TIOCSWINSZ) = %d", ret); } DPRINTF2(DEBUG_READLINE, "rl_prompt: %s, line_buffer: %s", mangle_string_for_debug_log(rl_prompt, 100), rl_line_buffer); rl_set_screen_size(winsize.ws_row, winsize.ws_col); /* this is safe: we know that right now rlwrap is not calling the readline library because we keep SIGWINCH blocked all the time */ if (!within_line_edit && !skip_rlwrap()) { wipe_textarea(&old_winsize); received_WINCH = TRUE; /* we can't start line edit in signal handler, so we only set a flag */ } else if (within_line_edit) { /* try to keep displayed line tidy */ wipe_textarea(&old_winsize); rl_on_new_line(); rl_redisplay(); } return (!always_readline || dont_wrap_command_waits()); /* pass the signal on (except when always_readline is set and command is not waiting) */ } else { /* window size has not changed */ return FALSE; } } /* After a resize, clear all the lines that were occupied by prompt + line buffer before the resize */ static void wipe_textarea(struct winsize *old_winsize) { int point, lineheight, linelength, cursor_height, i, promptlength; if (!prompt_is_single_line()) { /* Don't need to do anything in horizontal_scroll_mode */ promptlength = colourless_strlen((saved_rl_state.cooked_prompt ? saved_rl_state.cooked_prompt: saved_rl_state.raw_prompt), NULL, old_winsize -> ws_col); linelength = (within_line_edit ? strlen(rl_line_buffer) : 0) + promptlength; point = (within_line_edit ? rl_point : 0) + promptlength; assert(old_winsize -> ws_col > 0); lineheight = (linelength == 0 ? 0 : (1 + (max(point, (linelength - 1)) / old_winsize -> ws_col))); if (lineheight > 1 && term_cursor_up != NULL && term_cursor_down != NULL) { /* i.e. if we have multiple rows displayed, and we can clean them up first */ cr(); cursor_height = point / old_winsize -> ws_col; /* cursor is still on old line */ DPRINTF2(DEBUG_SIGNALS, "lineheight: %d, cursor_height: %d", lineheight, cursor_height); for (i = 1 + cursor_height; i < lineheight; i++) curs_down(); /* ...move it down to last line */ for (i = lineheight; i > 1; i--) { /* go up again, erasing every line */ clear_line(); curs_up(); } } clear_line(); cr(); } } static void child_died(int UNUSED(signo)) { int saved_errno; DEBUG_RANDOM_SLEEP; zero_select_timeout(); saved_errno = errno; DPRINTF0(DEBUG_SIGNALS, "Caught SIGCHLD"); if(command_pid && waitpid(command_pid, &commands_exit_status, WNOHANG)) { DPRINTF2(DEBUG_SIGNALS, "child (pid %d) has died, exit status: %x", command_pid, commands_exit_status); command_is_dead = TRUE; command_pid = 0; /* thus we know that there is no child anymore to pass signals to */ } else if (filter_pid && waitpid(filter_pid, &filters_exit_status, WNOHANG)) { DPRINTF2(DEBUG_SIGNALS, "filter (pid %d) has died, exit status: %x", filter_pid, filters_exit_status); filter_is_dead = TRUE; filter_pid = 0; } else { DPRINTF0(DEBUG_ALL, "Whoa, got a SIGCHLD, but not from slave command or filter! I must have children I don't know about (blush...)!"); /* ignore */ } errno = saved_errno; return; /* allow remaining output from child to be processed in main loop */ /* (so that we will see childs good-bye talk) */ /* this will then clean up and terminate */ } #ifdef DEBUG static void log_named_signal(int signo) { if (debug) DPRINTF1(DEBUG_SIGNALS, "got %s", signal_name(signo)); } #else static void handle_program_error_signal(int sig) { /* Even after sudden and unexpected death, leave the terminal in a tidy state */ int res; printf("\n%s: Oops, crashed (caught %s) - this should not have happened!\n" "If you need a core dump, re-configure with --enable-debug and rebuild\n" "Resetting terminal and cleaning up...\n", program_name, signal_name(sig)); if (colour_the_prompt || filter_pid) res = write(STDOUT_FILENO,"\033[0m",4); /* reset terminal colours */ if (terminal_settings_saved) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal_settings); exit(EXIT_FAILURE); } #endif static int coredump(int status) { #ifdef WCOREDUMP return WCOREDUMP(status); #else return 0; #endif } void suicide_by(int signal, int status) { /* Some signals suggest a program error. When rlwrap kills itself with one of those, the shell may tell the user that rlwrap itself has failed. Make clear that it didn't. @@@ We could also try argv[0] = command_name just before dying ? */ if (signals_program_error(signal)) { myerror(WARNING|NOERRNO, "%s crashed, killed by %s%s.\n%s itself has not crashed, but for transparency,\n" "it will now kill itself %swith the same signal\n", command_name, signal_name(signal), (coredump(status) ? " (core dumped)" : ""), program_name, (coredump(status) ? "" : "(without dumping core) ") ); } uninstall_signal_handlers(); unblock_all_signals(); set_ulimit(RLIMIT_CORE,0); /* prevent our own useless core dump from clobbering the interesting one created by command */ DPRINTF1(DEBUG_SIGNALS, "suicide by signal %s", signal_name(signal)); kill(getpid(), signal); /* still alive? */ sleep(1); exit(0); } static int myalarm_was_set = FALSE; /* drop-in replacement for alarm (*but* with arg in msecs, not secs). Also sets global flag myalarm_was_set */ void myalarm(int msecs) { #ifdef HAVE_SETITIMER int retval; struct itimerval awhile = {{0,0},{0,0}}; int secs = msecs/1000; awhile.it_value.tv_sec = secs; awhile.it_value.tv_usec = (msecs - secs * 1000) * 1000; received_sigALRM = FALSE; retval = setitimer(ITIMER_REAL, &awhile, NULL); DPRINTF3(DEBUG_AD_HOC, "setitimer() = %d (tv_sec = %d, tv_usec=%ld)", retval, secs, awhile.it_value.tv_usec); #else received_sigALRM = FALSE; alarm(msecs == 0 ? 0 : 1 + msecs/1000)); #endif DPRINTF1(DEBUG_AD_HOC, "set alarm (%d msecs)", msecs); if (msecs == 0) return; myalarm_was_set = TRUE; } void handle_sigALRM(int UNUSED(signo)) { received_sigALRM = TRUE; assert(myalarm_was_set); /* cry wolf if sigALRM is caught when none was requested by myalarm */ myalarm_was_set= FALSE; DPRINTF0(DEBUG_SIGNALS, "got sigALRM"); } /* Give name of signal. A case() construct is not appropriate here as on some architectures signal values may coincide */ char *signal_name(int signal) { return signal == SIGHUP ? "SIGHUP" : signal == SIGINT ? "SIGINT" : signal == SIGQUIT ? "SIGQUIT" : signal == SIGILL ? "SIGILL" : signal == SIGABRT ? "SIGABRT" : signal == SIGTRAP ? "SIGTRAP" : #ifdef SIGIOT /* 4.2 BSD (IOT trap ) */ signal == SIGIOT ? "SIGIOT" : #endif #ifdef SIGEMT /* 4.2 BSD (EMT trap ) */ signal == SIGEMT ? "SIGEMT" : #endif signal == SIGFPE ? "SIGFPE" : signal == SIGKILL ? "SIGKILL" : #ifdef SIGBUS /* 4.2 BSD (Bus error ) */ signal == SIGBUS ? "SIGBUS" : #endif signal == SIGSEGV ? "SIGSEGV" : #ifdef SIGSYS /* 4.2 BSD (Bad argument to system call ) */ signal == SIGSYS ? "SIGSYS" : #endif signal == SIGPIPE ? "SIGPIPE" : signal == SIGALRM ? "SIGALRM" : signal == SIGTERM ? "SIGTERM" : signal == SIGUSR1 ? "SIGUSR1" : signal == SIGUSR2 ? "SIGUSR2" : signal == SIGCHLD ? "SIGCHLD" : signal == SIGSTOP ? "SIGSTOP" : signal == SIGTSTP ? "SIGTSTP" : signal == SIGCONT ? "SIGCONT" : #ifdef SIGCLD /* System V (Same as SIGCHLD ) */ signal == SIGCLD ? "SIGCLD" : #endif #ifdef SIGPWR /* System V (Power failure restart ) */ signal == SIGPWR ? "SIGPWR" : #endif signal == SIGXCPU ? "SIGXCPU" : signal == SIGXFSZ ? "SIGXFSZ" : signal == SIGWINCH ? "SIGWINCH" : /* non-POSIX, but present on most systems */ as_string(signal); } static int signals_program_error(int signal) { return signal == SIGILL || signal == SIGABRT || signal == SIGTRAP || #ifdef SIGIOT /* 4.2 BSD (IOT trap ) */ signal == SIGIOT || #endif #ifdef SIGEMT /* 4.2 BSD (EMT trap ) */ signal == SIGEMT || #endif signal == SIGFPE || #ifdef SIGBUS /* 4.2 BSD (Bus error ) */ signal == SIGBUS || #endif signal == SIGSEGV || #ifdef SIGSYS /* 4.2 BSD (Bad argument to system call ) */ signal == SIGSYS || #endif signal == SIGXCPU || signal == SIGXFSZ || FALSE ? TRUE : FALSE; }