Normally, to indicate EOF to a program attached to standard input on a Linux terminal, I need to press Ctrl+D once if I just pressed Enter, or twice otherwise. I noticed that the patch
command is different, though. With it, I need to press Ctrl+D twice if I just pressed Enter, or three times otherwise. (Doing cat | patch
instead doesn't have this oddity. Also, If I press Ctrl+D before typing any real input at all, it doesn't have this oddity.) Digging into patch
's source code, I traced this back to the way it loops on fread
. Here's a minimal program that does the same thing:
#include <stdio.h>
int main(void) {
char buf[4096];
size_t charsread;
while((charsread = fread(buf, 1, sizeof(buf), stdin)) != 0) {
printf("Read %zu bytes. EOF: %d. Error: %d.\n", charsread, feof(stdin), ferror(stdin));
}
printf("Read zero bytes. EOF: %d. Error: %d. Exiting.\n", feof(stdin), ferror(stdin));
return 0;
}
When compiling and running the above program exactly as-is, here's a timeline of events:
- My program calls
fread
. fread
calls theread
system call.- I type "asdf".
- I press Enter.
- The
read
system call returns 5. fread
calls theread
system call again.- I press Ctrl+D.
- The
read
system call returns 0. fread
returns 5.- My program prints
Read 5 bytes. EOF: 1. Error: 0.
- My program calls
fread
again. fread
calls theread
system call.- I press Ctrl+D again.
- The
read
system call returns 0. fread
returns 0.- My program prints
Read zero bytes. EOF: 1. Error: 0. Exiting.
Why does this means of reading stdin have this behavior, unlike the way that every other program seems to read it? Is this a bug in patch
? How should this kind of loop be written to avoid this behavior?
UPDATE: This seems to be related to libc. I originally experienced it on glibc 2.23-0ubuntu3 from Ubuntu 16.04. @Barmar noted in the comments that it doesn't happen on macOS. After hearing this, I tried compiling the same program against musl 1.1.9-1, also from Ubuntu 16.04, and it didn't have this problem. On musl, the sequence of events has steps 12 through 14 removed, which is why it doesn't have the problem, but is otherwise the same (except for the irrelevant detail of readv
in place of read
).
Now, the question becomes: is glibc wrong in its behavior, or is patch wrong in assuming that its libc won't have this behavior?
fread()
? – Schellcharsread
is? – Schellread()
. If there's no buffered input, it makes zero bytes available, and zero bytes read indicates EOF. – Yuilleread
is returning zero twice. Still looking at GNU libc source.... – Afterword1
and thesizeof(buf)
arguments. If you put them the other way round, it doesn't happen. Interesting puzzle to work out what is happening underneath though... – Albinofgets()
to read line by line. – Schellprintf()
in the loop body, and when I swapped the order I got no output at all. The program just terminated when I typed Ctl-d at the beginning of the second line. – Schellecho hello | ./a.out
? -->it is a terminal thing,or an atifact. – Outerread()
again, and that won't normally happen unless you delay the loop (that's how tail -f works). – Schell