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-redhat-linux-gnu' -DCONF_VENDOR='redhat' -DLOCALEDIR='/usr/share/locale' -DPACKAGE='bash' -DSHELL -DHAVE_CONFIG_H -I. -I. -I./include -I./lib -D_GNU_SOURCE -DRECYCLES_PIDS -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -fwrapv
uname output: Linux (hostname removed) 2.6.32-504.8.1.el6.x86_64 #1 SMP Fri Dec 19 12:09:25 EST 2014 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-redhat-linux-gnu
Bash Version: 4.1
Patch Level: 2
Release Status: release
Description:
I ran into this issue while experimenting with gnu make and .ONESHELL. I've boiled the issue down to the following simple example:
$ newline=$'\n'
$ bash -e -c "trap 'echo handler' INT ERR;/bin/true; sleep 2; ${newline}/bin/false"
It will successfully trap if I do the following:
* hit ^C during the sleep
* use "false" instead of "/bin/false" because that'll use a builtin
* trap on EXIT instead
* remove the newline
* add another command after "/bin/false"
* add another command before "/bin/false" without a newline after it
If I replace "/bin/false" with a sleep then hit ^C then the INT trap also won't execute.
I traced this down to builtins/evalstring.c inside parse_and_execute (this is from the latest "develop" version of the file, not from the version I tested with):
#if defined (ONESHOT)
/*
* IF
* we were invoked as `bash -c' (startup_state == 2) AND
* parse_and_execute has not been called recursively AND
* we're not running a trap AND
* we have parsed the full command (string == '\0') AND
* we're not going to run the exit trap AND
* we have a simple command without redirections AND
* the command is not being timed AND
* the command's return status is not being inverted
* THEN
* tell the execution code that we don't need to fork
*/
if (should_suppress_fork (command))
{
command->flags |= CMD_NO_FORK;
command->value.Simple->flags |= CMD_NO_FORK;
}
else if (command->type == cm_connection)
optimize_fork (command);
#endif /* ONESHOT */
... and it appears this is the culprit. At first I was under the mistaken assumption "we're not running a trap" meant "a trap is set that could be triggered" when in reality it means what it says -- a trap is currently running.
Fix:
Two possible fixes come to mind:
1) Have the above code loop over the possible signals calling "signal_is_trapped"
2) Alter "change_signal" as follows:
/* mask of currently trapped signals */
unsigned long long current_traps;
/*...*/
/* If SIG has a string assigned to it, get rid of it. Then give it
VALUE. */
static void
change_signal (sig, value)
int sig;
char *value;
{
if ((sigmodes[sig] & SIG_INPROGRESS) == 0)
free_trap_command (sig);
trap_list[sig] = value;
sigmodes[sig] |= SIG_TRAPPED;
if (value == (char *)IGNORE_SIG)
sigmodes[sig] |= SIG_IGNORED;
else
sigmodes[sig] &= ~SIG_IGNORED;
if (sigmodes[sig] & SIG_INPROGRESS)
sigmodes[sig] |= SIG_CHANGED;
+
+ if( sigmodes[sig] & SIG_TRAPPED ) {
+ current_traps |= 1 << sig;
+ } else {
+ current_traps &= ~(1 << sig);
+ }
}
... then alter the block quoted above in parse_and_execute to check the value of "current_traps".
-brian