How to create a file only if it does not exist?

I wrote a UNIX daemon (targeting Debian, but that doesn't matter), and I would like to provide some way to create a "pid file" (a file that contains the process ID for the daemon).

I was looking for a way to open a file only if it does not exist , but could not find it.

Basically, I could do something like:

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

But be that as it may, this code does not perform the task in atomic mode, and it will be dangerous, because another process can create the file during my test and file creation.

Do you guys know how to do this?

Thanks.

PS: Bonus point for a solution that only includes std::streams .

+6
source share
5 answers

man 2 open:

O_EXCL Make sure this call creates a file: if this flag is specified with O_CREAT, and the path already exists, then open () will fail. The behavior of O_EXCL is undefined if O_CREAT is not specified.

so you can call fd = open(name, O_CREAT | O_EXCL, 0644); / * Open () atomic. (for any reason) * /

UPDATE: and you must, of course, OR one of the flags O_RDONLY, O_WRONLY, or O_RDWR in the flags argument.

+7
source

I found out about the correct demonization here (back in the day):

Good to read. Since then, I have improved the lock code to eliminate race conditions on platforms that allow messages to be blocked in certain regions.

Here is the relevant snippet from the project in which I participated:

 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, eg 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); } } } 

See also http://gitweb.zfs-fuse.net/?p=sehe;a=blob;f=src/zfs-fuse/util.c;h=7c9816cc895db4f65b94592eebf96d05cd2c369a;hb=refs/heads/maint

+4
source

The only way I can think of is to use locks at the system level. See: C ++, how to check if a file is used - a multi-threaded multi-processor system

+1
source

One way to solve this problem is to open the file to add. If the function succeeds and the position is 0, you can be pretty sure that this is a new file. There may still be an empty file, but this scenario may not be important.

 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); } 
+1
source

It would seem that there is no way to do this strictly using threads.

Instead, you can use open (as mentioned by wildplasser above), and if that succeeds, continue to open the same file as the stream. Of course, if everything you write to the file is a PID, it is unclear why you just don't write it using C-style write ().

O_EXCL excludes other processes that try to open the same file using O_EXCL. This, of course, means that you will never have a perfect guarantee, but if the file name / location is somewhere, somewhere else no one can open (except for the people you know, O_EXCL is used), you should be fine .

0
source

Source: https://habr.com/ru/post/911681/


All Articles