From silby@patrocles.silby.com  Fri Nov 26 08:05:51 2004
Return-Path: <silby@patrocles.silby.com>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id E6DD416A4CE
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 26 Nov 2004 08:05:51 +0000 (GMT)
Received: from patrocles.silby.com (adsl-68-249-2-11.dsl.milwwi.ameritech.net [68.249.2.11])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 5631A43D46
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 26 Nov 2004 08:05:51 +0000 (GMT)
	(envelope-from silby@patrocles.silby.com)
Received: from patrocles.silby.com (localhost [127.0.0.1])
	by patrocles.silby.com (8.13.1/8.13.1) with ESMTP id iAQ85vp7004920
	for <FreeBSD-gnats-submit@freebsd.org>; Fri, 26 Nov 2004 02:05:57 -0600 (CST)
	(envelope-from silby@patrocles.silby.com)
Received: (from silby@localhost)
	by patrocles.silby.com (8.13.1/8.13.1/Submit) id iAQ85uFk004919;
	Fri, 26 Nov 2004 02:05:56 -0600 (CST)
	(envelope-from silby)
Message-Id: <200411260805.iAQ85uFk004919@patrocles.silby.com>
Date: Fri, 26 Nov 2004 02:05:56 -0600 (CST)
From: Mike Silbersack <silby@silby.com>
Reply-To: Mike Silbersack <silby@silby.com>
To: FreeBSD-gnats-submit@freebsd.org
Cc:
Subject: sh does not handle signals to subshells properly and/or $! is broken
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         74404
>Category:       bin
>Synopsis:       sh(1) does not handle signals to subshells properly and/or $! is broken
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Fri Nov 26 08:10:18 GMT 2004
>Closed-Date:    Sat Jul 04 19:56:09 UTC 2009
>Last-Modified:  Sat Jul 04 19:56:09 UTC 2009
>Originator:     Mike Silbersack
>Release:        FreeBSD 6.0-CURRENT i386
>Organization:
>Environment:
System: FreeBSD patrocles.silby.com 6.0-CURRENT FreeBSD 6.0-CURRENT #2: Thu Nov 25 01:13:18 CST 2004 silby@patrocles.silby.com:/usr/obj/usr/src/sys/PATROCLES i386


	
Description:
	/bin/sh in the base system has an oddity in how it handles signals
to subshells.  If you run the supplied script (known as badsh.sh), you will
find that the subshell will not be terminated by the script, and the
"You didn't get me!" line will be printed.

Under bash, the subshell is killed as expected.

The reason for this issue appears to be due to a few things:

1.  sh spawns forks TWO copies of itself when creating the subshell.

2.  The first subshell's PID is returned by $!; this subshell then
forks again and creates the second subshell, which actually does
the sleep and echo.

3.  The first subshell does not pass on signals to the second subshell.
As a result, the kill signal is never delivered to the second subshell,
and it is not terminated.

This is frustrating behavior because aside from kill $! + 1 (which would be
a gross hack), the only way to kill the subshell would be to use ps | grep
something or other > kill.  So, the best fix would be for the 1st subshell
to simply pass on the kill signal to the second subshell before it dies.

This behavior happens on 6.0 and 4.10; I have not tested other OS versions.
Since bash handles it in what appears to be the correct fashion, this seems
to be a bug in sh.

How-To-Repeat:

This test program will help you see the issue:

#!/bin/sh
echo $$
    (
        sleep 10
	echo "You didn't get me!"
        exit 1
        )&
        echo $!
        kill $!
        exit 1

Immediately after running it, use ps and see how many /bin/sh processes are running.
Comment out the kill line and compare.

Fix:
	None at this time... using bash instead could be an alternative.


>Description:
>How-To-Repeat:
>Fix:
>Release-Note:
>Audit-Trail:

From: Jilles Tjoelker <jilles@stack.nl>
To: bug-followup@FreeBSD.org, silby@silby.com
Cc:  
Subject: Re: bin/74404: sh(1) does not handle signals to subshells properly
	and/or $! is broken
