CIS 307: An example using Read/Write File Locks

[fcntl.h], [fcntl.c], [fcntlmain.c]

In Stevens "Advanced Programming in the Unix Environment" we see ways to use the Unix service fcntl to lock portions of a file for reading and writing in the manner stated in the Reader and Writer problem [any number of readers at a time, but writers must operate alone]. Here we have three files that adapt and use the code from Stevens:

  • fcntl.h: Specification of the locking functions.
  • fcntl.c: Implementation of the locking functions.
  • fcntlmain.c: Driver that does a simple test of the locking functions.
  • WARNING: A file lock request which is blocked can be interrupted by a signal. In this case the lock operation returns EINTR. Thus we may think we got a lock when we really don't. A solution is to block signals when locking. Another solution is to test the value returned by the lock operation and relock if the value is EINTR. Another solution, which we adopt here, is to do nothing about it.

    fcntl.h

    /* fcntl.h  -- Defines mutexes in terms of read/write locks on files. 
     *             filerwlock, filerwlockCreate, filerwlockDelete,
     *             filerwreadlock, filerwlockUnlock
     */
    
    
    typedef struct {
        int fd;
        int n;} filerwlock;
    
    /* Create N read/write locks and returns the id of this cluster of locks. */
    filerwlock * filerwlockCreate(char *filename, int n);
    
    /* Delete the cluster of read/write locks associated with fl. */
    int filerwlockDelete(filerwlock *fl);
    
    /* Given the read/write lock cluster fl, lock its ith element */
    int filerwreadlock(filerwlock *fl, int i);
    
    int filerwwritelock(filerwlock *fl, int i);
    
    /* Given the lock cluster fl, unlock its ith element */
    int filerwunlock(filerwlock *fl, int i);
    
    /* Given the lock cluster fl, it read locks all its elements */
    int filerwlongreadlock(filerwlock *fl);
    
    /* Given the lock cluster fl, it unlocks all its elements */
    int filerwlongunlock(filerwlock *fl);
    
    

    fcntl.c

    /* fcntl.c  -- Defines mutexes in terms of read/write locks on files. 
     *             (code is mostly from Stevens: Advanced Programming in the
     *             Unix environment. See from page 367 on.
     *             filerwlock, filerwlockCreate, filerwlockDelete,
     *             filerwreadlock, filerwlongreadlock, filerwlongunlock, 
     *             filerwlockUnlock
     */
    
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    
    int		lock_reg(int, int, int, off_t, int, off_t);
    
    #define	read_lock(fd, offset, whence, len) \
    			lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len)
    #define	readw_lock(fd, offset, whence, len) \
    			lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, len)
    #define	write_lock(fd, offset, whence, len) \
    			lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len)
    #define	writew_lock(fd, offset, whence, len) \
    			lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, len)
    #define	un_lock(fd, offset, whence, len) \
    			lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, len)
    
    pid_t	lock_test(int, int , off_t , int , off_t );
    
    #define	is_readlock(fd, offset, whence, len) \
    			lock_test(fd, F_RDLCK, offset, whence, len)
    #define	is_writelock(fd, offset, whence, len) \
    			lock_test(fd, F_WRLCK, offset, whence, len)
    
    
    int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
    {
      struct flock lock;
      lock.l_type = type;     /* F_RDLCK, F_WRLCK, F_UNLCK */
      lock.l_start = offset;  /* byte offset relative to l_whence */
      lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
      lock.l_len = len;       /* #bytes (0 means to EOF) */
      return (fcntl(fd, cmd, &lock));
    }
    
    
    pid_t	lock_test(int fd, int type, off_t offset, int whence, off_t len) 
    {
      struct flock lock;
      lock.l_type = type;     /* F_RDLCK or F_WRLCK */
      lock.l_start = offset;  /* byte offset relative to l_whence */
      lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
      lock.l_len = len;       /* #bytes (0 means to EOF) */
      if (fcntl(fd,F_GETLK,&lock) < 0){
        perror("fcntl"); exit(1);}
      if (lock.l_type == F_UNLCK) 
        return (0);        /* false, region is not locked by another process */
      return (lock.l_pid); /* true, return pid of lock owner */
    }
    
    typedef struct {
        int fd;
        int n;} filerwlock;
    
    /* Create N read/write locks and returns the id of this cluster of locks. */
    filerwlock * filerwlockCreate(char *filename, int n) {
       filerwlock *fl = (filerwlock *)malloc(sizeof(filerwlock));
       if (((fl->fd) = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IWUSR)) < 0) {
          perror("open");
          exit(1);}
       fl->n = n;
       return fl;
     }
    
    /* Delete the cluster of read/write locks associated with fl. */
    int filerwlockDelete(filerwlock *fl) {
       if (close(fl->fd) < 0) {
         perror("close");
         exit(1);}
       return free(fl);
     }
    
    /* Given the read/write lock cluster fl, lock its ith element */
    int filerwreadlock(filerwlock *fl, int i) {
       if ((i < 0) | (i >= fl->n)) {
         printf("filerwlockLock needs i in range 0 .. %d\n", (fl->n)-1);
         exit(0);}
       readw_lock(fl->fd, i, SEEK_SET, 1);
     }
    
    int filerwwritelock(filerwlock *fl, int i) {
       if ((i < 0) | (i >= fl->n)) {
         printf("filerwlockLock needs i in range 0 .. %d\n", (fl->n)-1);
         exit(0);}
       writew_lock(fl->fd, i, SEEK_SET, 1);
     }
    
    /* Given the lock cluster fl, unlock its ith element */
    int filerwunlock(filerwlock *fl, int i){
    
       if ((i < 0) | (i >= fl->n)) {
         printf("filerwlockUnlock needs i in range 0 .. %d\n", (fl->n)-1);
         exit(0);}
       un_lock(fl->fd, i, SEEK_SET, 1);
     }
    
    /* Given the lock cluster fl, it read locks all its elements */
    int filerwlongreadlock(filerwlock *fl) {
      readw_lock(fl->fd, 0, SEEK_SET, fl->n);
    }
    
    /* Given the lock cluster fl, it unlocks all its elements */
    int filerwlongunlock(filerwlock *fl) {
      un_lock(fl->fd, 0, SEEK_SET, fl->n);
    }
    
    

    fcntlmain.c

    /* This is just a driver to test the filerwlock objects defined in fcntl.c */
    /* MAXCHILD processes are forked. They take turns in using LOCKSSIZE locks.*/
    /* I compiled the program as follows                                       */
    /*      cc fcntlmain.c fcntl.c -o fcntlmain                                 */
    /* and then run the image fcntlmain.                                        */
    /* Notice that after the program has run I find the file "mylock" in my    */
    /* directory. Not very desirable. Perhaps there is a way to avoid that?    */
    
    #include <stdio.h>
    #include <sys/types.h>
    #include "fcntl.h"
    
    #define LOCKFILE "mylock"
    #define LOCKSSIZE 5
    #define MAXCHILD 4
    
    void child (int self);
    
    pid_t cldrn[4];
    filerwlock *fl;
    
    int 
    main(void){
        int i;
    
        fl = filerwlockCreate(LOCKFILE, LOCKSSIZE);
        for (i=0;i < MAXCHILD; i++) {
           if ((cldrn[i]=fork()) < 0) {
    	  perror("fork");
    	  exit(1);}
           if (cldrn[i]==0) 
    	  child(i);
         }
        for (i=0; i < MAXCHILD; i++)
           wait();
        filerwlockDelete(fl);
        exit(0);
    }
    
    void child (int self) {
       int i, j;
       char s[256];
       for (j=0; j<8; j++) {
         if (self == 0)
           filerwwritelock(fl,1);
         else if (self == (MAXCHILD-1)) 
           filerwlongreadlock(fl);
         else
           filerwreadlock(fl,1);
         printf("Child %d starts to sleep on lock %d\n", self, 1);
         sleep(3);
         printf("Child %d ends sleep on lock %d\n", self, 1);
         if (self == (MAXCHILD-1))
           filerwlongunlock(fl);
         else
           filerwunlock(fl,1);
         sleep(1);
       }
       exit(0);
    }
    
    

    ingargiola.cis.temple.edu