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.