Date: Sun, 5 Apr 2009 15:36:04 +0200

 > [ sh forks twice for (somecommand) & ]
 
 It seems reasonable for sh to fork twice here. You can avoid it by doing
 { somecommand; } &.
 
 If I force bash (4.0.10) to fork twice (using  { ( sleep 900; echo a );
 echo b; }  ), it "breaks" the same way as sh does in your example. It
 does not "pass on" signals. If you want to pass on signals, do it
 manually using the trap builtin.
 
 It would be possible fairly easily to make sh treat (CMD)& as { CMD; }&
 but I'm not sure if it's worth it.
 
 -- 
 Jilles Tjoelker

From: Mike Silbersack <silby@silby.com>
To: Jilles Tjoelker <jilles@stack.nl>
Cc: bug-followup@FreeBSD.org
Subject: Re: bin/74404: sh(1) does not handle signals to subshells properly
 and/or $! is broken
Date: Mon, 6 Apr 2009 22:11:08 -0500 (CDT)

 On Sun, 5 Apr 2009, Jilles Tjoelker wrote:
 
 >> [ sh forks twice for (somecommand) & ]
 >
 > It seems reasonable for sh to fork twice here. You can avoid it by doing
 > { somecommand; } &.
 
 I honestly don't remember how I came up with the script in question, but I 
 suspect that I picked it up from an example bash script.  Based on that, 
 it may be worth making the script function as is.
 
 Mike "Silby" Silbersack

From: Jilles Tjoelker <jilles@stack.nl>
To: Mike Silbersack <silby@silby.com>
Cc: bug-followup@FreeBSD.org
Subject: Re: bin/74404: sh(1) does not handle signals to subshells properly
	and/or $! is broken
