48

I want my program to open a file if it exists, or else create the file. I'm trying the following code but I'm getting a debug assertion at freopen.c. Would I be better off using fclose and then fopen immediately afterward?

FILE *fptr;
    fptr = fopen("scores.dat", "rb+");
    if(fptr == NULL) //if file does not exist, create it
    {
        freopen("scores.dat", "wb", fptr);
    } 
karoma
  • 1,548
  • 5
  • 24
  • 43
  • @tbert - I am as worried that people are not upvoting the answer that explains the failure. Thank God it's Friday :-) – gbulmer Mar 23 '12 at 14:57

2 Answers2

67

You typically have to do this in a single syscall, or else you will get a race condition.

This will open for reading and writing, creating the file if necessary.

FILE *fp = fopen("scores.dat", "ab+");

If you want to read it and then write a new version from scratch, then do it as two steps.

FILE *fp = fopen("scores.dat", "rb");
if (fp) {
    read_scores(fp);
}

// Later...

// truncates the file
FILE *fp = fopen("scores.dat", "wb");
if (!fp)
    error();
write_scores(fp);
Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • It looks like the OP is trying to open it in read-only mode if it already exists (I don't understand why they want to create it at all in that case, though). – James M Mar 23 '12 at 14:12
  • Well, if he doesn't want to write to it, he doesn't need to. As far as I know, there's no way to open read-only on the condition that it exists without creating a needless race condition. – Dietrich Epp Mar 23 '12 at 14:14
  • @JamesMcLaughlin I thought the rb+ will let me write also? After this section of code I need to read everything from the file, then later write the file from scratch. – karoma Mar 23 '12 at 14:15
11

If fptr is NULL, then you don't have an open file. Therefore, you can't freopen it, you should just fopen it.

FILE *fptr;
fptr = fopen("scores.dat", "rb+");
if(fptr == NULL) //if file does not exist, create it
{
    fptr = fopen("scores.dat", "wb");
}

note: Since the behavior of your program varies depending on whether the file is opened in read or write modes, you most probably also need to keep a variable indicating which is the case.

A complete example

int main()
{
    FILE *fptr;
    char there_was_error = 0;
    char opened_in_read  = 1;
    fptr = fopen("scores.dat", "rb+");
    if(fptr == NULL) //if file does not exist, create it
    {
        opened_in_read = 0;
        fptr = fopen("scores.dat", "wb");
        if (fptr == NULL)
            there_was_error = 1;
    }
    if (there_was_error)
    {
        printf("Disc full or no permission\n");
        return EXIT_FAILURE;
    }
    if (opened_in_read)
        printf("The file is opened in read mode."
               " Let's read some cached data\n");
    else
        printf("The file is opened in write mode."
               " Let's do some processing and cache the results\n");
    return EXIT_SUCCESS;
}
Shahbaz
  • 46,337
  • 19
  • 116
  • 182
  • 3
    This has a race condition, it will fail if another process creates the file between the two calls to `fopen`. – Dietrich Epp Mar 23 '12 at 14:13
  • @DietrichEpp Hadn't though of that! – Shahbaz Mar 23 '12 at 14:20
  • 2
    If the initial point "If `fptr` is `NULL`, then you don't have an open file. Therefore, you can't `reopen` it." explains the behaviour the OP is seeing (which I believe it does), then, for clarity, it might be worth editing the text to seperate that point from the subsequent approach to fixing it. (also reopen should be freopen) – gbulmer Mar 23 '12 at 14:38
  • 2
    @Shahbaz - you're welcome. I think the other answer fails to identify the key point that it will _never_ work. So folks might read that answer and their take away might be about race conditions, especially at it has been upvoted, when it is actually much much simpler. I could imagine spending hours trying to nail the race condition (which isn't there :-) – gbulmer Mar 23 '12 at 14:55