Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The use of "r+" in fopen on windows vs linux

I was toying around with some code which was opening, reading, and modifying a text file. A quick (simplified) example would be:

#include <stdio.h>
int main()
{
    FILE * fp = fopen("test.txt", "r+");
    char line[100] = {'\0'};
    int count = 0;
    int ret_code = 0;
    while(!feof(fp)){
        fgets(line, 100, fp);
        // do some processing on line...
        count++;
        if(count == 4) {
          ret_code = fprintf(fp, "replaced this line\n");
          printf("ret code was %d\n", ret_code);
          perror("Error was: ");
        }
    }
    fclose(fp);
    return 0;
}

Now on Linux, compiled with gcc (4.6.2) this code runs, and modifies the file's 5th line. The same code, running on Windows7 compiled with Visual C++2010 runs and claims to have succeeded (reports a return code of 19 characters and perror says "No error") but fails to replace the line.

On Linux my file has full permissions:

-rw-rw-rw- 1 mike users 191 Feb 14 10:11 test.txt

And as far as I can tell it's the same on Windows:

test.txt (right click) -> properties -> Security
"Allow" is checked for Read & Write for user, System, and Admin.

I get the same results using MinGW's gcc on Windows so I know it's not a Visual C++ "feature".

Am I missing something obvious, or is the fact that I get no errors, but also no output just an undocumented "feature" of using r+ with fopen() on Windows?


EDIT:
Seems even at Microsoft's site they say "r+" should open for reading and writting. They also made this note:

When the "r+", "w+", or "a+" access type is specified, both reading and writing are allowed (the file is said to be open for "update"). However, when you switch between reading and writing, there must be an intervening fflush, fsetpos, fseek, or rewind operation. The current position can be specified for the fsetpos or fseek operation, if desired.

So I tried:

        ...
        if(count == 4) {
          fflush(fp);
          ret_code = fprintf(fp, "replaced this line\n");
          fflush(fp);
          printf("ret code was %d\n", ret_code);
          ...

to no avail.

like image 943
Mike Avatar asked Feb 14 '13 15:02

Mike


2 Answers

According to the Linux man page for fopen():

Reads and writes may be intermixed on read/write streams in any order. Note that ANSI C requires that a file positioning function intervene between output and input, unless an input operation encounters end-of-file. (If this condition is not met, then a read is allowed to return the result of writes other than the most recent.) Therefore it is good practice (and indeed sometimes necessary under Linux) to put an fseek(3) or fgetpos(3) operation between write and read operations on such a stream. This operation may be an apparent no-op (as in fseek(..., 0L, SEEK_CUR) called for its synchronizing side effect.

So, you should always call fseek() (as, eg. fseek(..., 0, SEEK_CUR)) when switching between reading and writing from a file.

like image 122
Hasturkun Avatar answered Oct 19 '22 04:10

Hasturkun


Before performing output after input, an fflush() isn't any good - you need to perform a seek operation. Something like:

fseek(fp, ftell(fp), SEEK_SET); // not fflush(fp);

from the C99 standard (7.19.5.3/6 "The fopen functoin):

When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file.

like image 37
Michael Burr Avatar answered Oct 19 '22 02:10

Michael Burr