File locking

Photo by FLY:D on Unsplash

File locking

IPC in Unix - part 4

We are continuing our journey through Unix IPC by looking at the concept of file locking. If you want to know how multiple processes can access the same file without causing chaos, let's dive right in!

File locking allows us to coordinate read and write access to a file. There are two main types of locking mechanisms: mandatory and advisory. Mandatory locks completely prevent read() and write() calls to a file, while advisory locks operate on a more cooperative principle. With advisory locks processes can still access a file while it's locked, but they have the ability to check for the presence of a lock beforehand. For our discussion, we'll be focusing on advisory locks.

There are 2 types of advisory locks:

  • Read Lock - multiple processes can read a file at the same time
  • Write Lock - exclusive access to a file, no other read or write locks are possible

With theory covered, let's dig into some system calls!

flock and fcntl()

Similar to signals, we will use a combination of a struct and a function to lock a file. The struct is called flock and looks like this:

struct flock {
    short int l_type;
    short int l_whence;
    __off64_t l_start;
    __off64_t l_len;
    __pid_t l_pid;
};
  • l_type is the type of lock to be applied:
    • F_RDLCK - read lock
    • F_WRLCK - write lock
    • F_UNLCK - unlock
  • l_whence is a reference point for l_start with 3 possible values:
    • SEEK_SET - the beginning of the file
    • SEEK_CUR - the current file position
    • SEEK_END - the end of the file
  • l_start is the starting offset for the lock, relative to l_whence parameter
  • l_len specifies the length to be locked in bytes. 0 means lock to the end of the file
  • l_pid is filled by the kernel when retrieving info about lock on the file

The function fcntl() (short for "file control") can do a multitude of things related to file descriptors. In case of advisory locking, it has the following parameters:

int fcntl(
    int fd,  // A file descriptor to operate on
    int cmd, // A command or operation to perform 
    &fl);    // A reference to the `flock` struct

To get the fd, we need to open() the file, and the mode in which we open it must be compatible with the type of lock we're trying to put.

  • F_RDLCK requires O_RDONLY or O_RDWR
  • F_WRLCK requires O_WRONLY or O_RDWR

Parameter cmd has these values for advisory locking:

  • F_GETLK to check if the lock defined in &fl can be placed. If there is a conflicting lock, its details will be in &fl after returning. If not, the &fl.l_type will be set to F_UNLCK to indicate that the lock can be placed.
  • F_SETLK to set (or clear) the lock. Returns error if there is a conflicting lock but does not block.
  • F_SETLKW to block the execution until the lock can be set.

As before, here's a demo program that shows locking and unlocking:

#include "common.h"
#define FILE_NAME "locks.c"

void locks()
{
    struct flock fl = {
        .l_type = F_WRLCK,
        .l_whence = SEEK_SET,
        .l_start = 0,
        .l_len = 0,
        .l_pid = 0,
    };

    int fd;

    fd = open(FILE_NAME, O_RDWR);

    printf("PARENT: Trying to get a write lock to the file\n");
    fcntl(fd, F_SETLKW, &fl);
    printf("PARENT: Got the lock\n");

    pid_t pid = fork();
    if (pid == 0)
    {
        fl.l_type = F_RDLCK;

        printf("CHILD: Checking who has the lock:\n");
        fcntl(fd, F_GETLK, &fl);
        printf("CHILD: Lock owner PID: %d, Parent PID: %d\n", fl.l_pid, getppid());

        printf("CHILD: Blocking until I get a read lock to the file\n");
        fcntl(fd, F_SETLKW, &fl);

        printf("CHILD: Got the lock, can exit now\n");
        fflush(stdout);

        exit(0);
    }
    else
    {
        printf("PARENT: Press <RETURN> to release the write lock\n");
        getchar();

        fl.l_type = F_UNLCK;
        fcntl(fd, F_SETLK, &fl);

        printf("PARENT: Unlocked the file\n");
        fflush(stdout);

        wait(NULL);
        close(fd);
    }
}

This was a dense one, with a BUNCH_OF constants AND_A lot of details, but that's what it takes to lock and unlock a file. To get back to the real inter-process communication, next time we'll look at message queues.

Did you find this article valuable?

Support Senseless events by becoming a sponsor. Any amount is appreciated!