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