Chapter 7. File and Record Locking
Prev | Part III. Advanced File Control | Next

## Chapter 7. File and Record Locking

IRIX supports the ability to place a lock upon an entire file or upon a range of bytes within a file. Programs must cooperate in respecting record locks. A file lock can be made mandatory but only at a cost in performance. For these reasons, file and record locking should normally be seen as a synchronization mechanism, not a security mechanism.

The chapter includes these topics:

- “Overview of File and Record Locking” presents an introduction to locking mechanisms.

- “Controlling File Access With File Permissions” discusses the relationship of file permissions to exclusive file access.

- “Using Record Locking” discusses the use of file and record locks to get exclusive data access.

- “Enforcing Mandatory Locking” describes how file locks can be made mandatory on programs that do not use locking.

- “Record Locking Across Multiple Systems” discusses how file locking can be extended to NFS-mounted files.

## Overview of File and Record Locking

Simultaneous access to file data is characteristic of many multiprocess, multithreaded, or real-time applications.The purpose of the file and record locking facility is to provide a way for programs to synchronize their use of common file data.

Advisory file and record locking can be used to coordinate independent, unrelated processes. In mandatory locking, on the other hand, the standard I/O subroutines and I/O system calls enforce the locking protocol. Mandatory locking keeps unrelated programs from accessing data out of sequence, at some cost of access speed.

The system functions used in file and record locking are summarized in Table 7-1.

Table 7-1. Functions for File and Record Locking

Function Name | Purpose and Operation
--- | ---
`fcntl(2)`, `fcntl(5)` | General function for modifying an open file descriptor; can be used to set file and record locks.
`lockf(3C)`, `lockf(3F)` | Library function to set and remove file and record locks on open files (SVR4 compatible).
`f` `lock(3B)` | Library function to set and remove file and record locks on open files (BSD compatible).
`chmod(1)`, `chmod(2)` | Command and system function that can enable mandatory file locking on a specified file.

### Terminology

The discussion of file and record locking depends on the terms defined in this section.

#### Record

A record is any contiguous sequence of bytes in a file. The UNIX operating system does not impose any record structure on files. The boundaries of records are defined by the programs that use the files. Within a single file, a record as defined by one process can overlap partially or completely on a record as defined by some other process.

#### Read (Shared) Lock

A read lock keeps a record from changing while one or more processes read the data. If a process holds a read lock, it may assume that no other process can alter that record at the same time. A read lock is also a shared lock because more than one process can place a read lock on the same record or on a record that overlaps a read-locked record. No process, however, can have a write lock that overlaps a read lock.

#### Write (Exclusive) Lock

A write lock is used to gain complete control over a record. A write lock is an exclusive lock because, when a write lock is in place on a record, no other process may read- or write-lock that record or any data that overlaps it. If a process holds a write lock it can assume that no other process will read or write that record at the same time.

#### Advisory Locking

An advisory lock is visible only when a program explicitly tries to place a conflicting lock. An advisory lock is not visible to the file I/O system functions such as `read()` and `write()`. A process that does not test for an advisory lock can violate the terms of the lock, for example, by writing into a locked record.

Advisory locks are useful when all processes make an appropriate record lock request before performing any I/O operation. When all processes use advisory locking, access to the locked data is controlled by the advisory lock requests. The success of advisory locking depends on the cooperation of all processes in enforcing the locking protocol; it is not enforced by the file I/O subsystem.

#### Mandatory Locking

Mandatory record locking is enforced by the file I/O system functions, and so is effective on unrelated processes that are not part of a cooperating group. Respect for locked records is enforced by the `creat()`, `open()`, `read()`, and `write()` system calls. When a record is locked, access to that record by any other process is restricted according to the type of lock on the record. Cooperating processes should still request an appropriate record lock before an I/O operation, but an additional check is made by IRIX before each I/O operation to ensure the record locking protocol is being honored. Mandatory locking offers security against unplanned file use by unrelated programs, but it imposes additional system overhead on access to the controlled files.

#### Lock Promotion and Demotion

