Search code examples
linuxfile-lockingflockfcntl

locking a file accessed by concurrent processes


I need to write to and read from a config file using shell CGI, and I also have a C program that must read and modify the config file every few seconds. I tried using flock to maintain data integrity and prevent race conditions, but both CGI and C program are still able to access the file at the same time.

If I use 2 C programs on the file it works, but not both the shell CGI and C program.

This is the C program am using:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main() {
    FILE *file;
    struct flock fl = {0};
    char buff[1000];

    file = fopen("/home/siddusuhaas/sample.conf", "r+");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }
    printf("Opened File\n");

    // Set an exclusive lock on the entire file
    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;

    if (fcntl(fileno(file), F_SETLKW, &fl) == -1) {
        perror("Error locking file");
        fclose(file);
        return 1;
    }
    printf("File Lock Successful\n");

    // Perform operations on the locked file here
    system("lslocks | grep 'bcf.conf'");

    // Read the file contents
    size_t nread = fread(buff, sizeof(char), sizeof(buff) - 1, file);
    if (nread == 0 && !feof(file)) {
        perror("Error reading file");
        fclose(file);
        return 1;
    }
    buff[nread] = '\0'; // Null-terminate the buffer
    printf("Data is %zu\t %.*s\n", nread, (int)nread, buff);
    for (int i = 0; i < 10; i++) {
        system("lslocks | grep 'bcf.conf'");
        sleep(1);
        fseek(file , 0  , SEEK_SET);
        memset(buff, 0, sizeof(buff));
        nread = fread(buff, sizeof(char), sizeof(buff) - 1, file);
        if (nread == 0 && !feof(file)) {
            perror("Error reading file");
            fclose(file);
            return 1;
        }
        buff[nread] = '\0'; // Null-terminate the buffer
        printf("Data is %zu\t %.*s\n", nread, (int)nread, buff);
    }
    fl.l_type = F_UNLCK;
    if (fcntl(fileno(file), F_SETLK, &fl) == -1) {
        perror("Error unlocking file");
        fclose(file);
        return 1;
    }
    printf("File Unlocked Successfully\n");

    fclose(file);
    return 0;
}

And this is my CGI:

#!/bin/bash

file=/home/siddusuhaas/sample.conf

exec 200>>"$file"
if ! flock -x 200; then
    echo "Unable to acquire lock on file" 
    exit 1
fi

echo "Surprise! The file is locked!" > "$file"

#Release the lock
exec 200>&-

Solution

  • The flock(1) program uses the flock(2) system call to lock files. Your C program is using fcntl(2) to lock files. Those are two different locking mechanisms that don't interact with each other on Linux. Change the C program to also use flock(2) locks.

    I would also drop the use of stdio functions for reading and writing the file; the interaction between stdio buffering and when underlying files are actually read and written is potentially a source of subtle and hard to reproduce bugs in the presence of multiple processes modifying the same file. Keep it simple and just use open()/read()/write()/etc.