tckmn
tckmn

Reputation: 59343

How can I safely write to a file unless it exists in C?

I'm trying to do something such as the following:

FILE* f = fopen_unless_exists("example.txt");

if (f != NULL) {
    fprintf(f, "foo bar baz\n");
} else {
    // f should be NULL if example.txt already exists
    fprintf(stderr, "Error: cannot write to file or file already exists");
}

I could of course use one of the methods mentioned in a related question, but as stated in the comments on the answers there, this would be a race condition (specifically, TOCTOU).

What's the simplest way to safely create and write to a file, unless the file already exists, without creating a race condition?

Upvotes: 3

Views: 368

Answers (3)

tchrist
tchrist

Reputation: 80443

You have to use the open(2) syscall with O_EXCL|O_CREAT|O_WRONLY and then call fdopen(3) on that descriptor.

#include <sys/errno.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char *program  = argv[0];
    char *filename = argv[1];

    if (argc != 2) {
        fprintf(stderr, "%s: expected a single file argument.\n", program);
        exit(1);
    }   

    int flags = O_EXCL|O_CREAT|O_WRONLY;
    int mode  = 0666;

    int fd; 
    if ((fd = open(filename, flags, mode)) == -1) {
        fprintf(stderr, "%s: cannot open %s with flags 0x%04X: %s\n",
            program, filename, flags, strerror(errno));
        exit(2);
    }   

    FILE *fp = fdopen(fd, "w");
    if (fp == NULL) {
        fprintf(stderr, "%s: cannot fdopen file descriptor %d: %s\n",
            program, fd, strerror(errno));
        exit(3);
    }   

    if (fprintf(fp, "12345\n") == EOF) {
        fprintf(stderr, "%s: cannot write to %s: %s\n",
            program, filename, strerror(errno));
        exit(4);
    }   

    if (fclose(fp) == EOF) {
        fprintf(stderr, "%s: cannot close %s: %s\n",
            program, filename, strerror(errno));
        exit(5);
    }   

    exit(0);
}

Those open(2) flags are amongst the few guaranteed by POSIX.

This is not guaranteed to work reliably on remote filesystems. In particular, NFS breaks the POSIX rules about atomic creats. [sic]

Upvotes: 1

Dai
Dai

Reputation: 155648

There is no cross-platform, portable way to do this in pure C as C's standard library lacks advanced IO operations, such as file-locking, nor a systemwide mutex library. C doesn't even have a "list directories" function.

I feel the best solution would be to handle the error condition by prompting the user to intervene and let them choose how to proceed (e.g. delete the file, force overwrite, etc).

A more fully-featured IO library is present in C++11 (...finally) but that would mean abandoning pure-C. Another alternative is to use a cross-platform wrapper library that wraps platform-specific IO functions.

Upvotes: 0

NZD
NZD

Reputation: 1970

If you use the GNU C library, then the call fopen("filename", "xw+") probably does what you want. The string x does the following:

x: Open the file exclusively (like the O_EXCL flag of open(2)). If the file already exists, fopen() fails, and sets errno to EEXIST. This flag is ignored for fdopen().

The function of the other option is:

w+: Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file.

You can also use:

a: Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file.

a+: Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.

There are even more options. See fopen for details

Upvotes: 0

Related Questions