Search code examples
cglibcstdio

Using rewind() on a FILE* opened with fmemopen


Solved with glibc 2.24 -- See UPDATE below

Here is a piece of C-Code (compiled with gcc 5.3.1, glibc 2.23):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

int main() {
  const char* s1="Original content of file.\n"
                 "still original content and some remaining original content.\n";
  const char* s2="overwriting data  with new content\n";
  const char* s3="appended data\n";

  const size_t bufsz=strlen(s1)+1;
  char buf[bufsz];
  FILE *f;
  int r;

  f=fmemopen(buf,bufsz,"w");
  assert(f!=NULL);

  // setbuf(f, NULL);          // variant no. 1
  // setbuffer(f, buf, bufsz); // variant no. 2

  r=fwrite(s1,strlen(s1),1,f);
  assert(r==1);

  r=fflush(f);
  assert(r==0);

  rewind(f);

  r=fwrite(s2,strlen(s2),1,f);
  assert(r==1);

  r=fwrite(s3,strlen(s3),1,f);
  assert(r==1);

  r=fclose(f);
  assert(r==0);

  printf("%s",buf);
}
  • It does what I expect -- The output is:

    overwriting data  with new content
    appended data
    and some remaining original content.
    
  • Now, the manpage fmemopen(3) advices to either disable buffering (uncomment variant 1), or to explicitely set buf as buffer (uncomment variant 2).

    However, in both cases, I get as result:

    appended data
    ta  with new content
    ginal content and some remaining original content.
    

    Thus, the appended data was not written after the second content, as expected, but did overwrite the second content.

  • The behaviour remains the same if I open the file in binary mode (i.e. replacing the "w" mode by "wb").

  • valgrind does not report any errors (Except a false positive "Source and destination overlap" in the case of buffering. The latter one is due to the try to write the STDIO buffer into the memory, which is indeed the same address.)

What is wrong? Did I make a mistake? Or is this a GLIBC bug?

UPDATE

On August 4th, a new glibc version 2.24 was released. With gcc 5.4.0 and glibc 2.24, variant no. 1 (unbuffered FILE) works fine. Variant 2 (self-buffered version) gives a different, but still erroneous result. Thus I believe R.. is right in claiming that this is a documentation bug in the manpage fmemopen(3). I will raise a bug report ...


Solution

  • Now, the manpage fmemopen(3) advices to either disable buffering (uncomment variant 1), or to explicitely set buf as buffer (uncomment variant 2).

    The latter is most certainly undefined behavior. Don't do it. It's not clear to me what the text you found in the Linux man page:

    Alternatively, the caller can explicitly set buf as the stdio stream buffer, at the same time informing stdio of the buffer's size, using:

    setbuffer(stream, buf, size);
    

    is trying to express, but it's a documentation bug and should probably just be removed.

    As for unbuffered mode not working, this is probably a glibc bug. Try to make the test case as minimal as you can, test against a recent glibc, and if it still happens, submit a bug report on the glibc bugzilla:

    https://sourceware.org/bugzilla/enter_bug.cgi?product=glibc

    A safe alternative when you want to know immediately about errors is just calling fflush and checking the return value.