misc/137819: fpurge violates stdio invariant

Eric Blake ebb9 at byu.net
Sun Aug 16 03:50:02 UTC 2009

>Number:         137819
>Category:       misc
>Synopsis:       fpurge violates stdio invariant
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sun Aug 16 03:50:00 UTC 2009
>Originator:     Eric Blake
>Release:        6.1
FreeBSD freebsd6 6.1-RELEASE FreeBSD 6.1-RELEASE #0: Sun May  7 04:42:56 UTC 2006     root at opus.cse.buffalo.edu:/usr/obj/usr/src/sys/SMP  i386

According to the stdio source code, all streams must meet the invariant that if a stream is open for both reading and writing, then _w is 0 if the stream is not currently writing.  If this invariant is violated, then code like putc will misbehave, and fflush will not realize that there is data to be written.  Unfortunately, fpurge blindly sets _w to non-zero even on read-write file streams where pending read data was flushed.  This in turn requires the gnulib fpurge module to write a wrapper around fpurge and poke at FILE internals in a number of GNU projects.
$ cat foo.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char **argv)
  FILE *f = fopen("bar", "w+");
  fputs ("abc", f);
  rewind (f);
  while (EOF != fgetc (f));
  fpurge (f); /* Nothing to purge, but stream state now corrupted.  */
  argc > 1 ? putc ('d', f) : fputc ('d', f);
  return 0;
$ ./foo
$ cat bar && echo
$ ./foo 1
$ cat bar && echo

Expected behavior - both './foo' and './foo 1' should set the contents of bar to "abcd".  However, fpurge mistakenly set the _w member to nonzero, while leaving the __SRD flag set (rather than setting the __SWR flag), such that subsequent putc use the buffer without kicking the stream over to write mode, and the implicit fflush/fclose at program exit see that the stream is in read mode and assume that nothing needs to be flushed.

In fpurge.c, replace this line:

		fp->_w = fp->_flags & (__SLBF|__SNBF) ? 0 : fp->_bf._size;


		fp->_w = fp->_flags & (__SLBF|__SNBF|__SRD) ? 0 : fp->_bf._size;


More information about the freebsd-bugs mailing list