fflush(NULL) and POSIX compliance

161 Views Asked by At

TL;DR: Is it posixly incorrect for fflush(NULL) not to flush stdin?

Consider the following code snippet,

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    char line[128] = {0};
    fgets(line, sizeof(line), stdin);
    puts(line);
    fflush(NULL);
    pid_t pid = fork();
    if (pid == 0) {
        fgets(line, sizeof(line), stdin);
        puts(line);
        exit(EXIT_SUCCESS);
    } else {
        waitpid(pid, NULL, 0);
        fgets(line, sizeof(line), stdin);
        puts(line);
    }
    return 0;
}

When fed a file with 3 lines using file redirection, it exhibits different behaviour on macOS and Linux,

$ cat test
line 1
line 2
line 3
macOS $ ./a.out < test
line 1

line 2

line 3

Linux $ ./a.out < test
line 1

line 2

line 2

The reason seems to be that glibc fflush(NULL) does not flush stdin although fflush(stdin) is well defined. glibc manual reads,

If stream is a null pointer, then fflush causes buffered output on all open output streams to be flushed.

while POSIX states,

For a stream open for reading with an underlying file description, if the file is not already at EOF, and the file is one capable of seeking, the file offset of the underlying open file description shall be set to the file position of the stream, and any characters pushed back onto the stream by ungetc() or ungetwc() that have not subsequently been read from the stream shall be discarded (without further changing the file offset).

If stream is a null pointer, fflush() shall perform this flushing action on all streams for which the behavior is defined above.

macOS libc source glibc source musl source

I am suspecting it to be a non-compliance bug in glibc/musl, but considering the enormous install base, I wonder if it is indeed against POSIX?

1

There are 1 best solutions below

4
John Bollinger On

TL;DR: Is it posixly incorrect for fflush(NULL) not to flush stdin?

You have already quoted the relevant POSIX provisions.

So, with respect to your example,

  • Is stdin a stream open for reading? Yes.
  • Does it have an underlying open file description? It should, since you're redirecting from a regular file.
  • Is that file capable of seeking? It should be, since you're redirecting from a regular file.
  • Is the file already at EOF? Here's the rub. It says "the file" not "the stream". When the standard input is connected to a regular file, it defaults to being fully buffered, and that buffer is probably large enough to accommodate your full example input. Thus, in your example program, it is likely that after the first read from stdin, the underlying file is at EOF, in which case POSIX does not specify any behavior for fflushing it.

More generally, then, there are conditions under which POSIX does not mandate that fflush(NULL) should flush stdin:

  • if it does not have an underlying open file description (though I'm unsure under what circumstances that would happen)

  • if the underlying file is incapable of seeking -- this is typically the case when stdin is connected to a terminal, for example

  • if the underlying file has already reached EOF

On the other hand, if Glibc's fflush(NULL) fails to flush stdin when all of the POSIX criteria are satisfied, then yes, that is posixly incorrect.