Do not use flock()
. It does not work reliably if the lock file directory happens to be a network filesystem (for example, NFS) and the OS you're using does not implement flock()
using fcntl()
advisory record locking.
(For example, in current Linux systems, flock()
and fcntl()
locks are separate and do not interact on local files, but do interact on files residing on NFS filesystems. It is not that strange to have /var/lock
on an NFS filesystem in server clusters, especially failover and web server systems, so this is, in my opinion, a real issue you should consider.)
Edited to add: If for some external reason you are constrained to use flock()
, you can use flock(fd, LOCK_EX|LOCK_NB)
to try to obtain the exclusive lock. This call will never block (wait for the lock to be released), but will fail with -1 and errno == EWOULDBLOCK
if the file is already locked. Similar to the fcntl()
locking scheme explained in detail below, you try to obtain the exclusive lock (without blocking); if successful, you keep the lock file descriptor open, and let the operating system release the lock automatically when the process exits. If the nonblocking lock fails, you must choose whether you will abort, or proceed anyway.
You can accomplish your goals by using POSIX.1 functions and fcntl()
advisory record locks (covering the entire file). The semantics are standard across all POSIXy systems, so this approach will work on all POSIXy and unix-like systems.
Features of fcntl()
locks are simple, but nonintuitive. When any descriptor referring to the lock file is closed, the advisory locks on that file are released. When the process exits, the advisory locks on all open files are automatically released. Locks are maintained across an exec*()
. Locks are not inherited via fork()
, nor are they released in the parent (even when marked close-on-exec). (If the descriptors are close-on-exec, then they will be automatically closed in the child process. Otherwise the child process will have an open descriptor to the file, but not any fcntl()
locks. Closing the descriptors in the child process will not affect the parent's lock on the file.)
Therefore the correct strategy is very simple: Open the lock file exactly once, and use fcntl(fd,F_SETLK,&lock)
to place an exclusive all-file advisory lock without blocking: if there is a conflicting lock, it will fail immediately, instead of blocking until the lock can be acquired. Keep the descriptor open, and let the operating system auto-release the lock when your process exits.
For example:
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/* Open and exclusive-lock file, creating it (-rw-------)
* if necessary. If fdptr is not NULL, the descriptor is
* saved there. The descriptor is never one of the standard
* descriptors STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO.
* If successful, the function returns 0.
* Otherwise, the function returns nonzero errno:
* EINVAL: Invalid lock file path
* EMFILE: Too many open files
* EALREADY: Already locked
* or one of the open(2)/creat(2) errors.
*/
static int lockfile(const char *const filepath, int *const fdptr)
{
struct flock lock;
int used = 0; /* Bits 0 to 2: stdin, stdout, stderr */
int fd;
/* In case the caller is interested in the descriptor,
* initialize it to -1 (invalid). */
if (fdptr)
*fdptr = -1;
/* Invalid path? */
if (filepath == NULL || *filepath == '\0')
return errno = EINVAL;
/* Open the file. */
do {
fd = open(filepath, O_RDWR | O_CREAT, 0600);
} while (fd == -1 && errno == EINTR);
if (fd == -1) {
if (errno == EALREADY)
errno = EIO;
return errno;
}
/* Move fd away from the standard descriptors. */
while (1)
if (fd == STDIN_FILENO) {
used |= 1;
fd = dup(fd);
} else
if (fd == STDOUT_FILENO) {
used |= 2;
fd = dup(fd);
} else
if (fd == STDERR_FILENO) {
used |= 4;
fd = dup(fd);
} else
break;
/* Close the standard descriptors we temporarily used. */
if (used & 1)
close(STDIN_FILENO);
if (used & 2)
close(STDOUT_FILENO);
if (used & 4)
close(STDERR_FILENO);
/* Did we run out of descriptors? */
if (fd == -1)
return errno = EMFILE;
/* Exclusive lock, cover the entire file (regardless of size). */
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLK, &lock) == -1) {
/* Lock failed. Close file and report locking failure. */
close(fd);
return errno = EALREADY;
}
/* Save descriptor, if the caller wants it. */
if (fdptr)
*fdptr = fd;
return 0;
}
The reason the above makes sure it does not accidentally reuse a standard descriptor, is because I've been bitten by it in a very rare case. (I wanted to exec an user-specified process while holding a lock, but redirecting the standard input and output to currently controlling terminal.)
The use is very simple:
int result;
result = lockfile(YOUR_LOCKFILE_PATH, NULL);
if (result == 0) {
/* Have an exclusive lock on YOUR_LOCKFILE_PATH */
} else
if (result == EALREADY) {
/* YOUR_LOCKFILE_PATH is already locked by another process */
} else {
/* Cannot lock YOUR_LOCKFILE_PATH, see strerror(result). */
}
Edited to add: I used internal linkage (static
) for the above function just out of habit. If the lock file is user-specific, it should use ~/.yourapplication/lockfile
; if it is system-wide, it should use e.g. /var/lock/yourapplication/lockfile
. I have a habit of keeping the functions related to this kind of initialization stuff, including defining/building the lockfile path etc. as well automatic plugin registration function (using opendir()
/readdir()
/dlopen()
/dlsym()
/closedir()
), in the same file; the lockfile function tends to be called internally (by the function that builds the lockfile path), and thus ends up having internal linkage.
Feel free to use, reuse, or modify the function as you wish; I consider it to be in public domain, or licensed under CC0
where public domain dedication is not possible.
The descriptor is "leaked" intentionally, so that it will be closed (and the lock on it released) by the operating system when the process exits, but not before.
If there is a lot of post-work cleanups your process does, during which you do wish to allow another copy of this process, you can retain the descriptor, and just close(thatfd)
at the point where you wish to release the lock.