1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

Fix SIGINT handling for external commands run from scripts

Reproducer:

  $ ksh -c 'bash -c '\''kill -s INT $$'\''; echo "$?, continuing"'

Expected result: output "258, continuing"; exit status 0.

Actual result: no output; exit status 258. The child process sent
SIGINT only to itself and not to the process group, so the parent
script was wrongly interrupted.

Every shell except ksh93 produces the expected result. ksh93 also
gave the expected result before version 2008-01-31 93s+, which
introduced the code below.

Analysis: The problem is in these lines of code in xec.c,
sh_exec(), TFORK case, parent branch of fork:

1649:	if(!sh_isstate(SH_MONITOR))
1650:	{
1651:		if(!(sh.sigflag[SIGINT]&(SH_SIGFAULT|SH_SIGOFF)))
1652:			sh_sigtrap(SIGINT);
1653:		sh.trapnote |= SH_SIGIGNORE;
1654:	}
[...pipe and I/O handling, wait for command to finish...]
1667:	if(!sh_isstate(SH_MONITOR))
1668:	{
1669:		sh.trapnote &= ~SH_SIGIGNORE;
1700:		if(sh.exitval == (SH_EXITSIG|SIGINT))
1701:			kill(sh.current_pid,SIGINT);
1702:	}

When a user presses Ctrl+C, SIGINT is sent to the entire process
group. If job control is fully off (i.e., !sh_isstate(SH_MONITOR)),
then the process group includes the parent script. Therefore, in a
script such as

  $ ksh -c 'bash -c '\''read x'\''; echo "$?, continuing"'

when the user presses Ctrl+C while bash waits for 'read x' input,
the parent ksh script should be interrupted as well.

Now, the code above ignores SIGINT while bash is running. (This is
done using special-casing in sh_fault() to handle that SH_SIGIGNORE
flag for SIGINT.) So, when Ctrl+C interrupts the process group, the
parent script is not getting interrupted as it should.

To compensate for that, the code then detects, using sh.exitval
(the child process' exit status), whether the child process was
killed by SIGINT. If so, it simply assumes that the signal was
meant for the process group including the parent script, so it
reissues SIGINT to itself after unignoring it.

But, as we can see from the broken reproducer above, that
assumption is not valid. Scripts are perfectly free to send SIGINT
to themselves only, and that must work as expected.

src/cmd/ksh93/sh/xec.c: sh_exec(): TFORK: parent branch:
- Instead of ignoring SIGINT, sigblock() it, which delays handling
  the signal until sigrelease(). (Note that these are macros
  defined in src/cmd/ksh93/features/sigfeatures according to OS
  capabilities.)
- This makes reissuing SIGINT redundant, so delete that, which
  fixes the bug.

src/cmd/ksh93/sh/fault.c:
- Nothing now sets the SH_SIGIGNORE flag in sh.trapnote, so remove
  special-casing added in 2008-01-31 93s+.
This commit is contained in:
Martijn Dekker 2022-02-01 03:11:10 +00:00
parent 9a5af738ef
commit d650c73e55
4 changed files with 25 additions and 12 deletions

View file

@ -113,8 +113,6 @@ void sh_fault(register int sig)
flag = sh.sigflag[sig]&~SH_SIGOFF;
if(!trap)
{
if(sig==SIGINT && (sh.trapnote&SH_SIGIGNORE))
return;
if(flag&SH_SIGIGNORE)
{
if(sh.subshell)
@ -386,7 +384,7 @@ void sh_chktrap(void)
{
register int sig=sh.st.trapmax;
register char *trap;
if(!(sh.trapnote&~SH_SIGIGNORE))
if(!sh.trapnote)
sig=0;
sh.trapnote &= ~SH_SIGTRAP;
/* execute errexit trap first */