views:

347

answers:

4

Windows has an option to open a file with exclusive access rights. Unix doesn't.

In order to ensure exclusive access to some file or device, it is common practice in unix to use a lock file usually stored in the /var/lock directory.

The C instruction open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 ) returns -1 if the lock file already exist, otherwise it creates it. The function is atomic and ensures there is not race condition.

When the resource is released, the lock file is deleted by the following instruction remove( "/var/lock/myLock.lock" ).

There are two problems with this method.

  1. The program may terminate without removing the lock. For instance because it is killed, crashes or whatever. The lock file remains in place, and will prevent any access to the resource even though it is not used anymore.

  2. The lock file is created with group and world write privilege but it is a common practice to configure accounts to use a permission mask that will clear the group and world write permission. Thus, if we had a reliable method to determine that the lock is orphan (not use), a user not owner of the file will not be allowed to remove it.

For the record, I use the lock file to ensure exclusive access to the device connected to the serial port (/dev/ttyUSBx in fact). Advisory method, requiring cooperation, is Ok. But exclusive access should be ensured between different users.

Here is the list of question I have on this issue:

Q1. Is there a better synchronization method than the lock file ?

Q2. How to determine if the process who created the lock file is still running ?

Q3. How making it possible for another user to remove the lock file if not in use ?

(Code, or link to code, recipes required for accepted answer)

One solution I came up with was to use the file as unix socket file. If the file exist, try to connect using the file. If it fails, we may assume the owner process of the file is dead. This requires to have a thread looping on socket accept() in the owner process. Unfortunately, the system wouldn't be atomic anymore.

EDIT: Check my own answer below for the code solution.

+6  A: 

You should probably be using flock(), as in

fd = open(filename, O_RDWR | O_CREAT, 0666); // open or create lockfile
//check open success...
rc = flock(fd, LOCK_EX | LOCK_NB); // grab exclusive lock, fail if can't obtain.
if (rc)
{
    // fail
}
Hasturkun
Good point. Note that it should be 0666 (octal) and not 666 and the O_EXCL option should also be used. What about the file permission issue ? If another user created the file, the open call will fail even if the file already exist and is in use because a write access is requested. If a read access was requested, is it possible to lock on it ?
chmike
Oops, the O_EXCL should not be used to allow opening an existing file. Though the write access may cause it to fail if the file is readonly fo the the user. Flock is independent of file permission and would this work even if the file is read only. The umask() function allows to clear the permission mask when creating the file.
chmike
aye, right about 0666, lazy typist here
Hasturkun
Mind that (on Linux) any program that doesn't call `flock` to check for an existing lock will be able to arbitrarily read/write the file, even if an exclusive lock by another process exists.
AndiDog
+8  A: 

Take a look at the enlightening presentation File Locking Tricks and Traps:

This short talk presents several common pitfalls of file locking and a few useful tricks for using file locking more effectively.

Edit: To address your questions more precisely:

Is there a better synchronization method than the lock file?

As @Hasturkun already mentioned and as the presentation above told, the system call you need to use is flock(2). If the resource you'd like to share across many users is already file-based (in your case it is /dev/ttyUSBx), then you can flock the device file itself.

How to determine if the process who created the lock file is still running?

You don't have to determine this, as the flock-ed lock will be automatically released upon closing the file descriptor associated with your file, even if the process was terminated.

How making it possible for another user to remove the lock file if not in use?

If you would lock the device file itself, then there will be no need to remove the file. Even if you would decide to lock an ordinary file in /var/lock, with flock you will not need to remove the file in order to release the lock.

Andrey Vlasovskikh
I wish I could vote your answer up again for the extra details
Hasturkun
A: 

To expand on Hasturhun's answer a little bit. Instead of using the presence or abscense of the lock file as an indicator you need to both create the lock file if it dosen't exists and then get an exclusive lock on the file. The advantages of this approach is that unlike many other methods of syncing programs the OS should tidy up for you if your program exits without locking. So the program structure would be something like:

1: open the lock file creating it if it doesn't exist
2: ask for an exclusive lock an agreed byte range in the lock file
3: when the lock is granted then
    4: <do my processing here>
    5: release my lock
    6: close the lock file
end

At 2: you can either block waiting for the lock to be granted or return immediately. The bytes you lock don't actually have to exist in the file. If you can get hold of a copy of Advanced Unix Programming by Marc J. Rochkind he developes a complete C library that uses this method to provide a way of syncing programs that gets tidied up by the OS irrespective of how the programs exit.

Jackson
A: 

The answer of Hasturkun is the one that has put me on track.

Here is the code I use

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>

/*! Try to get lock. Return its file descriptor or -1 if failed.
 *
 *  @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
 *  @return File descriptor of lock file, or -1 if failed.
 */
int tryGetLock( char const *lockName )
{
    mode_t m = umask( 0 );
    int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
    umask( m );
    if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
    {
        close( fd );
        fd = -1;
    }
    return fd;
}

/*! Release the lock obtained with tryGetLock( lockName ).
 *
 *  @param fd File descriptor of lock returned by tryGetLock( lockName ).
 *  @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
 */
void releaseLock( int fd, char const *lockName )
{
    if( fd < 0 )
        return;
    remove( lockName );
    close( fd );
}
chmike
I'd suggest flipping the unlink (remove, here) and close around, so you don't accidentally let someone grab the lock, then remove their lockfile.
Hasturkun
Excellent suggestion. Code changed. Thank you very much for your help.
chmike