Do not use flock() . This does not work reliably if the lock file directory is a network file system (for example, NFS) and the OS you are using does not implement flock() with fcntl() block messages.
(For example, in modern Linux systems, flock() and fcntl() locks are locked separately and do not interact with local files, but interact with files located in NFS file systems. It is not so strange to have /var/lock in the NFS file system in clusters servers, especially fault tolerance systems and the web server, so this is, in my opinion, a real problem that you should consider.)
Edited to add: if for some external reason you need to use flock() , you can use flock(fd, LOCK_EX|LOCK_NB) to try to get an exclusive lock. This call will never block (wait until the lock is released), but with a -1 error and errno == EWOULDBLOCK if the file is already locked. Like the fcntl() locking scheme described in detail below, you are trying to get an exclusive lock (without locking); if successful, you keep the lock file descriptor open and allow the operating system to automatically release the lock when you exit the process. If the lock is non-blocking, you must choose whether to interrupt or continue anyway.
You can accomplish your tasks using the POSIX.1 and fcntl() functions to block warning entries (covering the entire file). Semantics is standard across all POSIXy systems, so this approach will work on all POSIX and unified systems.
The features of fcntl() locks are simple but unintuitive. When any descriptor related to the lock file is closed, warning locks of this file are issued. When the process ends, warning locks on all open files are automatically released. Locks are supported through exec*() . Locks are not inherited via fork() , and they are not released in the parent (even if they are 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 file descriptor, but not fcntl() locks. Closing the descriptors in the child process will not affect the parent lock of 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 console lock without locking: if there is a conflicting lock, it will immediately fail, and not lock until the lock is received. Keep the handle open and let the operating system automatically release the lock when the process exits.
For instance:
#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 mentioned above does not accidentally use the standard descriptor, because I was bitten by it in a very rare case. (I wanted to execute a user-specified process, holding the lock, but redirecting standard input and output to the current control terminal.)
Using is very simple:
int result; result = lockfile(YOUR_LOCKFILE_PATH, NULL); if (result == 0) { } else if (result == EALREADY) { } else { }
Edited to add: I used internal binding ( static ) for the above function out of habit. If the lock file is user-dependent, it should use ~/.yourapplication/lockfile ; if it is system-wide, it should use, for example, /var/lock/yourapplication/lockfile . I have a habit of saving functions related to this type of initialization, including determining / creating a lock path, etc., As well as an automatic plug-in registration function (using opendir() / readdir() / dlopen() / dlsym() / closedir() ), in the same file; the lockfile function is usually called internally (a function that builds the path to the lock file) and thus has an internal connection.
Feel free to use, reuse or modify the function as you wish; I believe that it is in the public domain or licensed under CC0 , where devotion to the public domain is not possible.
The handle "leaked" intentionally, so that it will be closed (and locked when it is released) by the operating system when the process ends, but not earlier.
If there are many post-work corrections that your process makes during which you want to allow another copy of this process, you can save the handle and just close(thatfd) in the place where you want to release the lock.