How can I safely write to a file if it doesn't exist in C?

I am trying to do something like 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 the related question , but as pointed out in the comments to the answers there, it would be a race condition ( TOCTOU in particular ).

What's the easiest way to safely create and write to a file if the file doesn't already exist, without creating a race condition?

+3


source to share


3 answers


You have to use 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);
}

      



These public (2) flags are guaranteed by POSIX .

This does not guarantee reliable operation with remote file systems. In particular, NFS violates POSIX rules about atoms. [Sic]

+1


source


There is no cross-platform, portable way to do this in pure C, since the C standard library has no advanced I / O like file locking, nor does it have a system-wide mutex library. C doesn't even have a Directory List feature.

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



A more fully featured I / O library is present in C ++ 11 (... finally), but that means dropping pure-C. Another alternative is to use a cross-platform wrapper library that wraps the I / O functionality for the platform.

0


source


If you are using the GNU C library, the call fopen("filename", "xw+")

probably does what you want. The line x

does the following:

x

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

Other option function:

w+

: open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is located at the beginning of the file.

You can also use:

a

: Open for adding (entry at the end of the file). The file is created if it does not exist. The stream is located at the end of the file.

a+

: Open for reading and adding (write at the end of the file). The file is created if it does not exist. The starting position of the file to be read 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

0


source







All Articles