除非文件存在于C中,否则如何安全地写入文件?

除非文件存在于C中,否则如何安全地写入文件?,c,file,C,File,我正在尝试执行以下操作: 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"); }

我正在尝试执行以下操作:

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");
}
我当然可以使用,但正如在答案评论中所说的,这将是一个种族条件(特别是)


安全地创建和写入文件的最简单方法是什么,除非文件已经存在,而不创建竞争条件?

在纯C中没有跨平台、可移植的方法来实现这一点,因为C的标准库缺少高级IO操作,例如文件锁定,也没有系统范围的互斥体库。C甚至没有“列表目录”功能

我觉得最好的解决方案是通过提示用户干预并让他们选择如何继续(例如删除文件、强制覆盖等)来处理错误情况


C++11(…最后)中提供了一个功能更全面的IO库,但这意味着放弃pure-C。另一种选择是使用跨平台包装库,包装平台特定的IO函数。

如果使用GNU C库,那么调用
fopen(“filename”,“xw+”)
可能会满足您的需求。字符串
x
执行以下操作:

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");
}
x
:以独占方式打开文件(如打开(2)的O_EXCL标志)。如果文件已经存在,fopen()将失败,并将errno设置为EEXIST。fdopen()忽略此标志

另一个选项的功能是:

w+
:打开进行读写。如果文件不存在,则创建该文件,否则将截断该文件。流位于文件的开头

您还可以使用:

a
:打开以进行追加(在文件末尾写入)。如果文件不存在,则创建该文件。流位于文件的末尾

a+
:打开以进行读取和追加(在文件末尾写入)。如果文件不存在,则创建该文件。读取的初始文件位置位于文件的开头,但输出始终附加到文件的结尾

还有更多的选择。有关详细信息,请参见

您必须将open(2)系统调用与
O_EXCL | O_CREAT | O_WRONLY一起使用
,然后在该描述符上调用fdopen(3)

#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);
}
#包括
#包括
#包括
#包括
#包括
int main(int argc,char*argv[]){
char*program=argv[0];
char*filename=argv[1];
如果(argc!=2){
fprintf(stderr,“%s:应为单个文件参数。\n”,程序);
出口(1);
}   
int flags=O|u EXCL | O|u CREAT | O|u WRONLY;
int模式=0666;
int-fd;
如果((fd=open(文件名、标志、模式))=-1){
fprintf(stderr,“%s:无法打开带有标志0x%04X的%s:%s\n”,
程序、文件名、标志、strerror(errno));
出口(2);
}   
文件*fp=fdopen(fd,“w”);
如果(fp==NULL){
fprintf(stderr,“%s:无法打开文件描述符%d:%s\n”,
程序,fd,strerror(errno));
出口(3);
}   
if(fprintf(fp,“12345\n”)==EOF){
fprintf(stderr,“%s:无法写入%s:%s\n”,
程序,文件名,strerror(errno));
出口(4);
}   
如果(fclose(fp)=EOF){
fprintf(stderr,“%s:无法关闭%s:%s\n”,
程序,文件名,strerror(errno));
出口(5);
}   
出口(0);
}
这些开放(2)的旗帜是少数


这不能保证在远程文件系统上可靠地工作。特别是,NFS打破了POSIX关于原子创建的规则。[sic]

使用带有O_EXCL标志的open()。O_EXCL确保此调用创建文件:如果此标志与O_CREAT一起指定,并且路径名已存在,则open()将失败。@Alon
O_EXCL
不可移植。这是有意义的,但仍然需要两个单独的系统调用(
if(fileexists(foo)){prompt…}else{fprintf(foo,bar);}
或类似的内容),这仍然使竞争条件成为可能。如何在不创建竞争条件的情况下做到这一点?@Doorknob不,每个(现代)平台都提供了一个原子文件锁定API,您可以从C中使用它们,但在portable-C中无法实现。好的,您可以提供一个链接或某种资源来演示如何跨平台进行文件锁定吗?谢谢@对不起,我有点太热心了。我已经将我的断言更改为C缺少目录处理函数。C11具有@NZD的答案中提到的
x
mode字符。