A read lock can be promoted to write-lock status if no other process is holding a read lock in the same record. If processes with pending write locks are waiting for the same record, the lock promotion succeeds and the other (sleeping) processes wait. Demoting a write lock to a read lock can be done at any time.

Because the `lockf()` function does not support read locks, lock promotion is not applicable to locks set with that call. >

## Controlling File Access With File Permissions

The access permissions for each UNIX file control which users can read, write, or execute the file. These access permissions may be set only by the owner of the file or by the superuser. The permissions of the directory in which the file resides can also affect the access permissions for a file. Note that if the permissions for a directory allow anyone to write in the directory, and the “sticky bit” is not included in the permissions, files within that directory can be removed even by a user who does not have read, write, or execute permission for those files.

If your application warrants the use of record locking, make sure that the permissions on your files and directories are also set properly. A record lock, even a mandatory record lock, protects only the records that are locked, while they are locked. Unlocked parts of the files can be corrupted if proper precautions are not taken.

Only a known set of programs or users should be able to read or write a database. This can be enforced through file permissions as follows:

1. Using the `chown` facility (see the `chown(1)` and `chown(2)` reference pages), set the ownership of the critical directories and files to reflect the authorized group ID.

2. Using the `chmod` facility (see also the `chmod(1)` and `chmod(2)` reference pages), set the file permissions of the critical directories and files so that only members of the authorized group have write access (“775” permissions).

3. Using the `chown` facility, set the accessing program executable files to be owned by the authorized group.

4. Using the `chmod` facility, set the set-GID bit for each accessing program executable file and to permit execution by anyone (“2755” permissions).

Users who are not members of the authorized group cannot modify the critical directories and files. However, when an ordinary user executes one of the accessing programs, the program automatically adopts the group ID of its owner. The accessing program can create and modify files in the critical directory, but other programs started by an ordinary user cannot.

## Using Record Locking

This section covers the following topics:

- “Opening a File for Record Locking”

- “Setting a File Lock”

- “Setting and Removing Record Locks”

- “Getting Lock Information”

- “Deadlock Handling”

### Opening a File for Record Locking

The first requirement for locking a file or segment of a file is having a valid open file descriptor. If read locks are to be used, then the file must be opened with at least read access; likewise for write locks and write access.

Example 7-1 opens a file for both read and write access.

Example 7-1. Opening a File for Locked Use

    #include <stdio.h>
    #include <errno.h>
    #include <fcntl.h>
    int fd;    /* file descriptor */
    char *filename;
    main(argc, argv)
    int argc;
    char *argv[];
    {
        extern void exit(), perror();
        /* get database file name from command line and open the
         * file for read and write access.
         */
        if (argc < 2) {
            (void) fprintf(stderr, "usage: %s filename\n", argv[0]);
            exit(2);
        }
        filename = argv[1];
        fd = open(filename, O_RDWR);
        if (fd < 0) {
            perror(filename);
            exit(2);
        }
    }

The file is now open to perform both locking and I/O functions. The next step is to set a lock.

### Setting a File Lock

Several ways exist to set a lock on a file. These methods depend upon how the lock interacts with the rest of the program. Issues of portability and performance need to be considered. Three methods for setting a lock are given here: using the `fcntl()` system call; using the /usr/group standards-compatible `lockf()` library function; and using the BSD compatible `flock()` library function.

Locking an entire file is just a special case of record locking—one record is locked, which has the size of the entire file. The file is locked starting at a byte offset of zero and size of the maximum file size. This size is beyond any real end-of-file so that no other lock can be placed on the file.

You have a choice of three functions for this operation: the basic `fcntl()`, the library function `lockf()`, and the BSD compatible library function `flock()`. All three functions can interoperate. That is, a lock placed by one is respected by the other two.

#### Whole-File Lock With fcntl()

The `fcntl()` function treats a lock length of 0 as meaning “size of file.” The function `lockWholeFile()` in Example 7-2 attempts a specified number of times to obtain a whole-file lock using `fcntl()`. When the lock is placed, it returns 0; otherwise it returns the error code for the failure.

