仅当文件不存在时如何创建文件?
Posted
技术标签:
【中文标题】仅当文件不存在时如何创建文件?【英文标题】:How to create a file only if it doesn't exist? 【发布时间】:2012-03-26 14:26:40 【问题描述】:我编写了一个 UNIX 守护程序(针对 Debian,但这不重要),我想提供一些方法来创建一个“.pid”文件,(一个包含守护程序进程标识符的文件)。
我搜索了一种打开文件的方法仅如果它不存在,但找不到。
基本上,我可以这样做:
if (fileexists())
//fail...
else
//create it with fopen() or similar
但就目前而言,这段代码不会以原子方式执行任务,这样做会很危险,因为另一个进程可能会在我的测试和文件创建期间创建文件。
你们知道怎么做吗?
谢谢。
P.S:仅涉及 std::streams
的解决方案的奖励积分。
【问题讨论】:
How to test if a file exists before creating it的可能重复 或许fopen和flock together可以达到你想要的效果? 【参考方案1】:人 2 打开:
O_EXCL 确保此调用创建文件:如果此标志与 O_CREAT 一起指定,并且路径名已存在,则 open() 将失败。如果未指定 O_CREAT,则 O_EXCL 的行为未定义。
所以,你可以调用fd = open(name, O_CREAT | O_EXCL, 0644);
/* Open() 是原子的。 (出于某种原因)*/
更新:您当然应该将 O_RDONLY、O_WRONLY 或 O_RDWR 标志之一放入标志参数中。
【讨论】:
谢谢!正是我需要的。【参考方案2】:我在这里了解了正确的守护进程(过去):
http://www.enderunix.org/docs/eng/daemon.php这是一本好书。此后,我改进了锁定代码以消除平台上的竞争条件,这些平台允许 advisory file lock 指定特定区域。
这是我参与的一个项目的相关sn-p:
static int zfsfuse_do_locking(int in_child)
/* Ignores errors since the directory might already exist */
mkdir(LOCKDIR, 0700);
if (!in_child)
ASSERT(lock_fd == -1);
/*
* before the fork, we create the file, truncating it, and locking the
* first byte
*/
lock_fd = creat(LOCKFILE, S_IRUSR | S_IWUSR);
if(lock_fd == -1)
return -1;
/*
* only if we /could/ lock all of the file,
* we shall lock just the first byte; this way
* we can let the daemon child process lock the
* remainder of the file after forking
*/
if (0==lockf(lock_fd, F_TEST, 0))
return lockf(lock_fd, F_TLOCK, 1);
else
return -1;
else
ASSERT(lock_fd != -1);
/*
* after the fork, we instead try to lock only the region /after/ the
* first byte; the file /must/ already exist. Only in this way can we
* prevent races with locking before or after the daemonization
*/
lock_fd = open(LOCKFILE, O_WRONLY);
if(lock_fd == -1)
return -1;
ASSERT(-1 == lockf(lock_fd, F_TEST, 0)); /* assert that parent still has the lock on the first byte */
if (-1 == lseek(lock_fd, 1, SEEK_SET))
perror("lseek");
return -1;
return lockf(lock_fd, F_TLOCK, 0);
void do_daemon(const char *pidfile)
chdir("/");
if (pidfile)
struct stat dummy;
if (0 == stat(pidfile, &dummy))
cmn_err(CE_WARN, "%s already exists; aborting.", pidfile);
exit(1);
/*
* info gleaned from the web, notably
* http://www.enderunix.org/docs/eng/daemon.php
*
* and
*
* http://sourceware.org/git/?p=glibc.git;a=blob;f=misc/daemon.c;h=7597ce9996d5fde1c4ba622e7881cf6e821a12b4;hb=HEAD
*/
int forkres, devnull;
if(getppid()==1)
return; /* already a daemon */
forkres=fork();
if (forkres<0)
/* fork error */
cmn_err(CE_WARN, "Cannot fork (%s)", strerror(errno));
exit(1);
if (forkres>0)
int i;
/* parent */
for (i=getdtablesize();i>=0;--i)
if ((lock_fd!=i) && (ioctl_fd!=i)) /* except for the lockfile and the comm socket */
close(i); /* close all descriptors */
/* allow for airtight lockfile semantics... */
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200000; /* 0.2 seconds */
select(0, NULL, NULL, NULL, &tv);
VERIFY(0 == close(lock_fd));
lock_fd == -1;
exit(0);
/* child (daemon) continues */
setsid(); /* obtain a new process group */
VERIFY(0 == chdir("/")); /* change working directory */
umask(027); /* set newly created file permissions */
devnull=open("/dev/null",O_RDWR); /* handle standard I/O */
ASSERT(-1 != devnull);
dup2(devnull, 0); /* stdin */
dup2(devnull, 1); /* stdout */
dup2(devnull, 2); /* stderr */
if (devnull>2)
close(devnull);
/*
* contrary to recommendation, do _not_ ignore SIGCHLD:
* it will break exec-ing subprocesses, e.g. for kstat mount and
* (presumably) nfs sharing!
*
* this will lead to really bad performance too
*/
signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
if (0 != zfsfuse_do_locking(1))
cmn_err(CE_WARN, "Unexpected locking conflict (%s: %s)", strerror(errno), LOCKFILE);
exit(1);
if (pidfile)
FILE *f = fopen(pidfile, "w");
if (!f)
cmn_err(CE_WARN, "Error opening %s.", pidfile);
exit(1);
if (fprintf(f, "%d\n", getpid()) < 0)
unlink(pidfile);
exit(1);
if (fclose(f) != 0)
unlink(pidfile);
exit(1);
另见http://gitweb.zfs-fuse.net/?p=sehe;a=blob;f=src/zfs-fuse/util.c;h=7c9816cc895db4f65b94592eebf96d05cd2c369a;hb=refs/heads/maint
【讨论】:
非常好的阅读和有趣的文章。我希望我也能接受这个答案。为公平投票。【参考方案3】:我能想到的唯一方法是使用系统级锁。看到这个:C++ how to check if file is in use - multi-threaded multi-process system
【讨论】:
【参考方案4】:解决此问题的一种方法是打开文件进行附加。如果函数成功并且位置为 0,那么您可以相当确定这是一个新文件。仍然可能是一个空文件,但这种情况可能并不重要。
FILE* pFile = fopen(theFilePath, "a+");
if (pFile && gfetpos(pFile) == 0)
// Either file didn't previously exist or it did and was empty
else if (pFile)
fclose(pFile);
【讨论】:
文件为空的假设是相当错误的。它可能在大多数情况下都存在,但不知道具体情况...... 但是会出现一些线程同时打开这个文件的情况。他们每个人都会检查位置。每个线程的位置都有可能为零。然后我们将进行数据竞赛。【参考方案5】:看来没有办法严格使用流来做到这一点。
您可以改为使用 open(如上面 wildplasser 所述),如果成功,则继续打开同一个文件作为流。当然,如果你写入文件的只是一个 PID,那么你为什么不直接使用 C 风格的 write() 来编写它是不清楚的。
O_EXCL 仅排除尝试使用 O_EXCL 打开同一文件的其他进程。当然,这意味着您永远无法获得完美的保证,但如果文件名/位置位于其他人不可能打开的位置(除了您知道使用 O_EXCL 的人),您应该没问题。
【讨论】:
实际上O_EXCL
确实排除了所有其他进程,而不仅仅是那些试图用O_EXCL
打开同一个文件的进程。你可能在想flock
和朋友?
我想你是正确的。如果文件已经存在,O_EXCL
将失败,无论其他进程做什么或做了什么。但是在open
创建文件后,另一个进程可能会出现并在没有O_EXCL
(甚至O_CREAT
)的情况下打开它并成功。你可以通过 open(fname, O_WRONLY|O_CREAT|O_EXCL, 0000)
解决这个问题——是的,零模式——此时 你的进程 可以写入文件,但在你(或某人)之前没有其他非 root 进程可以打开它否则)chmod
s 它。
这基本上是我正在考虑的场景 - 其他人打开并写入文件。有点不太可能,正如你所说 - 将 perms 设置为 0 可以解决这个问题。
无需再次打开它 - 使用 fdopen
和您从 open
获得的文件描述符来获得 FILE*
。以上是关于仅当文件不存在时如何创建文件?的主要内容,如果未能解决你的问题,请参考以下文章
仅当行不存在时,如何将一行添加到不同子目录下的多个文本文件?