Understanding File Accessibility and File Locks with access() and fcntl() functions

Discussion in 'C' started by poornaMoksha, Dec 27, 2011.

  1. poornaMoksha

    poornaMoksha New Member

    Joined:
    Jan 29, 2011
    Messages:
    150
    Likes Received:
    33
    Trophy Points:
    0
    Occupation:
    Software developer
    Location:
    India
    Recently I have been writing a lot on Linux files and functions related to file I/O. So, extending the discussion further in this article lets discuss two more important Linux functions :

    A) access(const char *pathname, int mode)
    • This function checks whether the calling process can access the file pathname.
    • The argument 'mode' is F_OK to test the existence of a file.
    • The flags R_OK, W_OK or X_OK can be used to check whether the file grants read, write or execute permissions.
    • The function checks the accessibility of file using the calling process real UID and GID.
    • The function returns 0 on success.
    B) fcntl(int fd, int cmd, ... /* arg */ )

    This function is used for various purposes.
    • Duplicating a file descriptor
    • Manipulating File descriptor flags
    • Advisory locking
    We will not go into the details of first two but will describe the third purpose in detail.
    By advisory locking we mean to test or to put lock on file. Locks can be the same way as we put mutexs on the code blocks in our programs.

    F_GETLK, F_SETLK and F_SETLKW are used to acquire, release, and test for the existence of record locks. The third argument, lock, is a pointer to a structure that has at least the following fields (in unspecified order).

    Code:
    struct flock { 
    ... 
    short l_type;    /* Type of lock: F_RDLCK, 
                       F_WRLCK, F_UNLCK */ 
    short l_whence;  /* How to interpret l_start: 
                       SEEK_SET, SEEK_CUR, SEEK_END */ 
    off_t l_start;   /* Starting offset for lock */ 
    off_t l_len;     /* Number of bytes to lock */ 
    pid_t l_pid;     /* PID of process blocking our lock 
                       (F_GETLK only) */ 
    ... 
    }; 
    Lets discuss these two functions with some code snippets.

    The access() Function



    Here is the code :

    Code:
    #include <errno.h> 
    #include <stdio.h> 
    #include <unistd.h> 
     
    int main (int argc, char* argv[]) 
    { 
      if(argc < 2) 
      { 
          printf("\n Probably you didn't specify the text file path\n"); 
          return -1; 
      } 
      
      // Point the pointer 'path' to the file  
      // input by user. 
      char* path = argv[1]; 
      int rval; 
     
      // Call the function 'access()' 
      // The argument 'F_OK'is to check for  
      // existence of file 
      rval = access (path, F_OK); 
      if (rval == 0) 
        printf ("%s The file exists\n", path); 
      else { 
        if (errno == ENOENT) 
           printf ("%s File does not exist\n", path); 
        else if (errno == EACCES) 
           printf ("%s File is not accessible\n", path); 
        return 0; 
      } 
     
      /* Check read access. */ 
      rval = access (path, R_OK); 
      if (rval == 0) 
        printf ("%s File is readable\n", path); 
      else 
        printf ("%s File is not readable (access denied)\n", path); 
     
      /* Check write access. */ 
      rval = access (path, W_OK); 
      if (rval == 0) 
        printf ("%s File is writable\n", path); 
      else if (errno == EACCES) 
        printf ("%s File is not writable : access denied\n", path); 
      else if (errno == EROFS) 
        printf ("%s File is not writable : read only file system \n", path); 
      return 0; 
    }
    In the above code :
    • The user enters the path of the file to be tested.
    • If the user does not enter the file path, the code returns straight away.
    • The first call to the function access() with second argument as F_OK tests whether the file exists or not.
    • The second and third call to function access() with second arguments R_OK and W_OK are used to test the read and write accessibilities to the file.
    When the above code was executed, the following output was generated:

    Code:
    $ ./access test.txt 
    test.txt The file exists 
    test.txt File is readable 
    test.txt File is writable
    So we see that the file that we gave as an input exists and is both readable and writable.

    File locking using fcntl() function



    As we already described that file locking can be exercised using the fcntl function. Lets first understand why is it required ?

    Suppose one process is accessing a file by writing something to it. Meanwhile a second process tries to access the file, since the first process is already accessing the file so a second process accessing the same file can cause undefined results. Recall that mutexs do the same stuff for the code pieces which is being accessed by two different threads simultaneously. So we use file locks and for file locks we use fcntl function.

    Lets understand this function using a code snippet.

    Here is the code :

    Code:
    #include <fcntl.h> 
    #include <stdio.h> 
    #include <string.h> 
    #include <unistd.h> 
    int main (int argc, char* argv[]) 
    { 
      // Check if user entered a file path 
      if(argc < 2) 
      { 
          printf("\n Enter the file path\n"); 
          return -1; 
      } 
      // Store the address of the string containing 
      // text file in the variable 'file'. 
      char* file = argv[1]; 
     
      int fd;    
      struct flock lock; 
      printf ("opening %s\n", file); 
       
      // Open the file in read write mode 
      fd = open (file, O_WRONLY); 
      printf ("locking\n"); 
     
      //Use memset to initialize the structure 'flock' 
      memset (&lock, 0, sizeof(lock)); 
     
      // Hmm...we are preparing to lock file for write access 
      lock.l_type = F_WRLCK; 
     
      // put a write lock on file 
      if (-1 == fcntl (fd, F_SETLK, &lock)) 
      { 
          printf("\nCannot lock..hit enter to return\n"); 
      } 
      else 
      { 
          printf ("locked; hit Enter to unlock... "); 
      } 
       
      // Wait until user enters a character 
      getchar (); 
     
      printf ("unlocking\n"); 
     
      // Unlock process 
      lock.l_type = F_UNLCK; 
      fcntl (fd, F_SETLK, &lock); 
     
      // Close the file 
      close (fd); 
      return 0; 
    }
    In the above code snippet :
    • Code expects the user to input the file path
    • The file is opened in read and write mode.
    • We set 'lock.l_type' with value F_WRLCK for a write lock on file.
    • With this we call the function fcntl() function which actually places this lock on file.
    • Then we wait for the user to enter a key so that we can unlock the file and return.
    The code above works as expected. Here is the output :

    Code:
    $ ./fcntl test.txt 
    opening test.txt 
    locking 
    locked; hit Enter to unlock...  
    unlocking
    We see in the output, the file is opened, lock is placed and then after user hits a key, the file is unlocked and the program returns.

    Lets see what happens when the program is waiting for user to hit a key and meanwhile we run this program again and try to lock the same file :

    Code:
    $ ./fcntl test.txt 
    opening test.txt 
    locking 
     
    Cannot lock..returning 
     
    unlocking
    We see that the second process was not able to lock the file and it returned.

    Now what if we want the second process to wait until the first process unlocks the file. I mean if we do not want the second process to return immediately. Well we have a provision for that too. Look the following piece of code :

    Code:
    #include <fcntl.h> 
    #include <stdio.h> 
    #include <string.h> 
    #include <unistd.h> 
    int main (int argc, char* argv[]) 
    { 
      // Check if user entered a file path 
      if(argc < 2) 
      { 
          printf("\n Enter the file path\n"); 
          return -1; 
      } 
      // Store the address of the string containing 
      // text file in the variable 'file'. 
      char* file = argv[1]; 
     
      int fd;    
      struct flock lock; 
      printf ("opening %s\n", file); 
       
      // Open the file in read write mode 
      fd = open (file, O_WRONLY); 
      printf ("locking\n"); 
     
      //Use memset to initialize the structure 'flock' 
      memset (&lock, 0, sizeof(lock)); 
     
      // Hmm...we are preparing to lock file for write access 
      lock.l_type = F_WRLCK; 
     
      // put a write lock on file 
      if (-1 == fcntl (fd, F_SETLKW, &lock)) 
      { 
          printf("\nCannot lock..hit enter to return\n"); 
      } 
      else 
      { 
          printf ("locked; hit Enter to unlock... "); 
      } 
       
      // Wait until user enters a character 
      getchar (); 
     
      printf ("unlocking\n"); 
     
      // Unlock process 
      lock.l_type = F_UNLCK; 
      fcntl (fd, F_SETLKW, &lock); 
     
      // Close the file 
      close (fd); 
      return 0; 
    }
    Basically the code remains more or less the same with the differentiating point being the lock type, which now is F_SETLKW.

    Lets run the code now.

    This is the first process :

    Code:
    $ ./fcntl test.txt 
    opening test.txt 
    locking 
    locked; hit Enter to unlock...
    This is the second process :

    Code:
    $ ./fcntl test.txt 
    opening test.txt 
    locking
    We see that the second process is waiting.
    And when the first process is taken further by user entering a key.
    The second process shows :

    Code:
    $ ./fcntl test.txt 
    opening test.txt 
    locking 
    locked; hit Enter to unlock...  
    unlocking
    Hence we see that the second process places the lock on file and completes.

    Conclusion



    To conclude, In this article we studied two different but important system calls related to files. Through the function access() we can test the accessibility of files while through fcntl() we can do various things including the locking system on files.

    Stay tuned for more.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice