diff mbox series

[v3] redir: Handle nested exec within REALLY_CLOSED redirection

Message ID 20190116133231.mx53nyjhgrfiflch@gondor.apana.org.au (mailing list archive)
State Superseded
Delegated to: Herbert Xu
Headers show
Series [v3] redir: Handle nested exec within REALLY_CLOSED redirection | expand

Commit Message

Herbert Xu Jan. 16, 2019, 1:32 p.m. UTC
On Fri, Dec 14, 2018 at 01:01:12AM +0000, Martijn Dekker wrote:
>
> > So did anything happen on the bash front? I'm happy to change if
> > bash moves in the same direction.
> 
> Yes. According to the bash changelog, Chet fixed it in git last 30th of
> April, meaning it'll be in bash 5.0.
> 
> Patch attached, as per Harald's suggestion.

Thanks for the patch.  I took a deeper look into the history of
the bug and it turned out that I added REALLY_CLOSED as an
optimisation in order to avoid an unnecessary close(2) syscall.

Removing REALLY_CLOSED would mean an unnecessary close(2) in
such cases.

So I'm going to go for a more complicated fix:

---8<---
The value of REALLY_CLOSED is used to avoid an unnecessary close(2)
call when restoring redirections.  However, as it stands it can
remove a close(2) call that's actually needed.  This happens when
an enclosed exec(1) command leaves an open file descriptor behind.

This patch fixes this by going up one level when popping redirections
and changing REALLY_CLOSED back to CLOSED when we encounter the
nested exec(1) command.

Reported-by: Martijn Dekker <martijn@inlv.org>
Fixes: ce0f1900d869 ("[REDIR] Fix redirect restore on saved file...")
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Comments

Martijn Dekker Jan. 16, 2019, 9:39 p.m. UTC | #1
Op 16-01-19 om 14:32 schreef Herbert Xu:
> Thanks for the patch.  I took a deeper look into the history of
> the bug and it turned out that I added REALLY_CLOSED as an
> optimisation in order to avoid an unnecessary close(2) syscall.

Does this actually save cycles? I'm probably missing something, but that
code looks to me like it probably uses more cycles than close(2) going
'descriptor not open, return'.

- Martijn
Martijn Dekker Jan. 16, 2019, 9:53 p.m. UTC | #2
Op 16-01-19 om 22:39 schreef Martijn Dekker:
> Op 16-01-19 om 14:32 schreef Herbert Xu:
>> Thanks for the patch.  I took a deeper look into the history of
>> the bug and it turned out that I added REALLY_CLOSED as an
>> optimisation in order to avoid an unnecessary close(2) syscall.
> 
> Does this actually save cycles? I'm probably missing something, but that
> code looks to me like it probably uses more cycles than close(2) going
> 'descriptor not open, return'.

Never mind, stupid question that I should have googled before asking it.
The answer is that a switch to kernel mode and back is expensive.

- M.
Harald van Dijk Jan. 16, 2019, 10:29 p.m. UTC | #3
On 16/01/2019 21:53, Martijn Dekker wrote:
> Op 16-01-19 om 22:39 schreef Martijn Dekker:
>> Op 16-01-19 om 14:32 schreef Herbert Xu:
>>> Thanks for the patch.  I took a deeper look into the history of
>>> the bug and it turned out that I added REALLY_CLOSED as an
>>> optimisation in order to avoid an unnecessary close(2) syscall.
>>
>> Does this actually save cycles? I'm probably missing something, but that
>> code looks to me like it probably uses more cycles than close(2) going
>> 'descriptor not open, return'.
> 
> Never mind, stupid question that I should have googled before asking it.
> The answer is that a switch to kernel mode and back is expensive.

Still, if that's slow enough and happens commonly enough that it's worth 
avoiding, it seems like it would still be simpler, shorter and probably 
faster to just write

   if (fd != -1)
     close(fd);

in a few places, since we know that the only invalid fd that ever gets 
passed to close() is -1. That avoids the other cases where dash already 
happily calls close(-1) as well.

