File locking prevents multiple processes in UNIX from
accessing all 
or part of a file at the same time. For example, file
locking can 
prevent two processes from editing a file simultaneously.
Some editors, 
such as emacs, support file locking during editing;
other, such as 
vi, do not. File locking is not an issue limited to
file editors, 
however. Without file locking, in UNIX there is always
the risk of 
collision among processes trying to use the same file.
UNIX provides 
several ways to manage file locking:
Setting semaphores.
Unfortunately, most of these solutions assume that the
lock is valid 
only for the duration of the initiating process, and/or
only for processes 
on the same host machine. Services like NFS create a
situation in 
which processes on multiple hosts could attempt to lock
the same file. 
Although NFS has been extended to allow file locking
(via fcntl), 
this feature may not be universally implemented.
Another drawback to these solutions is that they are
generally not 
very "shell friendly." Shell scripts involve
the execution 
of several processes, but kernel-level file-locking
mechanisms cease 
to be valid when the initiating process goes away. What's
needed is 
a locking entity that is "shell friendly"
and that works across 
hosts within a network.
Modern UNIX File Locking Using fcntl
The fcntl call, upon which lockf is typically built,
supports the concept of file locking. In fact, fcntl
supports 
locking portions of a file in addition to the whole
file. Usually, 
calls are made to the higher level lockf function rathr
than 
to fcntl. Here is the prototype declaration for lockf:
int lockf(int filedes, int function, long size);
The first argument to lockf is a file descriptor 
to a UNIX file opened for write (O_WRONLY) or read/write
(O_RDWR). 
The second argument describes the function to perform:
#define F_ULOCK   0   /* unlocks previously locked areas */
#define F_LOCK    1   /* lock area, wait on exiting lock if necessary */
#define F_TLOCK   2   /* lock area, fail if area already locked */
#define F_TEST    3   /* test area for any existing locks */
The third argument specifies the number of bytes from
the current file offset that are to be locked. This
means that you 
can lock file areas in addition to the entire file.
As mentioned earlier, 
virtually all NFS implementations support file locking
using fcntl. 
Therefore, the fcntl-based lockf should work across
networked machines, provided that the file being locked
is from an 
NFS exported filesystem. Again, however, this mechanism
may have limited 
success within a shell script environment, since these
file locks 
are valid only while the initiating process exists.
fcntl provides for two kinds of file locking: advisory
and 
mandatory. Advisory locks place the burden of determining
the status 
of the lock on the process. In other words, the program
must determine 
if a lock exists for an area before accessing the area.
A mandatory 
lock forces IO operations to fail and therefore is typically
maintained 
within the kernel. Advisory locks are more universally
available than 
mandatory locks; the SGID (Set Group Id, 02000) bit
must be active 
on the file being accessed to enable mandatory locking.
However, in 
this implementation the group execution bit must be
turned off in 
order to distinguish the mandatory locking flag from
a SGID executable 
(which means something totally different). Therefore,
it is not possible 
to have mandatory locks with a SGID executable. One
way of turning 
the SGID bit on is to issue the following:
$ chmod +l filename
$ ls -l
-rw-rwlr--   1 user      user     1765 May 17 08:45 filename
All locks on filename will now be considered 
mandatory instead of advisory.
Older Techniques
Before fcntl, file locking had to be done some other
way. 
The simplest mechanism was to use the process id of
the locking process 
as the key for the lock. AT&T's Source Code Control
System (SCCS) 
uses this style. Oddly, this technique is still being
used within 
more modern program packages, such as emacs and the
Internet 
Network News package.
The SCCS Method in Detail
AT&T's Source Code Control System (SCCS) uses a
simple file-locking 
technique to prevent collision on access to its configuration
files 
(s-files). It places the process id of the first process
to 
access an s-file into what it calls the z-file (a z-file
is simply the name of the configured file with a "z."
prefix). 
Any subsequent process attempting to access the same
s-file 
will check first for the existence of a z-file. If a
z-file 
is found and contains a process id for an existing process,
then the 
lock is enforced and access is denied. However, if no
z-file 
exists, or the existing z-file refers to a non-active
process 
id, then the z-file is replaced with the process id
of the 
current process requesting the lock. Process ids are
not unique across 
a network, so this simple technique is not sufficient
on its own. 
However, I'll use a variation of this advisory locking
technique in 
the solution presented here.
emacs and shlock
Some current programs handle file locking using a variant
of the SCCS 
technique, among them emacs and standalone program called
shlock, which is available in the Internet Network News
(inn) 
package. The emacs locking function is in the filelock.c.
It uses a common lock directory to store all of its
locking files. 
The lock file names in this directory are either based
upon the full 
pathname of the file being edited with "!"
replacing "/" 
in the pathname or they are computed using a checksum
of the pathname 
of the file (the latter technique is used on hosts where
long filenames 
are not supported, e.g., the old sysV filesystem). Again,
as with 
SCCS, the process id of the locking emacs process is
placed 
in the lock file and this determines the lock file's
validity. The 
other program, shlock, is closer to the goal I want
in that 
it is designed to be callable by shell scripts. The
shlock 
program is very flexible; it allows the caller to choose
the process 
id and the full pathname of the lock file. However,
in both cases 
(emacs and shlock), the locking mechanism assumes 
that the process ids and lock files are on a single
host.
Presenting rlock
The solution I present here is similar to shlock but
was developed 
without knowledge of the shlock program. It uses the
SCCS 
technique of file locking, but takes the idea one step
further by 
putting hostname information within the lock file to
identify where 
the locking process resides. The algorithm is essentially
the same 
except where a lock file exists and the hostname in
the file does 
not match the hostname of the process accessing the
lock file. In 
this case, a remote shell is invoked on the locking
host, which in 
turn attempts to determine the existence of the process
id. This solution 
assumes that trusted user access is enabled on the network
of locking 
hosts. Since this may not be desirable under your existing
security 
policy, you might consider other ways of implementing
this service 
(see the sidebar, "Without Trusted User Access").
The source 
for rlock is in Listing 1.
Unlike emacs and/or shlock, rlock does not 
handle explicit lock file pathnames and does not use
a lock file directory. 
I did, however, add the "-z" option, which
allows rlock 
to output in a format which is compatible with SCCS.
This makes it 
easy to create z-files (using -pz.) for SCCS s-files.
I have found this useful when making global edits to
s-files 
using non-SCCS routines. By default, rlock places a
lock file 
in the same directory as the file mentioned as a parameter
and 
prepends a "Z." to the front of the file name.
The logic is 
that a "z." file is an SCCS lock file, and
a "Z." 
file is a network-wide lock file.
A major shortcoming of rlock is that it assumes that
a user has write access to the directory where the file
being locked 
resides. shlock gets around this problem by allowing
you to 
specify where the lock file resides. A single publicly
writable directory 
could be created for shlock lock files. Likewise, emacs
handles the problem by using lock files within a predefined
lock directory. 
Either technique could easily be added to the rlock
program
Applying rlock
In Listing 2, I present a very primitive script that
calls vi. 
The script assumes that only file names are given on
the command line, 
and it locks each file using rlock. This is not meant
to be 
a robust example, merely a suggestion of possible use.
I have also 
successfully used rlock as part of scripts wrapped around
SCCS commands. Having the lock file allowed me to use
several SCCS 
commands yet treat them as a single atomic operation.
My experience 
suggests that the need to treat several file operations
from 
within a shell script as an atomic operation is more
common than most 
people think. System administrators usually accept the
risk; with 
rlock, you don't have to worry about the risk.
Locking Things Down
I hope that you find rlock useful. If you use shell
scripts 
and need a file-locking mechanism that works well with
distributed 
file systems, rlock may meet your needs. If not, you
may want 
to modify shlock or borrow code from emacs. 
About the Author
Chris Cox is a Computer Science graduate of Texas A&M
University 
with an Electrical Engineering minor. He has been working
with UNIX 
since System III, and over the past ten years has been
a software 
developer, configuration manager, porting engineer,
and system administrator 
for heterogeneous UNIX environments. Chris is currently
employed as a
consultant for ARMS, Inc.