13

I wish to open a file using the "a+b" mode, i.e. if it does not exist it is created automatically, but if it does I don't want to overwrite it. I want to be able to read and write to the file.

The file is binary, and I want to save records of a specific struct in it. So I want to do fseek() to the record I want and then save the record using fwrite().

The code looks as follows (MyRecord is a typedef to a struct, while FILENAME is a #define to the file's name):

int saveRecord(MyRecord *pRecord, int pos)
{
    FILE* file = fopen(FILENAME, "a+b");
    if (file == NULL)
    {
        printf("Unable to open file %s\n", FILENAME);
        return 0;
    }

    fseek(file, pos * sizeof(MyRecord), SEEK_SET);
    fwrite(pRecord, sizeof(MyRecord), 1, file);
    fclose(file);
    return 1;
}

However this code just appends the record to the end of the file, even if I set pos to 0. Why isn't fseek() with SEEK_SET working in append mode?

I know I can simply open it with "r+b" and if it fails open it with "wb", but I want to know why this doesn't work and why fseek() with SEEK_SET is leaving the file pointer at the end. Any references to places where this behaviour is documented appreciated (because I couldn't find any, or I am using the wrong keywords).

jbx
  • 21,365
  • 18
  • 90
  • 144

3 Answers3

20

That's because in a mode, writing to the FILE* always appends to the end. fseek only sets the read pointer in this mode. This is documented in the C standard, 7.19.5.3 fopen:

Opening a file with append mode ('a' as the first character in the mode argument) causes all subsequent writes to the file to be forced to the then current end-of-file, regardless of intervening calls to the fseek function.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
4

Use "r+b" mode and fallback to "w+b" if it fails.

The "a+b" mode, allows you to read and append; the "r+b" allows random read and write.

The documentation for fopen describes how the file behaves with the different modes.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
pmg
  • 106,608
  • 13
  • 126
  • 198
  • This has a race condition that will clobber your file. – R.. GitHub STOP HELPING ICE Apr 03 '11 at 21:09
  • @R..: what race condition? should I say "fallback to "wb" if it fails with `errno == ENOENT`" – pmg Apr 03 '11 at 21:55
  • I'm assuming a multitasking OS where another process could create the file and fill it with data after the first `fopen` fails but before the second `fopen` attempt. If OP is dealing with an embedded system or any closed system where it's known that this won't happen, it may be a non-issue. – R.. GitHub STOP HELPING ICE Apr 04 '11 at 04:27
  • I see @R.. thanks. If that's an issue `fwrite` to the same position at the same time by different processes is also an issue; the OP could use the same method to manage both issues. – pmg Apr 04 '11 at 08:30
4

Plain C does not have any sane way to achieve what you want. If you're on a POSIX system or anything remotely close, you can use fd=open(FILENAME, O_CREAT|O_RDRW, 0666) and then fdopen(fd, "rb+").

Edit: Another thing you could try, with plain C:

f = fopen(FILENAME, "a+b");
if (!f) /* ... */
tmp = freopen(0, "r+b", f);
if (tmp) f = tmp;
else /* ... */
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Yes I guess that would work. I need this to work on pure ANSI C though. Still +1 for you for the good alternative. – jbx Apr 03 '11 at 21:14
  • Have you tried the `freopen` solution? No idea if it works in practice (`freopen` is ill-specified and could in theory be useless) but it might be ok. – R.. GitHub STOP HELPING ICE Apr 03 '11 at 21:36