Как создать файл, только если он не существует?

Я написал демон UNIX (нацеливая Debian, но это не имеет значения), и я хотел бы предоставить какой-то способ создания "файла pid" (файла, который содержит идентификатор процесса для демона).

Я искал способ открытия файла, только если он не существует, но не смог найти его.

В принципе, я мог бы сделать что-то вроде:

if (fileexists())
{
 //fail...
}
else
{
 //create it with fopen() or similar
}

Но как бы то ни было, этот код не выполняет задачу в атомном режиме, и это будет опасно, потому что другой процесс может создать файл во время моего теста и создания файла.

Вы, ребята, знаете, как это сделать?

Спасибо.

P.S: Бонусная точка для решения, которое включает только std::streams.

5 ответов

man 2 open:

O_EXCL Убедитесь, что этот вызов создает файл: если этот флаг указан вместе с O_CREAT, а путь уже существует, тогда откройте()               не удастся. Поведение O_EXCL undefined, если O_CREAT не указано.

поэтому вы можете называть fd = open(name, O_CREAT | O_EXCL, 0644);/* Open() атомарным. (по какой-либо причине) */

ОБНОВЛЕНИЕ: и вы должны, конечно, ИЛИ один из флагов O_RDONLY, O_WRONLY или O_RDWR в аргументе flags.


Я узнал о правильном демонализации здесь (назад в день):

Хорошо читать. С тех пор я улучшил код блокировки, чтобы устранить условия гонки на платформах, которые позволяют осуществлять блокировку сообщений в определенных регионах.

Вот соответствующий фрагмент из проекта, в котором я участвовал:

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


Один из способов решения этой проблемы - открыть файл для добавления. Если функция завершается успешно, а позиция равна 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);
}


Единственный способ, которым я могу думать, - использовать блокировки на уровне системы. Смотрите: С++, как проверить, используется ли файл - многопоточная многопроцессорная система


Казалось бы, нет никакого способа сделать это строго используя потоки.

Вместо этого вы можете использовать open (как упомянуто выше wildplasser), и если это удастся, продолжайте открывать тот же файл, что и поток. Конечно, если все, что вы пишете в файл, является PID, непонятно, почему вы просто не пишете его, используя C-style write().

O_EXCL исключает другие процессы, которые пытаются открыть один и тот же файл, используя O_EXCL. Это, конечно, означает, что у вас никогда не будет идеальной гарантии, но если имя/местоположение файла где-то, где-то еще никто не может открыться (кроме людей, которых вы знаете, используется O_EXCL), вы должны быть в порядке.

licensed under cc by-sa 3.0 with attribution.