• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 721
  • Last Modified:

File locking using fcntl()

I am trying to use the posix fcntl() to lock a file for read/write or unlock it. For some reason, it doesn't work...
Here's the code. Appreciate your advice.
#include <iostream.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
/*****************************************************
struct flock
{
    short l_type ;  /* F_RDLCK, F_WRLCK, F_UNLCK */
    short l_whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
    off_t  l_start;  /* relative starting offset in bytes */
    off_t  l_len;   /* #bytes; 0 means until EOF */
    pid_t l_pid; /* PID returned by F_GETLK *?
};
*****************************************************/
#define MY_NO_LOCK (F_UNLCK)
#define MY_READ_LOCK (F_RDLCK)
#define MY_WRITE_LOCK (F_RDLCK | F_WRLCK)

int lockFile(int fd, int lockType)
{
    struct flock fileLock;

    fileLock.l_type = 0;
    fileLock.l_whence = SEEK_SET;
    fileLock.l_start = 0;
    fileLock.l_len = 0;  /*  lock entire file */
    fileLock.l_pid = 0;

    if( fcntl(fd, F_GETLK, &fileLock) < 0 )
        return (-1); /* error */

    cout << "fcntl() returned lock type " << fileLock.l_type << endl;

    if( fileLock.l_type == MY_NO_LOCK )
    {
        cout << "File not currently locked" << endl;
        fileLock.l_type = lockType;
        fileLock.l_pid = 0;
        return fcntl( fd, F_SETLKW, &fileLock );
    }
    if( (fileLock.l_type == MY_READ_LOCK) && (lockType == MY_READ_LOCK) )
    {
        cout << "File is already READ locked by process " << fileLock.l_pid << endl;
        cout << "Current thread will increment lock count" << endl;
        fileLock.l_pid = 0;
        fileLock.l_type = lockType;
        return fcntl( fd, F_SETLKW, &fileLock );
    }
    if( (lockType == MY_WRITE_LOCK) )
    {
        cout << "File is already WRITE locked by process " << fileLock.l_pid << endl;
        cout << "Current thread will block for the lock " << endl;
        fileLock.l_pid = 0;
        fileLock.l_type = lockType;
        return fcntl( fd, F_SETLKW, &fileLock );
    }
}

char getLockTypeFromUser()
{
    char ch = 'X';
    while( ch != 'R' && ch != 'r' && ch != 'W' && ch != 'w'  && ch != 'U' && ch != 'u' && ch != 'Q' && ch != 'q' )
    {
        cout    << "     R for read lock " << endl
                << "     W for write lock " << endl
                << "     U for unlock " << endl
                << "     Q for unlock and quit " << endl
                << " : " ;
        ch = getchar();
    }
    cout << "Thanks !" << endl;
    getchar(); /* remove \r from stdin */

    return ch;
}
int main ()
{
    cout << "Current process id is " << getpid() << endl;
    int fd = open( "/tmp/testfile", O_RDWR );
    if ( fd < 0 )
    {
        cout << "Unable to open file : error " << errno << endl;
        return -1;
    }

    while (1)
    {
        switch( getLockTypeFromUser() )
        {
        case 'w':
        case 'W' :
            if( lockFile(fd, MY_WRITE_LOCK) < 0 )
                cout << "Unable to lock file : error " << errno << endl;
            else
                cout << "File locked for WRITE" << endl;
        break;
        case 'r':
        case 'R' :
            if( lockFile(fd, MY_READ_LOCK) < 0 )
                cout << "Unable to lock file : error " << errno << endl;
            else
                cout << "File locked for READ" << endl;
        break;
        case 'u':
        case 'U' :
            if( lockFile(fd, MY_NO_LOCK) < 0 )
                cout << "Unable to unlock file : error " << errno << endl;
            else
                cout << "File unlocked" << endl;
        break;
        case 'q':
        case 'Q' :
            if( lockFile(fd, MY_NO_LOCK) < 0 )
                cout << "Unable to unlock file : error " << errno << endl;
            else
                cout << "File unlocked ... exiting." << endl;
            close( fd );
            return 0;
        break;
        }
    }
}


0
Marthi
Asked:
Marthi
  • 3
  • 3
1 Solution
 
jlevieCommented:
Unfortunately the Linux man page for fcntl (at least on Redhat 6.1) doesn't fully describe the use of F_SETLK & friends. I had to look at the man page for fcntl on Solaris to figure it out.

