478 lines
12 KiB
C
478 lines
12 KiB
C
|
/* lock.c
|
|||
|
Lock and unlock a file name.
|
|||
|
|
|||
|
Copyright (C) 1991, 1992 Ian Lance Taylor
|
|||
|
|
|||
|
This file is part of the Taylor UUCP package.
|
|||
|
|
|||
|
This program is free software; you can redistribute it and/or
|
|||
|
modify it under the terms of the GNU General Public License as
|
|||
|
published by the Free Software Foundation; either version 2 of the
|
|||
|
License, or (at your option) any later version.
|
|||
|
|
|||
|
This program is distributed in the hope that it will be useful, but
|
|||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
|
General Public License for more details.
|
|||
|
|
|||
|
You should have received a copy of the GNU General Public License
|
|||
|
along with this program; if not, write to the Free Software
|
|||
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|||
|
|
|||
|
The author of the program may be contacted at ian@airs.com or
|
|||
|
c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254.
|
|||
|
*/
|
|||
|
|
|||
|
#include "uucp.h"
|
|||
|
|
|||
|
#if USE_RCS_ID
|
|||
|
const char lock_rcsid[] = "$Id: lock.c,v 1.1 1993/08/04 19:32:33 jtc Exp $";
|
|||
|
#endif
|
|||
|
|
|||
|
#include "uudefs.h"
|
|||
|
#include "sysdep.h"
|
|||
|
#include "system.h"
|
|||
|
|
|||
|
#include <errno.h>
|
|||
|
|
|||
|
#if HAVE_FCNTL_H
|
|||
|
#include <fcntl.h>
|
|||
|
#else
|
|||
|
#if HAVE_SYS_FILE_H
|
|||
|
#include <sys/file.h>
|
|||
|
#endif
|
|||
|
#endif
|
|||
|
|
|||
|
#ifndef O_RDONLY
|
|||
|
#define O_RDONLY 0
|
|||
|
#define O_WRONLY 1
|
|||
|
#define O_RDWR 2
|
|||
|
#endif
|
|||
|
|
|||
|
#ifndef O_NOCTTY
|
|||
|
#define O_NOCTTY 0
|
|||
|
#endif
|
|||
|
|
|||
|
#ifndef SEEK_SET
|
|||
|
#define SEEK_SET 0
|
|||
|
#endif
|
|||
|
|
|||
|
/* Lock something. If the fspooldir argument is TRUE, the argument is
|
|||
|
a file name relative to the spool directory; otherwise the argument
|
|||
|
is a simple file name which should be created in the system lock
|
|||
|
directory (under HDB this is /etc/locks). */
|
|||
|
|
|||
|
boolean
|
|||
|
fsdo_lock (zlock, fspooldir, pferr)
|
|||
|
const char *zlock;
|
|||
|
boolean fspooldir;
|
|||
|
boolean *pferr;
|
|||
|
{
|
|||
|
char *zfree;
|
|||
|
const char *zpath, *zslash;
|
|||
|
size_t cslash;
|
|||
|
pid_t ime;
|
|||
|
char *ztempfile;
|
|||
|
char abtempfile[sizeof "TMP1234567890"];
|
|||
|
int o;
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
int i;
|
|||
|
#else
|
|||
|
char ab[12];
|
|||
|
#endif
|
|||
|
int cwrote;
|
|||
|
const char *zerr;
|
|||
|
boolean fret;
|
|||
|
|
|||
|
if (pferr != NULL)
|
|||
|
*pferr = TRUE;
|
|||
|
|
|||
|
if (fspooldir)
|
|||
|
{
|
|||
|
zfree = NULL;
|
|||
|
zpath = zlock;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
zfree = zsysdep_in_dir (zSlockdir, zlock);
|
|||
|
zpath = zfree;
|
|||
|
}
|
|||
|
|
|||
|
ime = getpid ();
|
|||
|
|
|||
|
/* We do the actual lock by creating a file and then linking it to
|
|||
|
the final file name we want. This avoids race conditions due to
|
|||
|
one process checking the file before we have finished writing it,
|
|||
|
and also works even if we are somehow running as root.
|
|||
|
|
|||
|
First, create the file in the right directory (we must create the
|
|||
|
file in the same directory since otherwise we might attempt a
|
|||
|
cross-device link). */
|
|||
|
zslash = strrchr (zpath, '/');
|
|||
|
if (zslash == NULL)
|
|||
|
cslash = 0;
|
|||
|
else
|
|||
|
cslash = zslash - zpath + 1;
|
|||
|
|
|||
|
sprintf (abtempfile, "TMP%010lx", (unsigned long) ime);
|
|||
|
ztempfile = zbufalc (cslash + sizeof abtempfile);
|
|||
|
memcpy (ztempfile, zpath, cslash);
|
|||
|
memcpy (ztempfile + cslash, abtempfile, sizeof abtempfile);
|
|||
|
|
|||
|
o = creat (ztempfile, IPUBLIC_FILE_MODE);
|
|||
|
if (o < 0)
|
|||
|
{
|
|||
|
if (errno == ENOENT)
|
|||
|
{
|
|||
|
if (! fsysdep_make_dirs (ztempfile, FALSE))
|
|||
|
{
|
|||
|
ubuffree (zfree);
|
|||
|
ubuffree (ztempfile);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
o = creat (ztempfile, IPUBLIC_FILE_MODE);
|
|||
|
}
|
|||
|
if (o < 0)
|
|||
|
{
|
|||
|
ulog (LOG_ERROR, "creat (%s): %s", ztempfile, strerror (errno));
|
|||
|
ubuffree (zfree);
|
|||
|
ubuffree (ztempfile);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
i = ime;
|
|||
|
cwrote = write (o, &i, sizeof i);
|
|||
|
#else
|
|||
|
sprintf (ab, "%10d\n", (int) ime);
|
|||
|
cwrote = write (o, ab, strlen (ab));
|
|||
|
#endif
|
|||
|
|
|||
|
zerr = NULL;
|
|||
|
if (cwrote < 0)
|
|||
|
zerr = "write";
|
|||
|
if (close (o) < 0)
|
|||
|
zerr = "close";
|
|||
|
if (zerr != NULL)
|
|||
|
{
|
|||
|
ulog (LOG_ERROR, "%s (%s): %s", zerr, ztempfile, strerror (errno));
|
|||
|
(void) remove (ztempfile);
|
|||
|
ubuffree (zfree);
|
|||
|
ubuffree (ztempfile);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
/* Now try to link the file we just created to the lock file that we
|
|||
|
want. If it fails, try reading the existing file to make sure
|
|||
|
the process that created it still exists. We do this in a loop
|
|||
|
to make it easy to retry if the old locking process no longer
|
|||
|
exists. */
|
|||
|
fret = TRUE;
|
|||
|
if (pferr != NULL)
|
|||
|
*pferr = FALSE;
|
|||
|
o = -1;
|
|||
|
zerr = NULL;
|
|||
|
|
|||
|
while (link (ztempfile, zpath) != 0)
|
|||
|
{
|
|||
|
int cgot;
|
|||
|
int ipid;
|
|||
|
boolean freadonly;
|
|||
|
|
|||
|
fret = FALSE;
|
|||
|
|
|||
|
if (errno != EEXIST)
|
|||
|
{
|
|||
|
ulog (LOG_ERROR, "link (%s, %s): %s", ztempfile, zpath,
|
|||
|
strerror (errno));
|
|||
|
if (pferr != NULL)
|
|||
|
*pferr = TRUE;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
freadonly = FALSE;
|
|||
|
o = open ((char *) zpath, O_RDWR | O_NOCTTY, 0);
|
|||
|
if (o < 0)
|
|||
|
{
|
|||
|
if (errno == EACCES)
|
|||
|
{
|
|||
|
freadonly = TRUE;
|
|||
|
o = open ((char *) zpath, O_RDONLY, 0);
|
|||
|
}
|
|||
|
if (o < 0)
|
|||
|
{
|
|||
|
if (errno == ENOENT)
|
|||
|
{
|
|||
|
/* The file was presumably removed between the link
|
|||
|
and the open. Try the link again. */
|
|||
|
fret = TRUE;
|
|||
|
continue;
|
|||
|
}
|
|||
|
zerr = "open";
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* The race starts here. See below for a discussion. */
|
|||
|
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
cgot = read (o, &i, sizeof i);
|
|||
|
#else
|
|||
|
cgot = read (o, ab, sizeof ab - 1);
|
|||
|
#endif
|
|||
|
|
|||
|
if (cgot < 0)
|
|||
|
{
|
|||
|
zerr = "read";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
ipid = i;
|
|||
|
#else
|
|||
|
ab[cgot] = '\0';
|
|||
|
ipid = strtol (ab, (char **) NULL, 10);
|
|||
|
#endif
|
|||
|
|
|||
|
/* On NFS, the link might have actually succeeded even though we
|
|||
|
got a failure return. This can happen if the original
|
|||
|
acknowledgement was lost or delayed and the operation was
|
|||
|
retried. In this case the pid will be our own. This
|
|||
|
introduces a rather improbable race condition: if a stale
|
|||
|
lock was left with our process ID in it, and another process
|
|||
|
just did the kill, below, but has not yet changed the lock
|
|||
|
file to hold its own process ID, we could start up and make
|
|||
|
it all the way to here and think we have the lock. I'm not
|
|||
|
going to worry about this possibility. */
|
|||
|
if (ipid == ime)
|
|||
|
{
|
|||
|
fret = TRUE;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
/* If the process still exists, we will get EPERM rather than
|
|||
|
ESRCH. We then return FALSE to indicate that we cannot make
|
|||
|
the lock. */
|
|||
|
if (kill (ipid, 0) == 0 || errno == EPERM)
|
|||
|
break;
|
|||
|
|
|||
|
ulog (LOG_ERROR, "Found stale lock %s held by process %d",
|
|||
|
zpath, ipid);
|
|||
|
|
|||
|
/* This is a stale lock, created by a process that no longer
|
|||
|
exists.
|
|||
|
|
|||
|
Now we could remove the file (and, if the file mode disallows
|
|||
|
writing, that's what we have to do), but we try to avoid
|
|||
|
doing so since it causes a race condition. If we remove the
|
|||
|
file, and are interrupted any time after we do the read until
|
|||
|
we do the remove, another process could get in, open the
|
|||
|
file, find that it was a stale lock, remove the file and
|
|||
|
create a new one. When we regained control we would remove
|
|||
|
the file the other process just created.
|
|||
|
|
|||
|
These files are being generated partially for the benefit of
|
|||
|
cu, and it would be nice to avoid the race however cu avoids
|
|||
|
it, so that the programs remain compatible. Unfortunately,
|
|||
|
nobody seems to know how cu avoids the race, or even if it
|
|||
|
tries to avoid it at all.
|
|||
|
|
|||
|
There are a few ways to avoid the race. We could use kernel
|
|||
|
locking primitives, but they may not be available. We could
|
|||
|
link to a special file name, but if that file were left lying
|
|||
|
around then no stale lock could ever be broken (Henry Spencer
|
|||
|
would think this was a good thing).
|
|||
|
|
|||
|
Instead I've implemented the following procedure: seek to the
|
|||
|
start of the file, write our pid into it, sleep for five
|
|||
|
seconds, and then make sure our pid is still there. Anybody
|
|||
|
who checks the file while we're asleep will find our pid
|
|||
|
there and fail the lock. The only race will come from
|
|||
|
another process which has done the read by the time we do our
|
|||
|
write. That process will then have five seconds to do its
|
|||
|
own write. When we wake up, we'll notice that our pid is no
|
|||
|
longer in the file, and retry the lock from the beginning.
|
|||
|
|
|||
|
This relies on the atomicity of write(2). If it possible for
|
|||
|
the writes of two processes to be interleaved, the two
|
|||
|
processes could livelock. POSIX unfortunately leaves this
|
|||
|
case explicitly undefined; however, given that the write is
|
|||
|
of less than a disk block, it's difficult to imagine an
|
|||
|
interleave occurring.
|
|||
|
|
|||
|
Note that this is still a race. If it takes the second
|
|||
|
process more than five seconds to do the kill, the lseek, and
|
|||
|
the write, both processes will think they have the lock.
|
|||
|
Perhaps the length of time to sleep should be configurable.
|
|||
|
Even better, perhaps I should add a configuration option to
|
|||
|
use a permanent lock file, which eliminates any race and
|
|||
|
forces the installer to be aware of the existence of the
|
|||
|
permanent lock file.
|
|||
|
|
|||
|
We stat the file after the sleep, to make sure some other
|
|||
|
program hasn't deleted it for us. */
|
|||
|
if (freadonly)
|
|||
|
{
|
|||
|
(void) close (o);
|
|||
|
o = -1;
|
|||
|
(void) remove (zpath);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (lseek (o, (off_t) 0, SEEK_SET) != 0)
|
|||
|
{
|
|||
|
zerr = "lseek";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
i = ime;
|
|||
|
cwrote = write (o, &i, sizeof i);
|
|||
|
#else
|
|||
|
sprintf (ab, "%10d\n", (int) ime);
|
|||
|
cwrote = write (o, ab, strlen (ab));
|
|||
|
#endif
|
|||
|
|
|||
|
if (cwrote < 0)
|
|||
|
{
|
|||
|
zerr = "write";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
(void) sleep (5);
|
|||
|
|
|||
|
if (lseek (o, (off_t) 0, SEEK_SET) != 0)
|
|||
|
{
|
|||
|
zerr = "lseek";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
cgot = read (o, &i, sizeof i);
|
|||
|
#else
|
|||
|
cgot = read (o, ab, sizeof ab - 1);
|
|||
|
#endif
|
|||
|
|
|||
|
if (cgot < 0)
|
|||
|
{
|
|||
|
zerr = "read";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
#if HAVE_V2_LOCKFILES
|
|||
|
ipid = i;
|
|||
|
#else
|
|||
|
ab[cgot] = '\0';
|
|||
|
ipid = strtol (ab, (char **) NULL, 10);
|
|||
|
#endif
|
|||
|
|
|||
|
if (ipid == ime)
|
|||
|
{
|
|||
|
struct stat sfile, sdescriptor;
|
|||
|
|
|||
|
/* It looks like we have the lock. Do the final stat
|
|||
|
check. */
|
|||
|
if (stat ((char *) zpath, &sfile) < 0)
|
|||
|
{
|
|||
|
if (errno != ENOENT)
|
|||
|
{
|
|||
|
zerr = "stat";
|
|||
|
break;
|
|||
|
}
|
|||
|
/* Loop around and try again. */
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (fstat (o, &sdescriptor) < 0)
|
|||
|
{
|
|||
|
zerr = "fstat";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (sfile.st_ino == sdescriptor.st_ino
|
|||
|
&& sfile.st_dev == sdescriptor.st_dev)
|
|||
|
{
|
|||
|
/* Close the file before assuming we've succeeded to
|
|||
|
pick up any trailing errors. */
|
|||
|
if (close (o) < 0)
|
|||
|
{
|
|||
|
zerr = "close";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
o = -1;
|
|||
|
|
|||
|
/* We have the lock. */
|
|||
|
fret = TRUE;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Loop around and try the lock again. We keep doing this until
|
|||
|
the lock file holds a pid that exists. */
|
|||
|
(void) close (o);
|
|||
|
o = -1;
|
|||
|
fret = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
if (zerr != NULL)
|
|||
|
{
|
|||
|
ulog (LOG_ERROR, "%s (%s): %s", zerr, zpath, strerror (errno));
|
|||
|
if (pferr != NULL)
|
|||
|
*pferr = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
if (o >= 0)
|
|||
|
(void) close (o);
|
|||
|
|
|||
|
ubuffree (zfree);
|
|||
|
|
|||
|
/* It would be nice if we could leave the temporary file around for
|
|||
|
future calls, but considering that we create lock files in
|
|||
|
various different directories it's probably more trouble than
|
|||
|
it's worth. */
|
|||
|
if (remove (ztempfile) != 0)
|
|||
|
ulog (LOG_ERROR, "remove (%s): %s", ztempfile, strerror (errno));
|
|||
|
|
|||
|
ubuffree (ztempfile);
|
|||
|
|
|||
|
return fret;
|
|||
|
}
|
|||
|
|
|||
|
/* Unlock something. The fspooldir argument is as in fsdo_lock. */
|
|||
|
|
|||
|
boolean
|
|||
|
fsdo_unlock (zlock, fspooldir)
|
|||
|
const char *zlock;
|
|||
|
boolean fspooldir;
|
|||
|
{
|
|||
|
char *zfree;
|
|||
|
const char *zpath;
|
|||
|
|
|||
|
if (fspooldir)
|
|||
|
{
|
|||
|
zfree = NULL;
|
|||
|
zpath = zlock;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
zfree = zsysdep_in_dir (zSlockdir, zlock);
|
|||
|
zpath = zfree;
|
|||
|
}
|
|||
|
|
|||
|
if (remove (zpath) == 0
|
|||
|
|| errno == ENOENT)
|
|||
|
{
|
|||
|
ubuffree (zfree);
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ulog (LOG_ERROR, "remove (%s): %s", zpath, strerror (errno));
|
|||
|
ubuffree (zfree);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
}
|