Example 7-2. Setting a Whole-File Lock With fcntl()

    #include <fcntl.h>
    #include <errno.h>
    #define MAX_TRY 10

    int
    lockWholeFile(int fd, int tries)
    {
        int limit = (tries)?tries:MAX_TRY;
        int try;
        struct flock lck;
        lck.l_type = F_WRLCK;       /* write (exclusive) lock */
        lck.l_whence = 0;           /* 0 offset for l_start */
        lck.l_start = 0L;           /* lock starts at BOF */
        lck.l_len = 0L;             /* extent is entire file */
        for (try = 0; try < limit; ++try)
        {
            if ( 0 == fcntl(fd, F_SETLK, &lck) )
                break; /* mission accomplished */
            if ((errno != EAGAIN) && (errno != EACCES))
                break; /* mission impossible */
            sginap(1); /* let lock holder run */
        }
        return errno;
    }

The following points should be noted in Example 7-2:

- Because `fcntl()` supports both read and write locks, the type of the lock (F_WRLCK) is specified in the l_type.

- The operation code F_SETLK is used to request that the function return if it cannot place the lock. The code F_SETLKW would request that the function suspend until the lock can be placed.

- The starting location of the record is the sum of two fields, l_whence and l_start. Both must be set to 0 in order to get the starting point to the beginning of the file.

#### Whole-File Lock With lockf()

Example 7-3 shows a version of the `lockWholeFile()` function that uses `lockf()`. Like `fcntl()`, `lockf()` treats a record length of 0 as meaning “to end of file.”

Example 7-3. Setting a Whole-File Lock With lockf()

    #include <unistd.h> /* for F_TLOCK */
    #include <fcntl.h>  /* for O_RDWR */
    #include <errno.h>  /* for EAGAIN */
    #define MAX_TRY 10

    int
    lockWholeFile(int fd, int tries)
    {
        int limit = (tries)?tries:MAX_TRY;
        int try;
        lseek(fd,0L,SEEK_SET);  /* set start of lock range */
        for (try = 0; try < limit; ++try)
        {
            if (0 == lockf(fd, F_TLOCK, 0L) )
                break; /* mission accomplished */
            if (errno != EAGAIN)
                break; /* mission impossible */
            sginap(1); /* let lock holder run */
        }
        return errno;
    }

The following points should be noted about Example 7-3:

- The type of lock is not specified, because `lockf()` only supports exclusive locks.

- The operation code F_TLOCK specifies that the function should return if the lock cannot be placed. The F_LOCK operation would request that the function suspend until the lock could be placed.

- The start of the record is set implicitly by the current file position. That is why `lseek()` is called, to ensure the correct file position before `lockf()` is called.

#### Whole-File Lock With flock()

Example 7-4 displays a third example of the lockWholeFile subroutine, this one using `flock()`.

Example 7-4. Setting a Whole-File Lock With flock()

    #define _BSD_COMPAT
    #include <sys/file.h> /* includes fcntl.h */
    #include <errno.h>  /* for EAGAIN */
    #define MAX_TRY 10
    int
    lockWholeFile(int fd, int tries)
    {
        int limit = (tries)?tries:MAX_TRY;
        int try;
        for (try = 0; try < limit; ++try)
        {
            if ( 0 == flock(fd, LOCK_EX+LOCK_NB) )
                break; /* mission accomplished */
            if (errno != EWOULDBLOCK)
                break; /* mission impossible */
            sginap(1); /* let lock holder run */
        }
        return errno;
    }

The following points should be noted about Example 7-4:

- The compiler variable _BSD_COMPAT is defined in order to get BSD-compatible definitions from standard header files.

- The only use of `flock()` is to lock an entire file, so there is no attempt to specify the start or length of a record.

- The LOCK_NB flag requests the function to return if the lock cannot be placed. Without this flag the function suspends until the lock can be placed.

### Setting and Removing Record Locks

