Introduction
The FILE stream object is a convenient way to work with files as it handles buffering automatically and has a good range of functions that allow programmers to read and write complex structured data into files. With great convenience comes even greater complication and this article is an attempt to tame some of it, specifically with respect to maintaining the file offset in a stream object.
Inactive FILE handles
A file stream, on creation with fopen or fdopen, is not considered active till an activating action is performed on it. An activating action could either be an attempt to read, write or an offset change. This activation point is important because before the stream object is active, no assumptions can be made about what the status of the underlying file is. That is, it is entirely possible that the application may move the file offset from under your nose after a call to fopen and it is the responsibility of the stream object implementation to make sure that the change is visible via the stream object. This is why in the summary table below the offset can't be changed nor cached when the file stream is inactive (all operation==inactive entries are always no), and why the offset must be read each time.
Append Mode
The other problem in maintaining the offset comes up when the file is in append mode. Writes to a file in append mode always happen at the end of the file. Reads however can be from anywhere in the file, as long as one could seek to that offset. Additionally, even if reading is not allowed in a file (i.e. in append-only mode), it is perfectly valid for a program to seek to an arbitrary location in the file and ftell is supposed to be able to tell you the correct offset.
Now consider a situation where the stream object in append mode has just switched from read mode to write mode, with the writes not having been flushed to file. The file offset will not be at the end of the file and returning the actual file offset at this stage would be incorrect. The right thing to do here would be to seek to the end of file and then compute the offset. Incidentally, this is the only state change we ought to be affecting in ftell; every other operation should either compute the offset from the underlying file handle or use the cached offset.
The final caveat with append mode files is the initial file position. The standard leaves this detail to be implementation defined, i.e. the implementation may choose to either seek to the end of the file immediately or leave the file offset as is. The historical behaviour in glibc has been to seek to end of file for append-only streams and leave the offset as is for append+read mode streams.
Summary
All this can be summarized in a table for each of the major modes. The equivalent binary modes (rb, rb+, etc) should have the same behaviour as the core modes. The Operations in the table are operations after which an ftell call may be made. Inactive is when the FILE handle is inactive and write indicates when the stream is in write mode. read and seek have been clubbed together because the glibc implementation of the seek operations (fseek, rewind) switch the stream to read mode by flushing the buffer into file if necessary.
There are three things that ftell can do when it determines the current stream offset, which is to change the underlying file offset, use the cached offset if available or find the offset of the underlying file and finally, update the offset cache after either querying the offset or changing the offset.
Mode |
Operation |
Change Offset |
Use Cached Offset |
Update Offset Cache |
r |
inactive |
no |
no |
no |
r |
read/seek |
no |
yes |
no |
r+ |
inactive |
no |
no |
no |
r+ |
read/seek |
no |
yes |
no |
r+ |
write |
no |
yes |
no |
w |
inactive |
no |
no |
no |
w |
seek |
no |
yes |
no |
w |
write |
no |
yes |
no |
w+ |
inactive |
no |
no |
no |
w+ |
read/seek |
no |
yes |
no |
w+ |
write |
no |
yes |
no |
a |
inactive |
no* |
no |
no |
a |
seek |
no |
yes |
no |
a |
write |
yes |
yes |
yes |
a+ |
inactive |
no |
no |
no |
a+ |
read/seek |
no |
yes |
no |
a+ |
write |
yes |
yes |
yes |
* In append-only mode, the offset change is not done by ftell, but an fopen or fdopen will send the file offset to SEEK_END.