From: jer@stiatl.salestech.com (John Ramspott)
Subject: NFS File locking in AIX (bug)
Organization: Sales Technologies, Inc.
Date: Wed, 20 Jan 1993 22:33:56 GMT

Hello AIX developers,


We cannot
get file-locking working when two different processes are trying to
append to the file from two DIFFERENT machines via NFS. The Info
Explorer says that the NFS file lock manager supports the SVID
advisory locking, namely fcntl() and lockf(). We use fcntl() because
it is a part of POSIX and XPG3. The bottom line is we have tried lots
of different things, and the resulting data file contains errors. Typically,
a record from one process will overwrite a partial record, an entire record,
or even multiple records of the other process. The locking does work if
all processes are on the same machine.

We have spent over two days trying to get this to work, throwing me off
schedule quite a bit. This was suppose to just work :-). I give it a
very high priority. Running one of the processes on the NFS Server machine
(Barney in our case) seems to make very little difference. If two different
machines are used, you usually get errors.

The program will open a file called trylock.dat in the current directory,
and will append 500 records to it. It also opens trylock.dat.lock, but
never writes to it. The code as I am sending it does not use the lock file
(it locks the file being written to), but changing the second fd parameter
in writeline() call to fdl will have the program lock the lock file instead.
This did not help; in fact, the resulting data file had garbage in it (same
as if you had no locking at all).

The current program locks the entire file. We have tried variations
of just locking from the end-of-file the size of the message to be
written. This also failed the multi-node test. We would prefer to
just do record locking.

The program writes out what it believes to be the end-of-file position
and iteration number (loop position). If multiple processes are running,
no value for file_ptr for one process should EVER match one from the
other. This however does happen, causing one to overwrite the others data.

Sample Program usage:

trylock aaaaa > out1  (on machine 1)
trylock BBBBBBB > out2 (on machine 2)

Whatever you pass trylock, that is the buffer it appends to the trylock.dat
file. We have noticed that using different sized buffers for each
invocation makes errors in the file more obvious.

Thank you,
John E. Ramspott
Software Engineer
Sales Technologies, Inc.
jer@salestech.com
(404) 841-4986

Compile with: c89 -o trylock trylock.c -D_ALL_SOURCE
-------------------  trylock.c ---------------------------
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

short count;

short writeline( int fd, int fdl, char *message )
{
   int status;
   int msg_length;
   off_t file_ptr;
   struct flock alock;

   msg_length = strlen( message );

   /* Note that this locks the entire file. We initially were using */
   /* whence = SEEK_END, start = 0, and len = msg_length, to only */
   /* lock the record we want to write to. This seems to produce the */
   /* same affect as locking the whole file -- overlapping records from */
   /* different nodes. We would prefer to just lock the area we are adding */
   /* to the end of file. */
   alock.l_type = F_WRLCK;
   alock.l_whence = SEEK_SET;
   alock.l_start = 0;
   alock.l_len = 0;
   status = fcntl( fdl, F_SETLKW, &alock );
   if (status == -1)
      return( -3 );

   file_ptr = lseek( fd, 0, SEEK_END );
   printf( "file_ptr = %ld  iteration = %d\n", file_ptr, count );

   status = write( fd, message, msg_length );
   if (status == -1)
      return( -4 );

   alock.l_type = F_UNLCK;
   alock.l_whence = SEEK_SET;
   alock.l_start = 0;
   alock.l_len = 0;
   status = fcntl( fdl, F_SETLKW, &alock );
   if (status == -1)
      return( -1 );

   return( 0 );
}


main( int argc, char *argv[] )
{
   int fd, fdl;
   char writeBuff[ BUFSIZ ];
   short status;

   strcpy( writeBuff, argv[1] );
   strcat( writeBuff, "\n" );

   /* We have experimented with not using O_APPEND and using */
   /* lseek(), and doing both. We have also experimented with */
   /* the O_SYNC flag, with no noticeable affect */
   fd = open( "trylock.dat", O_CREAT | O_RDWR | O_APPEND,
              S_IRUSR | S_IWUSR );

   fdl = open( "trylock.dat.lock", O_CREAT | O_RDWR,
              S_IRUSR | S_IWUSR );

   for (count = 0; count < 500; count++ )
   {
      /* The second fd parameter below is the fd to use for file */
      /* locking. Using fdl for the second parameter will cause it */
      /* to use a separate lock file instead of the one we are writing */
      /* to. Using a separate file for locking actually made the resulting */
      /* data contain more garbage and errors for us */
      status = writeline( fd, fd, writeBuff );
      if (status != 0)
         printf( "status = %d  writeBuff = %s", status, writeBuff );
/*      usleep( 400000 );  Sleeping increases interleaving, but does */
/* not solve the problem of overlapping records. */
   }

   close( fd );
   close ( fdl );
}
------------------- end of trylock.c ------------------------

-- 
John E. Ramspott
Amiga Fanatic at Large
..!uupsi!stiatl!jer   (uupsi is a direct internet connection)
Newsgroups: comp.unix.aix