Locking a record is done the same way as locking a file, except that the record does not encompass the entire file contents. This section examines an example problem of dealing with two records (which may be either in the same file or in different files) that must be updated simultaneously so that other processes get a consistent view of the information they contain. This type of problem occurs, for example, when updating the inter-record pointers in a doubly linked list.

To deal with multiple locks, consider the following questions:

- What do you want to lock?

- For multiple locks, in what order do you want to lock and unlock the records?

- What do you do if you succeed in getting all the required locks?

- What do you do if you fail to get one or more locks?

In managing record locks, you must plan a failure strategy for the case in which you cannot obtain all the required locks. It is because of contention for these records that you have decided to use record locking in the first place. Different programs might

- wait a certain amount of time, and try again

- end the procedure and warn the user

- let the process sleep until signaled that the lock has been freed

- a combination of the above

Look now at the example of inserting an entry into a doubly linked list. All the following examples assume that a record is declared as follows:

    struct record {
    .../* data portion of record */...
        long prev;    /* index to previous record in the list */
        long next;    /* index to next record in the list */
    };

For the example, assume that the record after which the new record is to be inserted has a read lock on it already. The lock on this record must be promoted to a write lock so that the record may be edited. Example 7-5 shows a function that can be used for this.

Example 7-5. Record Locking With Promotion Using fcntl()

    /*
    || This function is called with a file descriptor and the
    || offsets to three records in it: this, here, and next.
    || The caller is assumed to hold read locks on both here and next.
    || This function promotes these locks to write locks.
    || If write locks on "here" and "next" are obtained
    ||    Set a write lock on "this".
    ||    Return index to "this" record.
    || If any write lock is not obtained:
    ||    Restore read locks on "here" and "next".
    ||    Remove all other locks.
    ||    Return -1.
    */
    long set3Locks(int fd, long this, long here, long next)
    {
        struct flock lck;
        lck.l_type = F_WRLCK;    /* setting a write lock */
        lck.l_whence = 0;        /* offsets are absolute */
        lck.l_len = sizeof(struct record);
        /* Promote the lock on "here" to write lock */
        lck.l_start = here;
        if (fcntl(fd, F_SETLKW, &lck) < 0) {
            return (-1);
        }
        /* Lock "this" with write lock */
        lck.l_start = this;
        if (fcntl(fd, F_SETLKW, &lck) < 0) {
            /* Failed to lock "this"; return "here" to read lock. */
            lck.l_type = F_RDLCK;
            lck.l_start = here;
            (void) fcntl(fd, F_SETLKW, &lck);
            return (-1);
        }
        /* Promote lock on "next" to write lock */
        lck.l_start = next;
        if (fcntl(fd, F_SETLKW, &lck) < 0) {
            /* Failed to promote "next"; return "here" to read lock... */
            lck.l_type = F_RDLCK;
            lck.l_start = here;
            (void) fcntl(fd, F_SETLK, &lck);
            /* ...and remove lock on "this".  */
            lck.l_type = F_UNLCK;
            lck.l_start = this;
            (void) fcntl(fd, F_SETLK, &lck);
            return (-1)
        }
        return (this);
    }

Example 7-5 uses the F_SETLKW command to `fcntl()`, with the result that the calling process will sleep if there are conflicting locks at any of the three points. If the F_SETLK command was used instead, the `fcntl()` system calls would fail if blocked. The program would then have to be changed to handle the blocked condition in each of the error return sections (as in Example 7-2).

It is possible to unlock or change the type of lock on a subsection of a previously set lock; this may cause an additional lock (two locks for one system call) to be used by the operating system. This occurs if the subsection is from the middle of the previously set lock.

Example 7-6 shows a similar example using the `lockf()` function. Since it does not support read locks, all (write) locks are referenced generically as locks.

