Monday, November 3, 2014

Unix Prog: Record locking(1)

1. Defintion

Record locking is the term normally used to describe the ability of a process to prevent other processes from modifying a region of a file while the first process is reading or modifying that portion of the file.

2. fcntl file locking

System Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/fcntl.h  
 ......  
 /* Do the file control operation described by CMD on FD.  
   The remaining arguments are interpreted depending on CMD.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int fcntl (int __fd, int __cmd, ...);  
 ......  
 /* XPG wants the following symbols.    has the same definitions.  */
 #if defined __USE_XOPEN || defined __USE_XOPEN2K8
 # define SEEK_SET       0       /* Seek from beginning of file.  */
 # define SEEK_CUR       1       /* Seek from current position.  */
 # define SEEK_END       2       /* Seek from end of file.  */
 #endif  /* XPG */
 ......
 ubuntu@ip-172-31-23-227:~$ less /usr/include/asm-generic/fcntl.h  
 ......  
 #define F_DUPFD     0    /* dup */  
 #define F_GETFD     1    /* get close_on_exec */  
 #define F_SETFD     2    /* set/clear close_on_exec */  
 #define F_GETFL     3    /* get file->f_flags */  
 #define F_SETFL     4    /* set file->f_flags */  
 #ifndef F_GETLK  
 #define F_GETLK     5  
 #define F_SETLK     6  
 #define F_SETLKW    7  
 #endif  
 ......  
 struct flock {
         short   l_type;
         short   l_whence;
         __kernel_off_t  l_start;
         __kernel_off_t  l_len;
         __kernel_pid_t  l_pid;
         __ARCH_FLOCK_PAD
 };
 ......
 /* for posix fcntl() and lockf() */
 #ifndef F_RDLCK
 #define F_RDLCK         0
 #define F_WRLCK         1
 #define F_UNLCK         2
 #endif
 ......

We can use fcntl to establish the file region lock.
1) The first argument of fcntl represents the file descriptor we are going to work against

2) The second argument of fcntl represents the command we can use. For record locking, we can use F_GETLK, F_SETLK, F_SETLKW.
F_GETLK: fcntl will determine whether the lock described by 3rd argument: struct flock pointer is blocked by some other lock. If a lock exists, the information on that existing lock will overwrite the information pointed to by struct flock pointer.
F_SETLK: set the lock described by the 3rd argument: struct flock pointer. If there is one lock there, fcntl will return immediately.
F_SETLKW: a blocking version of F_SETLK, if there is a lock there, the calling process will be put to sleep. The process wake up either when the lock becomes available or when interrupted by a signal.

3) The 3rd argument of fcntl represents a pointer of struct flock:
l_type can be: F_RDLCK, F_WRLCK, F_UNLCK. When command is F_GETLK, it represents the existing lock type. When command is F_SETLK, it will establish the read lock, write lock or unlock current existing lock.

l_whence can be: SEEK_SET, SEEK_CUR, SEEK_END
l_start: the starting byte offset of the region being locked or unlocked
l_len: the size of the region in bytes
l_pid: the ID of the process holding the lock that can block the current process.

3. fcntl lock rule:
1) lock can start and extend beyond the current end of file, but cannot start or extend before the beginning of the file
2) if l_len is 0, it means that the lock extends to the largest possible offset of the file. It allows us to lock a region starting anywhere in the file, up through and including any data that is appended to the file.
3) to lock the entire file, we set l_start and l_whence to point to the beginning of the file and specify a length(l_len) of 0.
4) If there are read locks on a byte, there can be more read locks but there can't be any more write locks
If there is a write lock on a byte, there can't be any more locks
5) rule 4 applies to lock requests made from different processes, not to multiple lock requests made by a single process. If a process has an existing lock on a range of file, a subsequent attempt to place a lock on the same range by the same process will replace the existing lock with the new one.
6) To obtain a read lock, the descriptor must be open for reading; to obtain a write lock, the descriptor must be open for writing.
7) When setting or releasing the lock on a file, the system combines or splits adjacent areas as required.

4. Example:
lock.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<fcntl.h>  
   
 #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))  
 #define is_read_lockable(fd, offset, whence, len) \  
  (lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)  
 #define is_write_lockable(fd, offset, whence, len) \  
  (lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)  
   
 int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)  
 {  
  struct flock lock;  
   
  lock.l_type = type;  
  lock.l_start = offset;  
  lock.l_whence = whence;  
  lock.l_len = len;  
   
  return(fcntl(fd, cmd, &lock));  
 }  
   
 int 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;  
  lock.l_whence = whence;  
  lock.l_len = len;  
   
  if(fcntl(fd, F_GETLK, &lock) < 0) {  
   printf("fcntl error!\n");  
   exit(1);  
  }  
   
  if(lock.l_type == F_UNLCK) return 0; // false, the region is not locked by another proc  
  return lock.l_pid;  
 }  
   
 int main(int argc, char* argv[])  
 {  
  int fd;  
  char buf[2] = {'a', 'b'};  
  pid_t pid;  
   
  // Open the file, create the file descriptor  
  fd = open("test.txt", O_RDWR | O_CREAT);  
  printf("fd: %d\n", fd);  
  write(fd, buf, 2);  
   
  // Lock the file with 2 read lock  
  read_lock(fd, 0, SEEK_SET, 2);  
  printf("is read: %d\n", is_read_lockable(fd, 0, SEEK_SET, 1));  
  printf("is write: %d\n", is_write_lockable(fd, 0, SEEK_SET, 1));  
   
  // Create the child process  
  if((pid = fork()) < 0) {  
   printf("fork error!\n");  
   exit(2);  
  }  
  else if (pid == 0) { // child process  
   read_lock(fd, 0, SEEK_SET, 2);  
   sleep(2);  
   exit(0);  
  }  
   
  // parent process  
  sleep(1);  
  printf("after child process lock the file:\n");  
  printf("is read: %d\n", is_read_lockable(fd, 0, SEEK_SET, 1));  
  printf("is write: %d\n", is_write_lockable(fd, 0, SEEK_SET, 1));  
   
  exit(0);  
 }  

shell:
Before the child process lock the file, the parent process should be able to establish the read/write lock. After the child process "read" lock the file, the parent process still can establish the "read" lock, but it can't establish the "write" lock any more.
 ubuntu@ip-172-31-23-227:~$ ./lock.out  
 fd: 3  
 is read: 1  
 is write: 1  
 after child process lock the file:  
 is read: 1  
 is write: 0  

No comments:

Post a Comment