diff options
Diffstat (limited to 'sig.c')
-rw-r--r-- | sig.c | 809 |
1 files changed, 809 insertions, 0 deletions
@@ -0,0 +1,809 @@ +/* sig.c - interface for shell signal handlers and signal initialization. */ + +/* Copyright (C) 1994-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash 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 3 of the License, or + (at your option) any later version. + + Bash 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 Bash. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include <sys/types.h> +# endif +# include <unistd.h> +#endif + +#include <stdio.h> +#include <signal.h> + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif /* JOB_CONTROL */ +#include "siglist.h" +#include "sig.h" +#include "trap.h" + +#include "builtins/common.h" +#include "builtins/builtext.h" + +#if defined (READLINE) +# include "bashline.h" +# include <readline/readline.h> +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern void initialize_siglist (); + +#if !defined (JOB_CONTROL) +extern void initialize_job_signals PARAMS((void)); +#endif + +/* Non-zero after SIGINT. */ +volatile sig_atomic_t interrupt_state = 0; + +/* Non-zero after SIGWINCH */ +volatile sig_atomic_t sigwinch_received = 0; + +/* Non-zero after SIGTERM */ +volatile sig_atomic_t sigterm_received = 0; + +/* Set to the value of any terminating signal received. */ +volatile sig_atomic_t terminating_signal = 0; + +/* The environment at the top-level R-E loop. We use this in + the case of error return. */ +procenv_t top_level; + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) +/* The signal masks that this shell runs with. */ +sigset_t top_level_mask; +#endif /* JOB_CONTROL */ + +/* When non-zero, we throw_to_top_level (). */ +int interrupt_immediately = 0; + +/* When non-zero, we call the terminating signal handler immediately. */ +int terminate_immediately = 0; + +#if defined (SIGWINCH) +static SigHandler *old_winch = (SigHandler *)SIG_DFL; +#endif + +static void initialize_shell_signals PARAMS((void)); + +void +initialize_signals (reinit) + int reinit; +{ + initialize_shell_signals (); + initialize_job_signals (); +#if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) + if (reinit == 0) + initialize_siglist (); +#endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ +} + +/* A structure describing a signal that terminates the shell if not + caught. The orig_handler member is present so children can reset + these signals back to their original handlers. */ +struct termsig { + int signum; + SigHandler *orig_handler; + int orig_flags; + int core_dump; +}; + +#define NULL_HANDLER (SigHandler *)SIG_DFL + +/* The list of signals that would terminate the shell if not caught. + We catch them, but just so that we can write the history file, + and so forth. */ +static struct termsig terminating_signals[] = { +#ifdef SIGHUP +{ SIGHUP, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGINT +{ SIGINT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGILL +{ SIGILL, NULL_HANDLER, 0, 1}, +#endif + +#ifdef SIGTRAP +{ SIGTRAP, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGIOT +{ SIGIOT, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGDANGER +{ SIGDANGER, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGEMT +{ SIGEMT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGFPE +{ SIGFPE, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGBUS +{ SIGBUS, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGSEGV +{ SIGSEGV, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGSYS +{ SIGSYS, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGPIPE +{ SIGPIPE, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGALRM +{ SIGALRM, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGTERM +{ SIGTERM, NULL_HANDLER, 0 }, +#endif + +/* These don't generate core dumps on anything but Linux, but we're doing + this just for Linux anyway. */ +#ifdef SIGXCPU +{ SIGXCPU, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGXFSZ +{ SIGXFSZ, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGVTALRM +{ SIGVTALRM, NULL_HANDLER, 0 }, +#endif + +#if 0 +#ifdef SIGPROF +{ SIGPROF, NULL_HANDLER, 0 }, +#endif +#endif + +#ifdef SIGLOST +{ SIGLOST, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR1 +{ SIGUSR1, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR2 +{ SIGUSR2, NULL_HANDLER, 0 }, +#endif +}; + +#define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig)) + +#define XSIG(x) (terminating_signals[x].signum) +#define XHANDLER(x) (terminating_signals[x].orig_handler) +#define XSAFLAGS(x) (terminating_signals[x].orig_flags) +#define XCOREDUMP(x) (terminating_signals[x].core_dump) + +static int termsigs_initialized = 0; + +/* Initialize signals that will terminate the shell to do some + unwind protection. For non-interactive shells, we only call + this when a trap is defined for EXIT (0) or when trap is run + to display signal dispositions. */ +void +initialize_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act, oact; +#endif + + if (termsigs_initialized) + return; + + /* The following code is to avoid an expensive call to + set_signal_handler () for each terminating_signals. Fortunately, + this is possible in Posix. Unfortunately, we have to call signal () + on non-Posix systems for each signal in terminating_signals. */ +#if defined (HAVE_POSIX_SIGNALS) + act.sa_handler = termsig_sighandler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + sigaddset (&act.sa_mask, XSIG (i)); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + sigaction (XSIG (i), &act, &oact); + XHANDLER(i) = oact.sa_handler; + XSAFLAGS(i) = oact.sa_flags; + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + /* XXX - should we do this for interactive shells, too? */ + if (interactive_shell == 0 && XHANDLER (i) == SIG_IGN) + { + sigaction (XSIG (i), &oact, &act); + set_signal_hard_ignored (XSIG (i)); + } +#if defined (SIGPROF) && !defined (_MINIX) + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + sigaction (XSIG (i), &oact, (struct sigaction *)NULL); +#endif /* SIGPROF && !_MINIX */ + } +#else /* !HAVE_POSIX_SIGNALS */ + + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + XHANDLER(i) = signal (XSIG (i), termsig_sighandler); + XSAFLAGS(i) = 0; + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + /* XXX - should we do this for interactive shells, too? */ + if (interactive_shell == 0 && XHANDLER (i) == SIG_IGN) + { + signal (XSIG (i), SIG_IGN); + set_signal_hard_ignored (XSIG (i)); + } +#ifdef SIGPROF + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + signal (XSIG (i), XHANDLER (i)); +#endif + } + +#endif /* !HAVE_POSIX_SIGNALS */ + + termsigs_initialized = 1; +} + +static void +initialize_shell_signals () +{ + if (interactive) + initialize_terminating_signals (); + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + /* All shells use the signal mask they inherit, and pass it along + to child processes. Children will never block SIGCHLD, though. */ + sigemptyset (&top_level_mask); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask); +# if defined (SIGCHLD) + if (sigismember (&top_level_mask, SIGCHLD)) + { + sigdelset (&top_level_mask, SIGCHLD); + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); + } +# endif +#endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */ + + /* And, some signals that are specifically ignored by the shell. */ + set_signal_handler (SIGQUIT, SIG_IGN); + + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + get_original_signal (SIGTERM); + set_signal_handler (SIGTERM, SIG_IGN); + set_sigwinch_handler (); + } +} + +void +reset_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act; +#endif + + if (termsigs_initialized == 0) + return; + +#if defined (HAVE_POSIX_SIGNALS) + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* Skip a signal if it's trapped or handled specially, because the + trap code will restore the correct value. */ + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + act.sa_handler = XHANDLER (i); + act.sa_flags = XSAFLAGS (i); + sigaction (XSIG (i), &act, (struct sigaction *) NULL); + } +#else /* !HAVE_POSIX_SIGNALS */ + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + signal (XSIG (i), XHANDLER (i)); + } +#endif /* !HAVE_POSIX_SIGNALS */ + + termsigs_initialized = 0; +} +#undef XHANDLER + +/* Run some of the cleanups that should be performed when we run + jump_to_top_level from a builtin command context. XXX - might want to + also call reset_parser here. */ +void +top_level_cleanup () +{ + /* Clean up string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (-1); + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = funcnest = 0; + executing_list = comsub_ignore_return = return_catch_flag = wait_intr_flag = 0; +} + +/* What to do when we've been interrupted, and it is safe to handle it. */ +void +throw_to_top_level () +{ + int print_newline = 0; + + if (interrupt_state) + { + if (last_command_exit_value < 128) + last_command_exit_value = 128 + SIGINT; + set_pipestatus_from_exit (last_command_exit_value); + print_newline = 1; + DELINTERRUPT; + } + + if (interrupt_state) + return; + + last_command_exit_signal = (last_command_exit_value > 128) ? + (last_command_exit_value - 128) : 0; + last_command_exit_value |= 128; + set_pipestatus_from_exit (last_command_exit_value); + + /* Run any traps set on SIGINT, mostly for interactive shells */ + if (signal_is_trapped (SIGINT) && signal_is_pending (SIGINT)) + run_interrupt_trap (1); + + /* Clean up string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (-1); + + if (running_trap > 0) + { + run_trap_cleanup (running_trap - 1); + running_trap = 0; + } + +#if defined (JOB_CONTROL) + give_terminal_to (shell_pgrp, 0); +#endif /* JOB_CONTROL */ + + /* This needs to stay because jobs.c:make_child() uses it without resetting + the signal mask. */ + restore_sigmask (); + + reset_parser (); + +#if defined (READLINE) + if (interactive) + bashline_reset (); +#endif /* READLINE */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = funcnest = 0; + executing_list = comsub_ignore_return = return_catch_flag = wait_intr_flag = 0; + + if (interactive && print_newline) + { + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + /* An interrupted `wait' command in a script does not exit the script. */ + if (interactive || (interactive_shell && !shell_initialized) || + (print_newline && signal_is_trapped (SIGINT))) + jump_to_top_level (DISCARD); + else + jump_to_top_level (EXITPROG); +} + +/* This is just here to isolate the longjmp calls. */ +void +jump_to_top_level (value) + int value; +{ + sh_longjmp (top_level, value); +} + +void +restore_sigmask () +{ +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); +#endif +} + +sighandler +termsig_sighandler (sig) + int sig; +{ + /* If we get called twice with the same signal before handling it, + terminate right away. */ + if ( +#ifdef SIGHUP + sig != SIGHUP && +#endif +#ifdef SIGINT + sig != SIGINT && +#endif +#ifdef SIGDANGER + sig != SIGDANGER && +#endif +#ifdef SIGPIPE + sig != SIGPIPE && +#endif +#ifdef SIGALRM + sig != SIGALRM && +#endif +#ifdef SIGTERM + sig != SIGTERM && +#endif +#ifdef SIGXCPU + sig != SIGXCPU && +#endif +#ifdef SIGXFSZ + sig != SIGXFSZ && +#endif +#ifdef SIGVTALRM + sig != SIGVTALRM && +#endif +#ifdef SIGLOST + sig != SIGLOST && +#endif +#ifdef SIGUSR1 + sig != SIGUSR1 && +#endif +#ifdef SIGUSR2 + sig != SIGUSR2 && +#endif + sig == terminating_signal) + terminate_immediately = 1; + + terminating_signal = sig; + + if (terminate_immediately) + { +#if defined (HISTORY) + /* XXX - will inhibit history file being written */ +# if defined (READLINE) + if (interactive_shell == 0 || interactive == 0 || (sig != SIGHUP && sig != SIGTERM) || no_line_editing || (RL_ISSTATE (RL_STATE_READCMD) == 0)) +# endif + history_lines_this_session = 0; +#endif + terminate_immediately = 0; + termsig_handler (sig); + } + +#if defined (READLINE) + /* Set the event hook so readline will call it after the signal handlers + finish executing, so if this interrupted character input we can get + quick response. If readline is active or has modified the terminal we + need to set this no matter what the signal is, though the check for + RL_STATE_TERMPREPPED is possibly redundant. */ + if (RL_ISSTATE (RL_STATE_SIGHANDLER) || RL_ISSTATE (RL_STATE_TERMPREPPED)) + bashline_set_event_hook (); +#endif + + SIGRETURN (0); +} + +void +termsig_handler (sig) + int sig; +{ + static int handling_termsig = 0; + int i, core; + sigset_t mask; + + /* Simple semaphore to keep this function from being executed multiple + times. Since we no longer are running as a signal handler, we don't + block multiple occurrences of the terminating signals while running. */ + if (handling_termsig) + return; + handling_termsig = 1; + terminating_signal = 0; /* keep macro from re-testing true. */ + + /* I don't believe this condition ever tests true. */ + if (sig == SIGINT && signal_is_trapped (SIGINT)) + run_interrupt_trap (0); + +#if defined (HISTORY) + /* If we don't do something like this, the history will not be saved when + an interactive shell is running in a terminal window that gets closed + with the `close' button. We can't test for RL_STATE_READCMD because + readline no longer handles SIGTERM synchronously. */ + if (interactive_shell && interactive && (sig == SIGHUP || sig == SIGTERM) && remember_on_history) + maybe_save_shell_history (); +#endif /* HISTORY */ + + if (this_shell_builtin == read_builtin) + read_tty_cleanup (); + +#if defined (JOB_CONTROL) + if (sig == SIGHUP && (interactive || (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)))) + hangup_all_jobs (); + + if ((subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)) == 0) + end_job_control (); +#endif /* JOB_CONTROL */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_all_fifos (); +# if defined (JOB_CONTROL) + procsub_clear (); +# endif +#endif /* PROCESS_SUBSTITUTION */ + + /* Reset execution context */ + loop_level = continuing = breaking = funcnest = 0; + executing_list = comsub_ignore_return = return_catch_flag = wait_intr_flag = 0; + + run_exit_trap (); /* XXX - run exit trap possibly in signal context? */ + + /* We don't change the set of blocked signals. If a user starts the shell + with a terminating signal blocked, we won't get here (and if by some + magic chance we do, we'll exit below). What we do is to restore the + top-level signal mask, in case this is called from a terminating signal + handler context, in which case the signal is blocked. */ + restore_sigmask (); + + set_signal_handler (sig, SIG_DFL); + + kill (getpid (), sig); + + if (dollar_dollar_pid != 1) + exit (128+sig); /* just in case the kill fails? */ + + /* We get here only under extraordinary circumstances. */ + + /* We are PID 1, and the kill above failed to kill the process. We assume + this means that we are running as an init process in a pid namespace + on Linux. In this case, we can't send ourselves a fatal signal, so we + determine whether or not we should have generated a core dump with the + kill call and attempt to trick the kernel into generating one if + necessary. */ + sigprocmask (SIG_SETMASK, (sigset_t *)NULL, &mask); + for (i = core = 0; i < TERMSIGS_LENGTH; i++) + { + set_signal_handler (XSIG (i), SIG_DFL); + sigdelset (&mask, XSIG (i)); + if (sig == XSIG (i)) + core = XCOREDUMP (i); + } + sigprocmask (SIG_SETMASK, &mask, (sigset_t *)NULL); + + if (core) + *((volatile unsigned long *) NULL) = 0xdead0000 + sig; /* SIGSEGV */ + + exit (128+sig); +} +#undef XSIG + +/* What we really do when SIGINT occurs. */ +sighandler +sigint_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + signal (sig, sigint_sighandler); +#endif + + /* interrupt_state needs to be set for the stack of interrupts to work + right. Should it be set unconditionally? */ + if (interrupt_state == 0) + ADDINTERRUPT; + + /* We will get here in interactive shells with job control active; allow + an interactive wait to be interrupted. wait_intr_flag is only set during + the execution of the wait builtin and when wait_intr_buf is valid. */ + if (wait_intr_flag) + { + last_command_exit_value = 128 + sig; + set_pipestatus_from_exit (last_command_exit_value); + wait_signal_received = sig; + SIGRETURN (0); + } + + /* In interactive shells, we will get here instead of trap_handler() so + note that we have a trap pending. */ + if (signal_is_trapped (sig)) + set_trap_state (sig); + + /* This is no longer used, but this code block remains as a reminder. */ + if (interrupt_immediately) + { + interrupt_immediately = 0; + set_exit_status (128 + sig); + throw_to_top_level (); + } +#if defined (READLINE) + /* Set the event hook so readline will call it after the signal handlers + finish executing, so if this interrupted character input we can get + quick response. */ + else if (RL_ISSTATE (RL_STATE_SIGHANDLER)) + bashline_set_event_hook (); +#endif + + SIGRETURN (0); +} + +#if defined (SIGWINCH) +sighandler +sigwinch_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + sigwinch_received = 1; + SIGRETURN (0); +} +#endif /* SIGWINCH */ + +void +set_sigwinch_handler () +{ +#if defined (SIGWINCH) + old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif +} + +void +unset_sigwinch_handler () +{ +#if defined (SIGWINCH) + set_signal_handler (SIGWINCH, old_winch); +#endif +} + +sighandler +sigterm_sighandler (sig) + int sig; +{ + sigterm_received = 1; /* XXX - counter? */ + SIGRETURN (0); +} + +/* Signal functions used by the rest of the code. */ +#if !defined (HAVE_POSIX_SIGNALS) + +/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */ +sigprocmask (operation, newset, oldset) + int operation, *newset, *oldset; +{ + int old, new; + + if (newset) + new = *newset; + else + new = 0; + + switch (operation) + { + case SIG_BLOCK: + old = sigblock (new); + break; + + case SIG_SETMASK: + old = sigsetmask (new); + break; + + default: + internal_error (_("sigprocmask: %d: invalid operation"), operation); + } + + if (oldset) + *oldset = old; +} + +#else + +#if !defined (SA_INTERRUPT) +# define SA_INTERRUPT 0 +#endif + +#if !defined (SA_RESTART) +# define SA_RESTART 0 +#endif + +SigHandler * +set_signal_handler (sig, handler) + int sig; + SigHandler *handler; +{ + struct sigaction act, oact; + + act.sa_handler = handler; + act.sa_flags = 0; + + /* XXX - bash-4.2 */ + /* We don't want a child death to interrupt interruptible system calls, even + if we take the time to reap children */ +#if defined (SIGCHLD) + if (sig == SIGCHLD) + act.sa_flags |= SA_RESTART; /* XXX */ +#endif + /* Let's see if we can keep SIGWINCH from interrupting interruptible system + calls, like open(2)/read(2)/write(2) */ +#if defined (SIGWINCH) + if (sig == SIGWINCH) + act.sa_flags |= SA_RESTART; /* XXX */ +#endif + /* If we're installing a SIGTERM handler for interactive shells, we want + it to be as close to SIG_IGN as possible. */ + if (sig == SIGTERM && handler == sigterm_sighandler) + act.sa_flags |= SA_RESTART; /* XXX */ + + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + if (sigaction (sig, &act, &oact) == 0) + return (oact.sa_handler); + else + return (SIG_DFL); +} +#endif /* HAVE_POSIX_SIGNALS */ |