Example 7-6. Record Locking Using lockf()

    /*
    || This function is called with a file descriptor and the
    || offsets to three records in it: this, here, and next.
    || The caller is assumed to hold no locks on any of the records.
    || This function tries to lock "here" and "next" using lockf().
    || If locks on "here" and "next" are obtained
    ||    Set a lock on "this".
    ||    Return index to "this" record.
    || If any lock is not obtained:
    ||    Remove all other locks.
    ||    Return -1.
    */
    long set3Locks(int fd, long this, long here, long next)
    {
        /* Set a lock on "here" */
        (void) lseek(fd, here, 0);
        if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
            return (-1);
        }
        /* Lock "this" */
        (void) lseek(fd, this, 0);
        if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
            /* Failed to lock "this"; clear "here" lock. */
            (void) lseek(fd, here, 0);
            (void) lockf(fd, F_ULOCK, sizeof(struct record));
            return (-1);
        }
        /* Lock "next" */
        (void) lseek(fd, next, 0);
        if (lockf(fd, F_LOCK, sizeof(struct record)) < 0) {
            /* Failed to lock "next"; release "here"... */
            (void) lseek(fd, here, 0);
            (void) lockf(fd, F_ULOCK, sizeof(struct record));
            /* ...and remove lock on "this".  */
            (void) lseek(fd, this, 0);
            (void) lockf(fd, F_ULOCK, sizeof(struct record));
            return (-1)
        }
        return (this);
    }

Locks are removed in the same manner as they are set; only the lock type is different (F_UNLCK or F_ULOCK). An unlock cannot be blocked by another process. An unlock can affect only locks that were placed by the unlocking process.

### Getting Lock Information

You can determine which processes, if any, are blocking a lock from being set. This can be used as a simple test or as a means to find locks on a file. To find this information, set up a lock as in the previous examples and use the F_GETLK command in the `fcntl()` call. If the lock passed to `fcntl()` would be blocked, the first blocking lock is returned to the process through the structure passed to `fcntl()`. That is, the lock data passed to `fcntl()` is overwritten by blocking lock information.

The returned information includes two pieces of data, l_pidf and l_sysid, that are used only with F_GETLK. These fields uniquely identify the process holding the lock. (For systems that do not support a distributed architecture, the value in l_sysid can be ignored.)

If a lock passed to `fcntl()` using the F_GETLK command is not blocked by another lock, the l_type field is changed to F_UNLCK and the remaining fields in the structure are unaffected.

Example 7-7 shows how to use this capability to print all the records locked by other processes. Note that if several read locks occur over the same record, only one of these is found.

Example 7-7. Detecting Contending Locks Using fcntl()

    /*
    || This function takes a file descriptor and prints a report showing
    || all locks currently set on that file. The loop variable is the
    || l_start field of the flock structure. The function asks fcntl()
    || for the first lock that would block a lock from l_start to the end
    || of the file (l_len==0). When no lock would block such a lock,
    || the returned l_type contains F_UNLCK and the loop ends.
    || Otherwise the contending lock is displayed, l_start is set to
    || the end-point of that lock, and the loop repeats.
    */
    void printAllLocksOn(int fd)
    {
        struct flock lck;
        /* Find and print "write lock" blocked segments of file. */
        (void) printf("sysid pid type start length\n");
        lck.l_whence = 0;
        lck.l_start = 0L;
        lck.l_len = 0L;
        for( lck.l_type = 0; lck.l_type != F_UNLCK; )
        {
            lck.l_type = F_WRLCK;
            (void) fcntl(fd, F_GETLK, &lck);
            if (lck.l_type != F_UNLCK)
            {
                (void) printf("%5d %5d %c %8d %8d\n",
                              lck.l_sysid,
                              lck.l_pid,
                              (lck.l_type == F_WRLCK) ? 'W' : 'R',
                              lck.l_start,
                              lck.l_len);
                if (lck.l_len == 0)
                    break; /* this lock goes to end of file, stop */
                lck.l_start += lck.l_len;
            }
        }
    }

`fcntl()` with the F_GETLK command always returns correctly (that is, it will not sleep or fail) if the values passed to it as arguments are valid.

The `lockf()` function with the F_TEST command can also be used to test if there is a process blocking a lock. This function does not, however, return the information about where the lock actually is and which process owns the lock. Example 7-8 shows a code fragment that uses `lockf()` to test for a lock on a file.

