Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -DPROGRAM='bash' -DCONF_HOSTTYPE='x86_64'
-DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='x86_64-unknown-linux-gnu'
-DCONF_VENDOR='unknown' -DLOCALEDIR='/usr/share/locale' -DPACKAGE='bash'
-DSHELL -DHAVE_CONFIG_H -I. -I. -I./include -I./lib
-D_FORTIFY_SOURCE=2 -march=x86-64 -mtune=generic -O2 -pipe
-fstack-protector-strong --param=ssp-buffer-size=4
-DDEFAULT_PATH_VALUE='/usr/local/sbin:/usr/local/bin:/usr/bin'
-DSTANDARD_UTILS_PATH='/usr/bin' -DSYS_BASHRC='/etc/bash.bashrc'
-DSYS_BASH_LOGOUT='/etc/bash.bash_logout'
uname output: Linux PatrickDesktop 3.19.2-1-ARCH #1 SMP PREEMPT Wed Mar
18 16:21:02 CET 2015 x86_64 GNU/Linux
Machine Type: x86_64-unknown-linux-gnu
Bash Version: 4.3
Patch Level: 39
Release Status: release
Description:
There's a bug that happens when waiting for a child process to complete
(via wait_for in jobs.c) that isn't part of a job, like command
substitution subshells. If a SIGINT is caught by the
wait_sigint_handler, the handler sets the wait_sigint_received flag
which is checked in set_job_status_and_cleanup. But
set_job_status_and_cleanup is called from waitchld only if the pid
belongs to a process that is part of a job. The result is that a SIGINT
trap, if it exists, is not called if a SIGINT is received while waiting
for a comsub subshell.
Repeat-By:
Running the following script (requires python) and giving it a SIGINT
(via ^C) when it blocks doesn't produce any output from the script.
Because SIGINT is trapped it should print “trapped”, however. Closing
stdout causes wait_for to be called in command_substitute:subst.c.
subst-sleep.sh:
trap 'echo trapped >&2; exit 1' INT
foo="$(python -c 'import time, os; os.close(1); time.sleep(20)')"
Fix:
The attached patch makes a conservative change that moves the code that
calls the SIGINT trap handler from set_job_status_and_cleanup to a new
function handle_sigint_caught_during_wait. This function is then
additionally called from waitchld for dead children that don't belong to
a job.
Related to the code the fix touches: handle_sigint_caught_during_wait
contains some duplicated code. Also, wait_for calls waitchld as long as
the child or job is still running and waitchld and
set_job_status_and_cleanup in turn call handle_sigint_caught_during_wait
if the child or job is dead. This seems to ensure that the SIGINT trap
handler is called at most once for a call to wait_for but isn't wait_for
a better place to check wait_sigint_received after having restored the
sigint handler (restore_sigint_handler()), especially since it already
contains CHECK_WAIT_INTR. Not to mention that a SIGINT could be received
after checking the wait_sigint_received flag and before restoring the
signal handler, as far as I understand it.
--- a/jobs.c
+++ b/jobs.c
@@ -260,6 +260,7 @@ static int print_job __P((JOB *, int, int, int));
static int process_exit_status __P((WAIT));
static int process_exit_signal __P((WAIT));
static int set_job_status_and_cleanup __P((int));
+static void handle_sigint_caught_during_wait __P((PROCESS *, int));
static WAIT job_signal_status __P((int));
static WAIT raw_job_exit_status __P((int));
@@ -3311,7 +3312,11 @@ itrace("waitchld: waitpid returns %d block = %d", pid,
block);
}
if (job == NO_JOB)
- continue;
+ {
+ if (PEXITED (child))
+ handle_sigint_caught_during_wait (child, NO_JOB);
+ continue;
+ }
call_set_current += set_job_status_and_cleanup (job);
@@ -3371,18 +3376,104 @@ itrace("waitchld: waitpid returns %d block = %d", pid,
block);
return (children_exited);
}
-/* Set the status of JOB and perform any necessary cleanup if the job is
- marked as JDEAD.
+/* Handle SIGINTs received while waiting for a foreground JOB, or CHILD if it
+ isn't part of a job, to finish. */
+static void handle_sigint_caught_during_wait (child, job)
+ PROCESS *child;
+ int job;
+{
+ SigHandler *temp_handler;
+ if (job != NO_JOB)
+ child = jobs[job]->pipe;
+ int tstatus, job_has_job_control, is_foreground_job;
+
+ job_has_job_control = job == NO_JOB ? job_control != 0 : IS_JOBCONTROL (job);
+ is_foreground_job = job == NO_JOB ? interactive_shell == 0 :
+ IS_FOREGROUND (job);
+
+ if (!(wait_sigint_received && is_foreground_job))
+ return;
+
+ /* If we're running a shell script and we get a SIGINT with a
+ SIGINT trap handler, but the foreground job handles it and
+ does not exit due to SIGINT, run the trap handler but do not
+ otherwise act as if we got the interrupt. */
+ if (interactive_shell == 0 &&
+ child_caught_sigint && signal_is_trapped (SIGINT))
+ {
+ int old_frozen;
+ wait_sigint_received = 0;
+ last_command_exit_value = process_exit_status (child->status);
+
+ old_frozen = jobs_list_frozen;
+ jobs_list_frozen = 1;
+ tstatus = maybe_call_trap_handler (SIGINT);
+ jobs_list_frozen = old_frozen;
+ }
+
+ /* If the foreground job is killed by SIGINT when job control is not
+ active, we need to perform some special handling.
+
+ The check of wait_sigint_received is a way to determine if the
+ SIGINT came from the keyboard (in which case the shell has already
+ seen it, and wait_sigint_received is non-zero, because keyboard
+ signals are sent to process groups) or via kill(2) to the foreground
+ process by another process (or itself). If the shell did receive the
+ SIGINT, it needs to perform normal SIGINT processing. */
+ else if (child_caught_sigint == 0 && job_has_job_control == 0)
+ {
+ int old_frozen;
+
+ wait_sigint_received = 0;
+
+ /* If SIGINT is trapped, set the exit status so that the trap
+ handler can see it. */
+ if (signal_is_trapped (SIGINT))
+ last_command_exit_value = process_exit_status (child->status);
+
+ /* If the signal is trapped, let the trap handler get it no matter
+ what and simply return if the trap handler returns.
+ maybe_call_trap_handler() may cause dead jobs to be removed from
+ the job table because of a call to execute_command. We work
+ around this by setting JOBS_LIST_FROZEN. */
+ old_frozen = jobs_list_frozen;
+ jobs_list_frozen = 1;
+ tstatus = maybe_call_trap_handler (SIGINT);
+ jobs_list_frozen = old_frozen;
+ if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
+ {
+ /* wait_sigint_handler () has already seen SIGINT and
+ allowed the wait builtin to jump out. We need to
+ call the original SIGINT handler, if necessary. If
+ the original handler is SIG_DFL, we need to resend
+ the signal to ourselves. */
+
+ temp_handler = old_sigint_handler;
+
+ /* Bogus. If we've reset the signal handler as the result
+ of a trap caught on SIGINT, then old_sigint_handler
+ will point to trap_handler, which now knows nothing about
+ SIGINT (if we reset the sighandler to the default).
+ In this case, we have to fix things up. What a crock. */
+ if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0)
+ temp_handler = trap_to_sighandler (SIGINT);
+ restore_sigint_handler ();
+ if (temp_handler == SIG_DFL)
+ termsig_handler (SIGINT); /* XXX */
+ else if (temp_handler != SIG_IGN)
+ (*temp_handler) (SIGINT);
+ }
+ }
+}
- Currently, the cleanup activity is restricted to handling any SIGINT
- received while waiting for a foreground job to finish. */
+/* Set the status of JOB and perform any necessary cleanup if the job is
+ marked as JDEAD. */
static int
set_job_status_and_cleanup (job)
int job;
{
PROCESS *child;
- int tstatus, job_state, any_stopped, any_tstped, call_set_current;
- SigHandler *temp_handler;
+ int job_state, any_stopped, any_tstped, call_set_current;
child = jobs[job]->pipe;
jobs[job]->flags &= ~J_NOTIFIED;
@@ -3467,81 +3558,7 @@ set_job_status_and_cleanup (job)
*/
if (JOBSTATE (job) == JDEAD)
- {
- /* If we're running a shell script and we get a SIGINT with a
- SIGINT trap handler, but the foreground job handles it and
- does not exit due to SIGINT, run the trap handler but do not
- otherwise act as if we got the interrupt. */
- if (wait_sigint_received && interactive_shell == 0 &&
- child_caught_sigint && IS_FOREGROUND (job) &&
- signal_is_trapped (SIGINT))
- {
- int old_frozen;
- wait_sigint_received = 0;
- last_command_exit_value = process_exit_status (child->status);
-
- old_frozen = jobs_list_frozen;
- jobs_list_frozen = 1;
- tstatus = maybe_call_trap_handler (SIGINT);
- jobs_list_frozen = old_frozen;
- }
-
- /* If the foreground job is killed by SIGINT when job control is not
- active, we need to perform some special handling.
-
- The check of wait_sigint_received is a way to determine if the
- SIGINT came from the keyboard (in which case the shell has already
- seen it, and wait_sigint_received is non-zero, because keyboard
- signals are sent to process groups) or via kill(2) to the foreground
- process by another process (or itself). If the shell did receive the
- SIGINT, it needs to perform normal SIGINT processing. */
- else if (wait_sigint_received &&
- child_caught_sigint == 0 &&
- IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
- {
- int old_frozen;
-
- wait_sigint_received = 0;
-
- /* If SIGINT is trapped, set the exit status so that the trap
- handler can see it. */
- if (signal_is_trapped (SIGINT))
- last_command_exit_value = process_exit_status (child->status);
-
- /* If the signal is trapped, let the trap handler get it no matter
- what and simply return if the trap handler returns.
- maybe_call_trap_handler() may cause dead jobs to be removed from
- the job table because of a call to execute_command. We work
- around this by setting JOBS_LIST_FROZEN. */
- old_frozen = jobs_list_frozen;
- jobs_list_frozen = 1;
- tstatus = maybe_call_trap_handler (SIGINT);
- jobs_list_frozen = old_frozen;
- if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
- {
- /* wait_sigint_handler () has already seen SIGINT and
- allowed the wait builtin to jump out. We need to
- call the original SIGINT handler, if necessary. If
- the original handler is SIG_DFL, we need to resend
- the signal to ourselves. */
-
- temp_handler = old_sigint_handler;
-
- /* Bogus. If we've reset the signal handler as the result
- of a trap caught on SIGINT, then old_sigint_handler
- will point to trap_handler, which now knows nothing about
- SIGINT (if we reset the sighandler to the default).
- In this case, we have to fix things up. What a crock. */
- if (temp_handler == trap_handler && signal_is_trapped (SIGINT) ==
0)
- temp_handler = trap_to_sighandler (SIGINT);
- restore_sigint_handler ();
- if (temp_handler == SIG_DFL)
- termsig_handler (SIGINT); /* XXX */
- else if (temp_handler != SIG_IGN)
- (*temp_handler) (SIGINT);
- }
- }
- }
+ handle_sigint_caught_during_wait (NULL, job);
return call_set_current;
}