'correct' semantics for ftell() when used on a memory stream

446 views Asked by At

Can anyone explain the 'correct' semantics for ftell() when used on a memory stream.

Given the following program:

#include <stdio.h>
#include <stdlib.h>
#include <gnu/libc-version.h>

int main(void)
{
   puts (gnu_get_libc_version ());

   size_t n_buffer = 1024;
   char *buffer = calloc(n_buffer, sizeof(char));
   FILE *file = fmemopen(buffer, n_buffer, "w");

   /* "ABCD" */
   static const char magic_number[] = 
   {
     0x41, 0x42, 0x43, 0x44 
   };

   const size_t written = fwrite(magic_number, 1, 4, file);
   fprintf(stderr,"written=%d\n",written);

   int fstatus = fflush(file);
   fprintf(stderr,"fstatus=%d\n",fstatus);

   long ftellpos = ftell(file);
   fprintf(stderr,"ftellpos=%ld\n",ftellpos);

   fstatus = fseek(file, 0, SEEK_END);
   fprintf(stderr,"fstatus=%d\n",fstatus);

   ftellpos = ftell(file);
   fprintf(stderr,"ftellpos2=%ld\n",ftellpos);

   return 0;
}

The output on RHEL7 is:

2.17
written=4
fstatus=0
ftellpos=4
fstatus=0
ftellpos2=4

Whereas the output on OpenSUSE Leap 42 is:

2.22
written=4
fstatus=0
ftellpos=0
fstatus=0
ftellpos2=4

(This led to a unit test failure in code I was looking at)

My questions are:

  • Is the fseek() required (by a standard) to make the result of ftell() valid?
  • Is this a bug or change in behaviour of glibc?
  • Why doesn't it work on OpenSUSE?

The most obvious implementation is for the file position indicator to be an index in the memory buffer given to fmemopen. Its hard to see how that could go wrong.

Indeed the implementation:

https://github.com/bminor/glibc/blob/73dfd088936b9237599e4ab737c7ae2ea7d710e1/libio/fmemopen.c

Has c->pos = pos + s; at line 85.

And presumably ftell() just returns c->pos (in a roundabout way)

There has been some re-organisation of the glibc source source between 2.17 and 2.22 that would probably explain this if I could unravel it. But is it a bug or feature?

I'm not sure if the Posix and C standards fully specify whether ftell should work correctly for a memory stream. Intuitively its hard to see why it shouldn't be mandated as it ought to just work.

http://man7.org/linux/man-pages/man3/fmemopen.3.html

Says:

"The current position is implicitly updated by I/O operations. It can be explicitly updated using fseek(3), and determined using ftell(3)."

Other man pages mention that ftell might not have to work for things that aren't really files. However, I believe they really have devices in mind there.

1

There are 1 answers

8
Serge On

Just found this quote on the net in a discussion:

The ftell() Open Group Base Specifications Issue 7 doc states ‘ftell() shall return the current value of the file-position indicator for the stream.’ The file position indicator is not updated without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), or until the buffer is full.

So, it looks like there is some buffer handling difference in rh and suse. You would need to flush the buffer in a way to read the correct position in the file.