Cheers,
Harald van Dijk
Herbert Xu Jan. 17, 2019, 7:01 a.m. UTC | #4
On Wed, Jan 16, 2019 at 10:29:50PM +0000, Harald van Dijk wrote:
>
> Still, if that's slow enough and happens commonly enough that it's worth
> avoiding, it seems like it would still be simpler, shorter and probably
> faster to just write
> 
>   if (fd != -1)
>     close(fd);
> 
> in a few places, since we know that the only invalid fd that ever gets
> passed to close() is -1. That avoids the other cases where dash already
> happily calls close(-1) as well.

The issue is not close(-1), it's close(8) for 8>&- where we already
know that fd 8 was closed to start with (because the first dup
returned -1 on it).

Cheers,
Harald van Dijk Jan. 17, 2019, 8:28 a.m. UTC | #5
On 17/01/2019 07:01, Herbert Xu wrote:
> On Wed, Jan 16, 2019 at 10:29:50PM +0000, Harald van Dijk wrote:
>>
>> Still, if that's slow enough and happens commonly enough that it's worth
>> avoiding, it seems like it would still be simpler, shorter and probably
>> faster to just write
>>
>>    if (fd != -1)
>>      close(fd);
>>
>> in a few places, since we know that the only invalid fd that ever gets
>> passed to close() is -1. That avoids the other cases where dash already
>> happily calls close(-1) as well.
> 
> The issue is not close(-1), it's close(8) for 8>&- where we already
> know that fd 8 was closed to start with (because the first dup
> returned -1 on it).

Oh, okay. In that case, the fact that there are code paths in which dash 
ends up calling close(-1) may be another issue to look at, since that is 
known before the call to be a wasted syscall as well.

Cheers,
Harald van Dijk
Martijn Dekker Jan. 17, 2019, 10:07 p.m. UTC | #6
Op 16-01-19 om 14:32 schreef Herbert Xu:
> So I'm going to go for a more complicated fix:

The v3 patch introduces a bug:

====begin test script====
init() {
        exec 8</dev/null
} >&-

{
        init
        : <&8 && echo ok
} 8<&-
====end test script====

Actual output:
test2.sh: 9: test2.sh: 8: Bad file descriptor

Expected output:
ok

The 'read' gets a 'bad file descriptor', so the effect of the 'exec'
from the init function is lost.

Interestingly, removing either the ">&-" from the function definition
block, or the "8<&-" from the other braces block, or both, makes the
error go away.

- Martijn
diff mbox series

Patch

diff --git a/src/redir.c b/src/redir.c
index e67cc0a..4f84a80 100644
--- a/src/redir.c
+++ b/src/redir.c
@@ -128,12 +128,12 @@  redirect(union node *redir, int flags)
 			p = &sv->renamed[fd];
 			i = *p;
 
-			if (likely(i == EMPTY)) {
-				i = CLOSED;
-				if (fd != newfd) {
+			if (likely(i <= EMPTY)) {
+				if (i == EMPTY && fd != newfd) {
 					i = savefd(fd, fd);
 					fd = -1;
-				}
+				} else
+					i = CLOSED;
 			}
 
 			if (i == newfd)
@@ -340,16 +340,20 @@  out:
 void
 popredir(int drop)
 {
+	struct redirtab *nrp;
 	struct redirtab *rp;
 	int i;
 
 	INTOFF;
 	rp = redirlist;
+	nrp = rp->next;
 	for (i = 0 ; i < 10 ; i++) {
 		switch (rp->renamed[i]) {
 		case CLOSED:
 			if (!drop)
 				close(i);
+			else if (nrp && nrp->renamed[i] == REALLY_CLOSED)
+				nrp->renamed[i] = CLOSED;
 			break;
 		case EMPTY:
 		case REALLY_CLOSED:
@@ -361,7 +365,7 @@  popredir(int drop)
 			break;
 		}
 	}
-	redirlist = rp->next;
+	redirlist = nrp;
 	ckfree(rp);
 	INTON;
 }
@@ -451,8 +455,17 @@  struct redirtab *pushredir(union node *redir)
 	sv = ckmalloc(sizeof (struct redirtab));
 	sv->next = q;
 	redirlist = sv;
-	for (i = 0; i < 10; i++)
-		sv->renamed[i] = EMPTY;
+	for (i = 0; i < 10; i++) {
+		int val = EMPTY;
+
+		if (q) {
+			val = q->renamed[i];
+			if (val > EMPTY)
+				val = EMPTY;
+		}
+
+		sv->renamed[i] = val;
+	}
 
 out:
 	return q;