Date: Thu, 9 Apr 2009 22:45:52 +0200

 --x+6KMIRAuhnl3hBn
 Content-Type: text/plain; charset=us-ascii
 Content-Disposition: inline
 
 On Mon, Apr 06, 2009 at 10:11:08PM -0500, Mike Silbersack wrote:
 > On Sun, 5 Apr 2009, Jilles Tjoelker wrote:
 > >> [ sh forks twice for (somecommand) & ]
 
 > > It seems reasonable for sh to fork twice here. You can avoid it by doing
 > > { somecommand; } &.
 
 > I honestly don't remember how I came up with the script in question, but I 
 > suspect that I picked it up from an example bash script.  Based on that, 
 > it may be worth making the script function as is.
 
 After investigating some more, I agree. I found an elegant way to treat
 (CMD)& as { CMD; }&, and it turns out that many other shells already do
 something like it, so it is likely problematic use of $! occurs more
 often. The point is that it is not necessary to fork for a subshell if
 this is the last thing the shell process has to do (and there are no
 traps), just like already is implemented for external commands (to some
 degree).
 
 Patches for src/bin/sh, also on web space in case gnats mangles them
 
 http://www.stack.nl/~jilles/unix/sh-forkiftrapped.patch adds the code to
 check if there are traps and ensures the shell stays around waiting for
 an external command if there are traps. Formerly this was tied to -T.
 (This only happens with traps set in subshells. With the old code, a
 workaround is to place ;: after the external command.)
 
 http://www.stack.nl/~jilles/unix/sh-subshell-noforkifunneeded.patch
 avoids forking for subshells if not necessary, and depends on the first
 patch.
 
 -- 
 Jilles Tjoelker
 
 --x+6KMIRAuhnl3hBn
 Content-Type: text/x-diff; charset=us-ascii
 Content-Disposition: attachment; filename="sh-forkiftrapped.patch"
 
 Don't skip forking for an external command if any traps are active.
 
 Example:
   sh -c '(trap "echo trapped" EXIT; sleep 3)'
 now correctly prints "trapped".
 
 With this check, it is no longer necessary to check for -T
 explicitly in that case.
 
 diff --git a/eval.c b/eval.c
 --- a/eval.c
 +++ b/eval.c
 @@ -730,7 +730,7 @@ evalcommand(union node *cmd, int flags, 
  	/* Fork off a child process if necessary. */
  	if (cmd->ncmd.backgnd
  	 || (cmdentry.cmdtype == CMDNORMAL
 -	    && ((flags & EV_EXIT) == 0 || Tflag))
 +	    && ((flags & EV_EXIT) == 0 || have_traps()))
  	 || ((flags & EV_BACKCMD) != 0
  	    && (cmdentry.cmdtype != CMDBUILTIN
  		 || cmdentry.u.index == CDCMD
 diff --git a/trap.c b/trap.c
 --- a/trap.c
 +++ b/trap.c
 @@ -222,6 +222,21 @@ clear_traps(void)
  
  
  /*
 + * Check if we have any traps enabled.
 + */
 +int
 +have_traps(void)
 +{
 +	char *volatile *tp;
 +
 +	for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) {
 +		if (*tp && **tp)	/* trap not NULL or SIG_IGN */
 +			return 1;
 +	}
 +	return 0;
 +}
 +
 +/*
   * Set the signal handler for the specified signal.  The routine figures
   * out what it should be set to.
   */
 diff --git a/trap.h b/trap.h
 --- a/trap.h
 +++ b/trap.h
 @@ -39,6 +39,7 @@ extern volatile sig_atomic_t gotwinch;
  
  int trapcmd(int, char **);
  void clear_traps(void);
 +int have_traps(void);
  void setsignal(int);
  void ignoresig(int);
  void onsig(int);
 
 --x+6KMIRAuhnl3hBn
 Content-Type: text/x-diff; charset=us-ascii
 Content-Disposition: attachment; filename="sh-subshell-noforkifunneeded.patch"
 
 Do not fork for a subshell if it is the last thing this shell is doing
 (EV_EXIT). The fork is still done as normal if any traps
 are active.
 
 Example:
   sh -c '(/bin/sleep 10)& sleep 1;ps -p $! -o comm='
 Now prints "sleep" instead of "sh". $! is more useful this way.
 Most shells (dash, bash, pdksh, ksh93, zsh) seem to print "sleep" for this.
 
 Example:
   sh -c '( ( ( (ps jT))))'
 Now shows only one waiting shell process instead of four.
 Most shells (dash, bash, pdksh, ksh93, zsh) seem to show zero or one.
 
 diff --git a/eval.c b/eval.c
 --- a/eval.c
 +++ b/eval.c
 @@ -397,8 +397,8 @@ evalsubshell(union node *n, int flags)
  	int backgnd = (n->type == NBACKGND);
  
  	expredir(n->nredir.redirect);
 -	jp = makejob(n, 1);
 -	if (forkshell(jp, n, backgnd) == 0) {
 +	if ((!backgnd && flags & EV_EXIT && !have_traps()) ||
 +			forkshell(jp = makejob(n, 1), n, backgnd) == 0) {
  		if (backgnd)
  			flags &=~ EV_TESTED;
  		redirect(n->nredir.redirect, 0);
 
 --x+6KMIRAuhnl3hBn--

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/74404: commit references a PR
Date: Sat, 13 Jun 2009 21:10:55 +0000 (UTC)

 Author: jilles
 Date: Sat Jun 13 21:10:41 2009
 New Revision: 194127
 URL: http://svn.freebsd.org/changeset/base/194127
 
 Log:
   Don't skip forking for an external command if any traps are active.
   
   Example:
     sh -c '(trap "echo trapped" EXIT; sleep 3)'
   now correctly prints "trapped".
   
   With this check, it is no longer necessary to check for -T
   explicitly in that case.
   
   This is a useful bugfix by itself and also important because I plan to
   skip forking more often.
   
   PR:		bin/113860 (part of)
   PR:		bin/74404 (part of)
   Reviewed by:	stefanf
   Approved by:	ed (mentor)
 
 Modified:
   head/bin/sh/eval.c
   head/bin/sh/trap.c
   head/bin/sh/trap.h
 
 Modified: head/bin/sh/eval.c
 ==============================================================================
 --- head/bin/sh/eval.c	Sat Jun 13 20:58:12 2009	(r194126)
 +++ head/bin/sh/eval.c	Sat Jun 13 21:10:41 2009	(r194127)
 @@ -731,7 +731,7 @@ evalcommand(union node *cmd, int flags, 
  	/* Fork off a child process if necessary. */
  	if (cmd->ncmd.backgnd
  	 || (cmdentry.cmdtype == CMDNORMAL
 -	    && ((flags & EV_EXIT) == 0 || Tflag))
 +	    && ((flags & EV_EXIT) == 0 || have_traps()))
  	 || ((flags & EV_BACKCMD) != 0
  	    && (cmdentry.cmdtype != CMDBUILTIN
  		 || cmdentry.u.index == CDCMD
 
 Modified: head/bin/sh/trap.c
 ==============================================================================
 --- head/bin/sh/trap.c	Sat Jun 13 20:58:12 2009	(r194126)
 +++ head/bin/sh/trap.c	Sat Jun 13 21:10:41 2009	(r194127)
 @@ -222,6 +222,21 @@ clear_traps(void)
  
  
  /*
 + * Check if we have any traps enabled.
 + */
 +int
 +have_traps(void)
 +{
 +	char *volatile *tp;
 +
 +	for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) {
 +		if (*tp && **tp)	/* trap not NULL or SIG_IGN */
 +			return 1;
 +	}
 +	return 0;
 +}
 +
 +/*
   * Set the signal handler for the specified signal.  The routine figures
   * out what it should be set to.
   */
 
 Modified: head/bin/sh/trap.h
 ==============================================================================
 --- head/bin/sh/trap.h	Sat Jun 13 20:58:12 2009	(r194126)
 +++ head/bin/sh/trap.h	Sat Jun 13 21:10:41 2009	(r194127)
 @@ -39,6 +39,7 @@ extern volatile sig_atomic_t gotwinch;
  
  int trapcmd(int, char **);
  void clear_traps(void);
 +int have_traps(void);
  void setsignal(int);
  void ignoresig(int);
  void onsig(int);
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/74404: commit references a PR
Date: Tue, 23 Jun 2009 21:50:17 +0000 (UTC)

 Author: jilles
 Date: Tue Jun 23 21:50:06 2009
 New Revision: 194774
 URL: http://svn.freebsd.org/changeset/base/194774
 
 Log:
   Do not fork for a subshell if it is the last thing this shell is doing
   (EV_EXIT). The fork is still done as normal if any traps are active.
   
   In many cases, the fork can be avoided even without this change by using {}
   instead of (), but in practice many scripts use (), likely because the
   syntax is simpler.
   
   Example:
     sh -c '(/bin/sleep 10)& sleep 1;ps -p $! -o comm='
   Now prints "sleep" instead of "sh". $! is more useful this way.
   Most shells (dash, bash, pdksh, ksh93, zsh) seem to print "sleep" for this.
   
   Example:
     sh -c '( ( ( (ps jT))))'
   Now shows no waiting shell processes instead of four.
   Most shells (dash, bash, pdksh, ksh93, zsh) seem to show zero or one.
   
   PR:		bin/74404
   Approved by:	ed (mentor) (implicit)
 
 Modified:
   head/bin/sh/eval.c
 
 Modified: head/bin/sh/eval.c
 ==============================================================================
 --- head/bin/sh/eval.c	Tue Jun 23 21:48:04 2009	(r194773)
 +++ head/bin/sh/eval.c	Tue Jun 23 21:50:06 2009	(r194774)
 @@ -401,8 +401,8 @@ evalsubshell(union node *n, int flags)
  	int backgnd = (n->type == NBACKGND);
  
  	expredir(n->nredir.redirect);
 -	jp = makejob(n, 1);
 -	if (forkshell(jp, n, backgnd) == 0) {
 +	if ((!backgnd && flags & EV_EXIT && !have_traps()) ||
 +			forkshell(jp = makejob(n, 1), n, backgnd) == 0) {
  		if (backgnd)
  			flags &=~ EV_TESTED;
  		redirect(n->nredir.redirect, 0);
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
State-Changed-From-To: open->closed 
State-Changed-By: jilles 
State-Changed-When: Sat Jul 4 19:56:08 UTC 2009 
State-Changed-Why:  
Fixed. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=74404 
>Unformatted:
