Go4Expert

Go4Expert (http://www.go4expert.com/)
-   C (http://www.go4expert.com/articles/c-tutorials/)
-   -   Understanding File Accessibility and File Locks with access() and fcntl() functions (http://www.go4expert.com/articles/understanding-file-accessibility-file-t27461/)

poornaMoksha 27Dec2011 22:34

Understanding File Accessibility and File Locks with access() and fcntl() functions
 
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.


All times are GMT +5.5. The time now is 01:37.