I've included a simple pair of programs that demonstrates the use. The "Locker" needs to be running before "Writer" is executed. Since I don't have much use for C++, I wrote the pair in C.

--- Locker Starts here ---
/*
 * Playtoy for file locks via fnctl
 * Locker creates and locks a file (/tmp/testfile)
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

extern int errno;

main()
{
  int fd, c;
  struct flock lock;

  if((fd = open("/tmp/testfile", O_RDWR|O_CREAT))==0)
  {
    perror("Locker - Failed to create testfile");
    exit(1);
  }
  lock.l_type = F_WRLCK;
  lock.l_whence = SEEK_SET;     /* Set locked area to begin ... */
  lock.l_start = 0;             /* ... zero bytes from start of file ... */
  lock.l_len = 0;               /* ... zero means to end of file */
  if(fcntl(fd, F_SETLK, &lock)==-1)
  {
    perror("Locker - Failed to set lock");
    exit(1);
  }
  printf("Writer - Lock succeeded, hit return to exit\n");
  c = getchar();
}

--- Writer Starts here ---
/*
 * Playtoy for file locks via fnctl
 * Writer attempts to open for writing a locked file (/tmp/testfile)
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

extern int errno;

main()
{
  int fd, c;
  struct flock lock;

  if((fd = open("/tmp/testfile", O_RDWR))==0)
  {
    perror("Writer - Failed to open file");
    exit(1);
  }
  lock.l_type = F_WRLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  if(fcntl(fd, F_SETLK, &lock)==-1)
  {
    if(fcntl(fd, F_GETLK, &lock)==-1)
    {
      perror("Writer - Failed to retrieve lock struct");
    }
    perror("Writer - Can't get lock");
    printf("Locked by process: %ld\n", lock.l_pid);
    exit(1);
  }
  printf("Writer - Lock succeeded, hit return to exit\n");
  c = getchar();
}
0
 
MarthiAuthor Commented:
It worked for me on Linux too... Thanks !
0
 
jlevieCommented:
Was the code enough of an explanation as to how it works? After reading Sun's man page (lots & lots more info compared to Linux), I wasn't sure if the code alone would suffice.
0
Cloud Class® Course: CompTIA Cloud+

The CompTIA Cloud+ Basic training course will teach you about cloud concepts and models, data storage, networking, and network infrastructure.

 
MarthiAuthor Commented:
Your code helped me find the right way to use fcntl(F_SETLK) and fcntl(F_GETLK). Combined with the information in Linux header files and W.R.Stevens, I could get my file locks working.

So, Thanks again...

This was one of the things I was experimenting with and was having trouble. However, I probably need a different solution altogether. If you are interested, please read on.

I have a small persistent database on the filesystem. Each "table" of this "database" is a file. I need to read and write lock the file so that other processes as well as threads within the same process are synchronized in their access to the files.

To do this, (with your kind help) I put together a small scheme of locking the process and then locking the database file (both using fcntl()). Although the database is small, it can easily have upwards of 700 such files. For this application fcntl() is sub-optimal and something like a POSIX - process shared mutex would be nicer. Unfortunately, those are not implemented on Linux (apparently, they are on Solaris, AIX ??)

Still looking for an optimal solution.

Kailash
0
 
jlevieCommented:
Other than using an actual database, which may not be possible depending on the nature of the data, you're right. What you need is a  process shared mutex facility. I suspect we'll get them eventually in Linux as the SMP kernel work progresses.

Using fcntl to implement advisory locks works okay between processes, but I don't think it's a good solution for a multi-threaded process. The only other way would be to change the design and have all I/O to/from the files controlled by one process and use Unix sockets for intertask I/O. Not elegant, but you can absolutely control who gets what access and when.
0
 
MarthiAuthor Commented:
Thanks for your attention.

My "application" is really a library that other processes link with. You can think of it as the registry in Windows NT/95/98, except it holds data differently and so on.

While controlling I/O through one process is feasible, I don't think I'll be allowed to do that. I was thinking of a distributed lock manager process which will use pthread_mutex_t 's to control access and every process (thru' my API) will attempt to get a read/write lock from the lock manager before accessing the file.

My solution is no more elegant than yours and the real problem is getting approval for the changed design... :-(

Thanks all the same. I've enjoyed discussing this with you.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 3
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now