View Issue Details

IDProjectCategoryView StatusLast Update
00008161003.1(2013)/Issue7+TC1System Interfacespublic2019-08-29 16:09
Reportereblake Assigned To 
PrioritynormalSeverityObjectionTypeClarification Requested
Status ClosedResolutionAccepted 
NameEric Blake
OrganizationRed Hat
User Referenceebb.fflush
Sectionfflush
Page Number850
Line Number28391
Interp Status---
Final Accepted TextSee desired action
Summary0000816: does fflush(NULL) affect seekable read streams?
DescriptionAs a C extension, POSIX requires fflush(stream) to affect the underlying position of a seekable file descriptor associated with any input stream. But it is not obvious whether this effect is also required for fflush(NULL), even though such behavior is observable and desirable on POSIX platforms. The existing wording describes fflush(NULL) in terms of actions "defined above", but where the above text only describes output streams, and the CX behavior of input streams is described below, so someone could argue that input streams are unaffected by such a flush, but that contradicts existing behavior.

I noticed the problem while trying to explain why the program below violates XSH 2.5.1 (page 496) and therefore exhibits undefined behavior when called with 0 arguments [argc==1] (in particular, Solaris reports different output than glibc, and the program goes into an infinite loop on any build of cygwin from 2013. Cygwin has since been patched to behave more like glibc, although I still contend that glibc has never fully obeyed the rules regarding fflush on exit in the first place). The reason that the argc==1 case is undefined is that the first handle (the use of the stream prior to fork()) used fgetc() to alter the position of the stream, but did not fflush or fseek to reflect that position back to the underlying fd; the second stream then also alters the position of the stream (via the implicit flush done by the implicit exit() at the end of main) leaving the next byte read from the parents stream indeterminate. My immediate suggestion to insert an fflush(NULL) prior to fork() won't work if fflush(NULL) doesn't guarantee an effect on input streams [the behavior demonstrated with argc==3 - and evidence of the glibc bug of not flushing on exit], but for all systems that already comply with POSIX, my testing showed identical to the behavior of fflush(fp) [argc==2], which is why I prefer mandating the behavior even if it further points out problems in the glibc implementation.

$ cat foo.c
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    FILE *fp = fopen("foo.c", "r");
    printf("parent: %d\n", (int)getpid());
    fgetc(fp);
    switch (argc) {
    case 2:
        fflush(fp);
        break;
    case 3:
        fflush(NULL);
        break;
    }
    switch (fork()) {
    case -1:
        _exit(1);
    case 0:
        break;
    default:
        sleep(1);
    }
    printf("process %d, offset %d\n", (int)getpid(),
           (int)lseek(fileno(fp), 0, SEEK_CUR));
    return 0;
}

Linux:
$ ./foo
parent: 24952
process 24953, offset 526
process 24952, offset 526
$ ./foo 1
parent: 24960
process 24961, offset 1
process 24960, offset 1
$ ./foo 1 2
parent: 25702
process 25703, offset 526
process 25702, offset 526

Solaris:
$ ./foo
parent: 16333
process 16334, offset 526
process 16333, offset 1
$ ./foo 1
parent: 16336
process 16337, offset 1
process 16336, offset 1
$ ./foo 1 2
parent: 16347
process 16348, offset 1
process 16347, offset 1

Intuitively, just as:
putc('a');
if (cond)
  fflush(NULL);
fork();
exit();
needs 'cond' to be non-zero to guarantee that 'a' is not output twice, the counterpart for reading needs fflush(NULL) before fork to guarantee that 'a' is not read twice.

And the reason that Linux behavior is buggy is that we require that:

{ sed -n 1q; sed -n 1q; cat; } < file

behave the same as

tail -n+3 file

for any seekable file, but because Linux fails to reset the position of fds on normal exit(), the subsequent processes in the group do not pick up processing where the earlier processes left off.
Desired ActionOn page 850 (XSH fflush DESCRIPTION), swap lines 28391-2 with lines 28393-7. The resulting order of the two paragraphs (as shown below) then makes it clear that fflush(NULL) must affect underlying positions of seekable fds associated with read streams:

For a stream open for reading, 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.
Tagstc2-2008

Relationships

related to 0000087 Closedajosey 1003.1(2008)/Issue 7 fflush and ungetc 
related to 0000701 Closed 1003.1(2013)/Issue7+TC1 unget[w]c() and file position after discarding push back 

Activities

eblake

2014-01-17 22:04

manager   bugnote:0002108

For anyone that read the original problem description, I ended up tweaking it to fix some errors in my writeup that I noticed in subsequent testing.

Issue History

Date Modified Username Field Change
2014-01-17 21:38 eblake New Issue
2014-01-17 21:38 eblake Name => Eric Blake
2014-01-17 21:38 eblake Organization => Red Hat
2014-01-17 21:38 eblake User Reference => ebb.fflush
2014-01-17 21:38 eblake Section => fflush
2014-01-17 21:38 eblake Page Number => 850
2014-01-17 21:38 eblake Line Number => 28391
2014-01-17 21:38 eblake Interp Status => ---
2014-01-17 21:52 eblake Description Updated
2014-01-17 22:03 eblake Description Updated
2014-01-17 22:04 eblake Note Added: 0002108
2014-01-17 22:13 eblake Relationship added related to 0000087
2014-03-13 16:00 nick Final Accepted Text => See desired action
2014-03-13 16:00 nick Status New => Resolved
2014-03-13 16:00 nick Resolution Open => Accepted
2014-03-13 16:00 nick Tag Attached: tc2-2008
2014-03-13 16:01 nick Desired Action Updated
2019-06-10 08:54 agadmin Status Resolved => Closed
2019-08-29 16:09 eblake Relationship added related to 0000701