Example 7-8. Testing for Contending Lock Using lockf()

    /* find a blocked record. */
    /* seek to beginning of file */
    (void) lseek(fd, 0, 0L);
    /* set the size of the test region to zero
     * to test until the end of the file address space.
     */
    if (lockf(fd, F_TEST, 0L) < 0) {
        switch (errno) {
        case EACCES:
        case EAGAIN:
            (void) printf("file is locked by another process\n");
            break;
        case EBADF:
            /* bad argument passed to lockf */
            perror("lockf");
            break;
        default:
            (void) printf("lockf: unknown error <%d>\n", errno);
            break;
        }
    }

When a process forks, the child receives a copy of the file descriptors that the parent has opened. The parent and child also share a common file pointer for each file. If the parent seeks to a point in the file, the child's file pointer is also set to that location. Similarly, when a share group of processes is created using `sproc()`, and the `sproc()` flag PR_SFDS is used to keep the open-file table synchronized for all processes (see the `sproc(2)` reference page), then there is a single file pointer for each file and it is shared by every process in the share group.

This feature has important implications when using record locking. The current value of the file pointer is used as the reference for the offset of the beginning of the lock, in `lockf()` at all times and in `fcntl()` when using an l_whence value of 1. Since there is no way to perform the sequence lseek(); fcntl(); as an atomic operation, there is an obvious potential for race conditions—a lock might be set using a file pointer that was just changed by another process.

The solution is to have the child process close and reopen the file. This creates a distinct file descriptor for the use of that process. Another solution is to always use the `fcntl()` function for locking with an l_whence value of 0 or 2. This makes the locking function independent of the file pointer (processes might still contend for the use of the file pointer for other purposes such as direct-access input).

### Deadlock Handling

A certain level of deadlock detection and avoidance is built into the record locking facility. This deadlock handling provides the same level of protection granted by the /usr/group standard `lockf()` call. This deadlock detection is valid only for processes that are locking files or records on a single system.

Deadlocks can potentially occur only when the system is about to put a record locking system call to sleep. A search is made for constraint loops of processes that would cause the system call to sleep indefinitely. If such a situation is found, the locking system call fails and sets errno to the deadlock error number.

If a process wishes to avoid using the system's deadlock detection, it should set its locks using F_GETLK instead of F_GETLKW.

## Enforcing Mandatory Locking

File locking is usually an in-memory service of the IRIX kernel. The kernel keeps a table of locks that have been placed. Processes anywhere in the system update the table by calling `fcntl()` or `lockf()` to request locks. When all processes that use a file do this, and respect the results, file integrity can be maintained.

It is possible to extend file locking by making it mandatory on all processes, whether or not they were designed to be part of the cooperating group. Mandatory locking is enforced by the file I/O function calls. As a result, an independent process that calls `write()` to update a locked record is blocked or receives an error code.

The `write()` and other system functions test for a contending lock on a file that has mandatory locking applied. The test is made for every operation on that file. When the caller is a process that is cooperating in the lock, and has already set an appropriate lock, the mandatory test is unnecessary overhead.

Mandatory locking is enforced on a file-by-file basis, triggered by a bit in the file inode that is set by `chmod` (see the `chmod(1)` and `chmod(2)` reference pages). In order to enforce mandatory locking on a particular file, turn on the set-group-ID bit along with a nonexecutable group permission, as in these examples, which are equivalent:

    $ chmod 2644 target.file
    $ chmod +l target.file

The bit must be set before the file is opened; a change has no effect on a file that is already open.

Example 7-9 shows a fragment of code that sets mandatory lock mode on a given filename.

Example 7-9. Setting Mandatory Locking Permission Bits

    #include <sys/types.h>
    #include <sys/stat.h>
    int setMandatoryLocking(char *filename)
    {
       int mode;
       struct stat buf;
       if (stat(filename, &buf) < 0)
       {
          perror("stat(2)");
          return error;
       }
       mode = buf.st_mode;
       /* ensure group execute permission 0010 bit is off */
       mode &= ~(S_IEXEC>>3);
       /* turn on 'set group id bit' in mode */
       mode |= S_ISGID;
       if (chmod(filename, mode) < 0)
       {
          perror("chmod(2)");
          return error;
       }
       return 0;
    }

