When a process sets O_NONBLOCK on its stdin, that setting
applies to the entire file description, causing `read` of that
file description (even in other processes to possibly return EAGAIN.
`bash` tries to work around this by resetting `O_NONBLOCK` on
its input file descriptors, e.g. at startup in shell.c:

shell.c:1732
```
  /* Make sure the fd from which we are reading input is not in
     no-delay mode. */
  if (interactive == 0)
    sh_unset_nodelay_mode (default_buffered_input);
  else
    sh_unset_nodelay_mode (fileno (stdin));
```

However, this is insufficient if the O_NONBLOCK is set by one
of bash's child processes. To work around this, `bash` attempts
to re-reset this flag whenever it sees EAGAIN:

input.c:96
```
              if (sh_unset_nodelay_mode (fileno (stream)) < 0)
                {
                  sys_error (_("cannot reset nodelay mode for fd %d"), fileno 
(stream));
                  local_index = local_bufused = 0;
                  return EOF;
                }
```

parse.y:1679
```
      sh_unset_nodelay_mode (fileno (rl_instream));     /* just in case */
```

However, this kind of reset is missing in `b_fill_buffer`, causing
trouble if the input is a non-interactive fifo:

```
cat <<EOF > /tmp/set_nonblock.c
int main() {
        fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
        return 0;
}
EOF
gcc -o /tmp/set_nonblock /tmp/set_nonblock.c

rm -f /tmp/test_fifo
mkfifo /tmp/test_fifo

bash < /tmp/test_fifo &
BASH_PID=$!

sleep 0.5

exec 3>/tmp/test_fifo
echo '/tmp/set_nonblock' >&3
sleep 0.2
echo 'echo hello1' >&3
sleep 0.2
echo 'echo hello2' >&3
sleep 0.2
echo 'echo hello3' >&3
exec 3>&-

wait $BASH_PID 2>/dev/null
echo "Exit code: $?"
rm -f /tmp/test_fifo
```

This gives on my machine:
```
[1] 1899972
bash: error reading input file: Resource temporarily unavailable
[1]+  Exit 2                  bash < /tmp/test_fifo
```

The provided patch adds the same retry logic already present in
the `getc_with_restart` into the buffer fill logic as well,
fixing the above reproducer.

Signed-off-by: Keno Fischer <[email protected]>
Reported-by: Erick Friis <[email protected]>
---
 input.c | 57 ++++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 38 insertions(+), 19 deletions(-)

diff --git a/input.c b/input.c
index 9995b94f..d63701a2 100644
--- a/input.c
+++ b/input.c
@@ -495,33 +495,52 @@ b_fill_buffer (BUFFERED_STREAM *bp)
   if (bp->b_flag & B_ERROR)    /* try making read errors `sticky' */
     return EOF;
 
-  /* In an environment where text and binary files are treated differently,
-     compensate for lseek() on text files returning an offset different from
-     the count of characters read() returns.  Text-mode streams have to be
-     treated as unbuffered. */
-  if ((bp->b_flag & (B_TEXT | B_UNBUFF)) == B_TEXT)
+  while (1)
     {
-      o = lseek (bp->b_fd, 0, SEEK_CUR);
-      nr = zread (bp->b_fd, bp->b_buffer, bp->b_size);
-      if (nr > 0 && nr < lseek (bp->b_fd, 0, SEEK_CUR) - o)
+      /* In an environment where text and binary files are treated differently,
+         compensate for lseek() on text files returning an offset different 
from
+         the count of characters read() returns.  Text-mode streams have to be
+         treated as unbuffered. */
+      if ((bp->b_flag & (B_TEXT | B_UNBUFF)) == B_TEXT)
        {
-         lseek (bp->b_fd, o, SEEK_SET);
-         bp->b_flag |= B_UNBUFF;
-         bp->b_size = 1;
+         o = lseek (bp->b_fd, 0, SEEK_CUR);
          nr = zread (bp->b_fd, bp->b_buffer, bp->b_size);
+         if (nr > 0 && nr < lseek (bp->b_fd, 0, SEEK_CUR) - o)
+           {
+             lseek (bp->b_fd, o, SEEK_SET);
+             bp->b_flag |= B_UNBUFF;
+             bp->b_size = 1;
+             nr = zread (bp->b_fd, bp->b_buffer, bp->b_size);
+           }
        }
-    }
-  else
-    nr = zread (bp->b_fd, bp->b_buffer, bp->b_size);
-  if (nr <= 0)
-    {
+      else
+       nr = zread (bp->b_fd, bp->b_buffer, bp->b_size);
+
+      if (nr > 0)
+       break;
+
       bp->b_used = bp->b_inputp = 0;
       bp->b_buffer[0] = 0;
       if (nr == 0)
-       bp->b_flag |= B_EOF;
+       {
+         bp->b_flag |= B_EOF;
+         return (EOF);
+       }
+      else if (errno == X_EAGAIN || errno == X_EWOULDBLOCK)
+       {
+         if (sh_unset_nodelay_mode (bp->b_fd) < 0)
+           {
+             sys_error (_("cannot reset nodelay mode for fd %d"), bp->b_fd);
+             bp->b_flag |= B_ERROR;
+             return (EOF);
+           }
+         continue;
+       }
       else
-       bp->b_flag |= B_ERROR;
-      return (EOF);
+       {
+         bp->b_flag |= B_ERROR;
+         return (EOF);
+       }
     }
 
   bp->b_used = nr;
-- 
2.43.0


Reply via email to