Versions
- go version: upstream
656b5b3abe25 ("internal/poll: don't skip empty writes on Windows", 2025-03-28)
- gosh version: upstream
bf4faa20c0ea ("interp: clarify handler docs a bit", 2025-03-28)
Description
For constructing a pipeline of N commands, a shell performs something like this:
- call
pipe() N-1 times
- call
fork() N times
- in each child process:
- call
dup2() to duplicate the read end of the pipe to the left (if any) over fd#0
- call
dup2() to duplicate the write end of the pipe to the right (if any) over fd#1
- close all pipe fds from step 1
exec()
- in the parent, close all pipe fds from step 1
The bug is that gosh omits the last step, and it breaks pipeline behavior.
Reproducer
gosh
$ dash -c 'exec >/dev/null; exec sleep 3600' | cat >/dev/null &
$ ls -g -o /proc/$(pidof sleep)/fd/1
l-wx------. 1 64 Mar 28 19:19 /proc/781245/fd/1 -> /dev/null
$ ls -g -o /proc/$(pidof cat)/fd/0
lr-x------. 1 64 Mar 28 19:19 /proc/781246/fd/0 -> 'pipe:[592425]'
$ ls -g -o /proc/$$/fd | grep -F 'pipe:[592425]'
lr-x------. 1 64 Mar 28 19:20 3 -> pipe:[592425]
l-wx------. 1 64 Mar 28 19:20 4 -> pipe:[592425]
Explanation:
- The first command in the pipeline drops its fd#1 reference to the pipe. At this point, there should be no writers left for the pipe.
- However, the second command in the pipeline does not see EOF on the pipe; it blocks on reading the pipe.
- Turns out that gosh itself keeps the pipe open for writing (fd#4), which is what prevents
cat from seeing EOF.
cat hangs uselessly, as a result, instead of exiting.
- The problem occurs because gosh does not close file descriptors 3 and 4 right after the pipeline is constructed (step 4 at the top).
Expected behavior
dash
$ dash -c 'exec >/dev/null; exec sleep 3600' | cat >/dev/null &
$ ls -g -o /proc/$(pidof sleep)/fd/1
l-wx------. 1 64 Mar 28 19:25 /proc/781363/fd/1 -> /dev/null
$ ls -g -o /proc/$(pidof cat)/fd/0
ls: cannot access '/proc//fd/0': No such file or directory
$ ls -g -o /proc/$$/fd | grep -F 'pipe:'
(nothing)
Dash doesn't keep the pipe open (at all); thus, cat reads EOF (and exits), after the first command in the pipeline drops its reference -- the only reference -- to the write end of the pipe. This is the proper behavior.
Alternative reproducer
This one uses redirection operators with a named pipe (FIFO), instead of a pipeline.
Erroneous behavior
gosh
$ rm -f fifo
$ mkfifo fifo
$ dash -c 'exec >/dev/null; exec sleep 3600' >fifo &
$ cat <fifo >/dev/null &
$ ls -g -o /proc/$(pidof sleep)/fd/1
l-wx------. 1 64 Mar 28 19:35 /proc/781581/fd/1 -> /dev/null
$ ls -g -o /proc/$(pidof cat)/fd/0
lr-x------. 1 64 Mar 28 19:35 /proc/781580/fd/0 -> .../fifo
$ ls -g -o /proc/$$/fd | grep -F 'fifo'
l-wx------. 1 64 Mar 28 19:36 3 -> .../fifo
lr-x------. 1 64 Mar 28 19:36 4 -> .../fifo
Expected behavior
dash
$ rm -f fifo
$ mkfifo fifo
$ dash -c 'exec >/dev/null; exec sleep 3600' >fifo &
$ cat <fifo >/dev/null &
$ ls -g -o /proc/$(pidof sleep)/fd/1
l-wx------. 1 64 Mar 28 19:35 /proc/781581/fd/1 -> /dev/null
$ ls -g -o /proc/$(pidof cat)/fd/0
ls: cannot access '/proc//fd/0': No such file or directory
$ ls -g -o /proc/$$/fd | grep -F 'fifo'
(nothing)
Versions
656b5b3abe25("internal/poll: don't skip empty writes on Windows", 2025-03-28)bf4faa20c0ea("interp: clarify handler docs a bit", 2025-03-28)Description
For constructing a pipeline of N commands, a shell performs something like this:
pipe()N-1 timesfork()N timesdup2()to duplicate the read end of the pipe to the left (if any) over fd#0dup2()to duplicate the write end of the pipe to the right (if any) over fd#1exec()The bug is that
goshomits the last step, and it breaks pipeline behavior.Reproducer
Explanation:
catfrom seeing EOF.cathangs uselessly, as a result, instead of exiting.Expected behavior
Dash doesn't keep the pipe open (at all); thus,
catreads EOF (and exits), after the first command in the pipeline drops its reference -- the only reference -- to the write end of the pipe. This is the proper behavior.Alternative reproducer
This one uses redirection operators with a named pipe (FIFO), instead of a pipeline.
Erroneous behavior
Expected behavior