When IRIX opens a file, it checks to see whether both of two conditions are true:

- Set-group-ID bit is 1.

- Group execute permission is 0.

When both are true, the file is marked for mandatory locking, and each use of `creat()`, `open()`, `read()`, and `write()` tests for contending locks.

Some points to remember about mandatory locking:

- Mandatory locking does not protect against file truncation with the `truncate()` function (see the `truncate(2)` reference page), which does not look for locks on the truncated portion of the file.

- Mandatory locking protects only those portions of a file that are locked. Other portions of the file that are not locked may be accessed according to normal UNIX system file permissions.

- Advisory locking is more efficient because a record lock check does not have to be performed for every I/O request.

## Record Locking Across Multiple Systems

Record locking is always effective within a single copy of the IRIX kernel. Locking is effective within a multiprocessor because processes running in different CPUs of the multiprocessor share a single copy of the IRIX kernel.

Record locking can be effective on processes that execute in different systems that access a filesystem mounted through NFS. However, there are these drawbacks:

- Deadlock detection is not possible between processes in different systems.

- You must make sure that the NFS locking daemon is running in both the NFS client (application) and server systems.

- Using record locking on NFS files has a strong impact on performance.

### NFS File Locking

When a process running in an NFS client system requests a file or record lock, a complex sequence of events begins. (For details, consult the `lockd(1M)` reference page.)

First the kernel in the client system receives the lock request and determines that the file resides on a filesystem mounted using NFS. The kernel sends the lock request to a daemon called `rpc.lockd`. This daemon is responsible for communicating lock requests to other systems.

The `rpc.lockd` process sends the lock request to the `rpc.lockd` daemon running on the NFS server where the target file is physically mounted. On the server, that `rpc.lockd` issues the lock request locally. The server `rpc.lockd` sends the result, success or failure, back to the client `rpc.lockd`. The result is passed back to the calling process.

When the lock succeeds on the server side, `rpc.lockd` on the client system requests another daemon, `rpc.statd`, to monitor the NFS server that implements the lock. If the server fails and then recovers, `rpc.statd` will be informed. It then tries to reestablish all active locks. If the NFS server fails and recovers, and `rpc.lockd` is unable to reestablish a lock, it sends a signal (SIGUSR1) to the process that requested the lock.

When a process writes to a write-locked record, the data is sent directly to the NFS server, bypassing the local NFS buffer cache. This can have a significant impact on file performance.

### Configuring NFS Locking

When `rpc.lockd` is not running in the NFS client system, or in the NFS server system, a cross-system lock cannot be established. In this case, locks are effective within the local system, but are not effective against contending file access from other systems.

To discover whether `rpc.lockd` is running, use the `chkconfig` command:

    % /etc/chkconfig | grep lockd

If the returned value is off, `rpc.lockd` is not running and locks have local scope only.

To use `rpc.lockd`, the administrator must configure it on as follows:

    % /etc/chkconfig lockd on

Then the system must be rebooted. This must be done on both the NFS file server and on all NFS clients where locks are requested.

### Performance Impact

Normally, the NFS software uses a data cache to speed access to files. Data read or written to NFS mounted files is held in a memory cache for some time, and access requests to cached data is satisfied from memory instead of being read from the server. Data caching has a major effect on the speed of NFS file access.

As soon as any process places a file or record lock on an NFS mounted file, the file is marked as uncachable. All I/O requests for that file bypass the local memory cache and are sent to the NFS server. This ensures consistent results and data integrity. However, it means that every read or write to the file, at any offset, and from any process, incurs a network delay.

The file remains uncachable even when the lock is released. The file cannot use the cache again until it has been closed by all processes that have it open.

Prev | Table of Contents | Next
Part III. Advanced File Control |  | Chapter 8. Using Asynchronous I/O