Locking will be done using the fcntl(2) system call. I’m aware of lockf(3) and flock(2), but both of them normally use fcntl(2) under the hood, and they are not as portable. More information on file locking.
For a real use-case, I’ve written a notification program which uses the same mechanism, so that notifications can be queued without having to run a daemon, such as D-Bus, in the background.
First create the lock file with write permissions. O_CREAT
is used to create
the file in case it doesn’t exist already:
#include <err.h>
#include <fcntl.h>
...
char *lockfile = "/tmp/foo.lock";
int fd;
if ((fd = open(lockfile, O_CREAT | O_WRONLY, 0600)) < 0)
err(1, "open(%s)", lockfile);
Locking commands operate on the flock
structure. Before a call to fcntl(2) is
made, we need to write the following fields:
struct flock {
off_t l_start; /* starting offset */
off_t l_len; /* len = 0 means until end of file */
short l_type; /* lock type: read/write, etc. */
short l_whence; /* type of l_start */
...
};
The starting offset, l_len
, can be anything, but 0 is what makes the most
sense. We’ll set l_len
to 0 as well, since we want each process to lock the
entire file. The lock type, l_type
, needs to be an exclusive lock
(F_WRLCK
), that is, a lock that prevents any other process from setting a
lock on that area before it’s released. l_whence
will be set to SEET_SET
to
indicate that the relative offset l_start
will be measured from the beginning
of the file:
struct flock fl;
fl.l_len = 0;
fl.l_start = 0;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
The F_SETLKW
command will make the calling process wait until the lock
request can be satisfied. There’s also F_SETLK
, but it returns immidiately if
the lock is already acquired, which is not very useful for queuing processes:
if (fcntl(fd, F_SETLKW, &fl) < 0)
err(1, "fcntl(F_SETLKW)");
When we get past the call to fcntl(2), it means that we have acquired the lock
until a call to close(2) is made. Here is where we’ll put the part of the code
we want to queue, in this case a simple printf
followed by a 3-second sleep
to make sure queuing really works:
printf("hello from %d\n", getpid());
sleep(3);
When done, release the lock:
close(fd);
To test the code, open two terminals and run the program on both of them. You’ll see the process that was run last will not execute the code after fcntl(2) until the first one has finished.