Frode Akselsen
Frode Akselsen

Reputation: 676

Atomically open and lock file

I have a file foo.hex that is accessed by two processes. One process has O_RDONLY access and the other has O_RDWR access.

When starting the system for the very first time, the reading process should not access the file before the writing process has initialized it.

Thus, I wrote something like this to initialize the file.

fd = open("foo.hex", O_RDWR|O_CREAT, 0666);
flock(fd, LOCK_EX);

init_structures(fd);

flock(fd, LOCK_UN);

Which still leaves the possibility to the reader process to access the file before it is initialized.

I couldn't find a way to open() and flock() in an atomic fashion. Besides mutexes what other possibilities are there to achieve my goal in an elegant way with as little overhead as possible (since it's only used once, the very first time the system is started)?

Upvotes: 5

Views: 1807

Answers (4)

Andrew Henle
Andrew Henle

Reputation: 1

Another approach could be to remove the existing file, recreate it without permissions for any process to access it, then change the file permissions after it's written:

unlink("foo.hex");
fd = open("foo.hex", O_RDWR|O_CREAT|O_EXCL, 0);

init_structures(fd);

fchmod(fd, 0666);

That likely won't work if you're running as root. (Which you shouldn't be doing anyway...)

This would prevent any process from using old data once the unlink() call is made. Depending on your requirements, that may or may not be worth the extra reader code necessary to deal with the file not existing or being accessible while the new file is being initialized.

Personally, I'd use the rename( "foo.hex.init", "foo.hex" ) solution unless init_structures() takes significant time, and there's a real, hard requirement to not use old data once new data is available. But sometimes important people aren't comfortable with using old data while any portion of new data is available, and they don't really understand, "If the reader process started two milliseconds earlier it would use the old data anyway".

Upvotes: 2

zwol
zwol

Reputation: 140659

An alternative approach is for the reader process to sleep a little and retry upon finding that the file doesn't yet exist, or is empty.

int open_for_read(const char *fname)
{
    int retries = 0;

    for (;;) {
        int fd = open(fname, O_RDONLY);
        if (fd == -1) {
            if (errno != ENOENT) return -1;
            goto retry;
        } 
        if (flock(fd, LOCK_SH)) {
            close(fd);
            return -1;
        }

        struct stat st;
        if (fstat(fd, &st)) {
            close(fd);
            return -1;
        }
        if (st.st_size == 0) {
            close(fd);
            goto retry;
        }
        return fd;

    retry:
        if (++retries > MAX_RETRIES) return -1;
        sleep(1);
    }
    /* not reached */
}

You need similar code on the write side, so that if the writer loses the race it doesn't have to be restarted.

Upvotes: 1

Some programmer dude
Some programmer dude

Reputation: 409196

There are many ways of inter-process communications.

Perhaps use a named semaphore that the writing process locks before opening and initializing the file? Then the reading process could attempt to lock the semaphore as well, and if it succeeds and the file doesn't exist it unlocks the semaphore and wait a little while and retry.

The simplest way though, especially if the file will be recreated by the writing process every time, is already in the answer by John Zwinck.

Upvotes: 0

John Zwinck
John Zwinck

Reputation: 249223

Make the writer create a file called "foo.hex.init" instead, and initialize that before renaming it to "foo.hex". This way, the reader can never see the uninitialized file contents.

Upvotes: 6

Related Questions