691 lines
17 KiB
C
691 lines
17 KiB
C
/* lock.c
|
||
Lock and unlock a file name.
|
||
|
||
Copyright (C) 1991, 1992, 1993, 1995 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||
|
||
The author of the program may be contacted at ian@airs.com or
|
||
c/o Cygnus Support, 48 Grove Street, Somerville, MA 02144.
|
||
*/
|
||
|
||
#include "uucp.h"
|
||
|
||
#if USE_RCS_ID
|
||
const char lock_rcsid[] = "$FreeBSD$";
|
||
#endif
|
||
|
||
#include "uudefs.h"
|
||
#include "sysdep.h"
|
||
#include "system.h"
|
||
|
||
#include <errno.h>
|
||
#include <ctype.h>
|
||
|
||
#if HAVE_FCNTL_H
|
||
#include <fcntl.h>
|
||
#else
|
||
#if HAVE_SYS_FILE_H
|
||
#include <sys/file.h>
|
||
#endif
|
||
#endif
|
||
|
||
#if TM_IN_SYS_TIME
|
||
#include <sys/time.h>
|
||
#else
|
||
#include <time.h>
|
||
#endif
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
#include <sys/kernel.h>
|
||
#include <sys/psinfo.h>
|
||
#include <sys/seginfo.h>
|
||
#include <sys/vc.h>
|
||
#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
|
||
|
||
#ifndef localtime
|
||
extern struct tm *localtime ();
|
||
#endif
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
static boolean fsqnx_stale P((unsigned long ipid, unsigned long inme,
|
||
unsigned long inid, boolean *pferr));
|
||
#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 "TMP12345678901234567890"];
|
||
int o;
|
||
#if HAVE_QNX_LOCKFILES
|
||
nid_t inme;
|
||
char ab[23];
|
||
char *zend;
|
||
#else
|
||
#if HAVE_V2_LOCKFILES
|
||
int i;
|
||
#else
|
||
char ab[12];
|
||
#endif
|
||
#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 ();
|
||
#if HAVE_QNX_LOCKFILES
|
||
inme = getnid ();
|
||
#endif
|
||
|
||
/* 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;
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
sprintf (abtempfile, "TMP%010lx%010lx", (unsigned long) ime,
|
||
(unsigned long) inme);
|
||
#else
|
||
sprintf (abtempfile, "TMP%010lx", (unsigned long) ime);
|
||
#endif
|
||
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_QNX_LOCKFILES
|
||
sprintf (ab, "%10ld %10ld\n", (long) ime, (long) inme);
|
||
cwrote = write (o, ab, strlen (ab));
|
||
#else
|
||
#if HAVE_V2_LOCKFILES
|
||
i = (int) ime;
|
||
cwrote = write (o, &i, sizeof i);
|
||
#else
|
||
sprintf (ab, "%10ld\n", (long) ime);
|
||
cwrote = write (o, ab, strlen (ab));
|
||
#endif
|
||
#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;
|
||
pid_t ipid;
|
||
boolean freadonly;
|
||
struct stat st;
|
||
char abtime[sizeof "1991-12-31 12:00:00"];
|
||
#if HAVE_QNX_LOCKFILES
|
||
nid_t inid;
|
||
#endif
|
||
|
||
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 DEBUG > 0
|
||
#if HAVE_V2_LOCKFILES
|
||
{
|
||
char ab[10];
|
||
|
||
if (read (o, ab, sizeof ab) > 4
|
||
&& isdigit (BUCHAR (ab[0])))
|
||
ulog (LOG_ERROR,
|
||
"Lock file %s may be HDB format; check LOCKFILES in policy.h",
|
||
zpath);
|
||
}
|
||
#else
|
||
if (cgot == 4)
|
||
ulog (LOG_ERROR,
|
||
"Lock file %s may be V2 format; check LOCKFILES in policy.h",
|
||
zpath);
|
||
#endif
|
||
#endif /* DEBUG > 0 */
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
ab[cgot] = '\0';
|
||
ipid = (pid_t) strtol (ab, &zend, 10);
|
||
inid = (nid_t) strtol (zend, (char **) NULL, 10);
|
||
#else
|
||
#if HAVE_V2_LOCKFILES
|
||
ipid = (pid_t) i;
|
||
#else
|
||
ab[cgot] = '\0';
|
||
ipid = (pid_t) strtol (ab, (char **) NULL, 10);
|
||
#endif
|
||
#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)
|
||
{
|
||
#if HAVE_QNX_LOCKFILES
|
||
if (inid == inme)
|
||
#endif
|
||
{
|
||
fret = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* If the lock file is empty (cgot == 0), we assume that it is
|
||
stale. This can happen if the system crashed after the lock
|
||
file was created but before the process ID was written out. */
|
||
if (cgot > 0)
|
||
{
|
||
#if HAVE_QNX_LOCKFILES
|
||
if (! fsqnx_stale ((unsigned long) ipid, (unsigned long) inme,
|
||
(unsigned long) inid, pferr))
|
||
break;
|
||
#else
|
||
/* 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;
|
||
#endif
|
||
}
|
||
|
||
if (fstat (o, &st) < 0)
|
||
strcpy (abtime, "unknown");
|
||
else
|
||
{
|
||
time_t itm;
|
||
struct tm *q;
|
||
|
||
itm = (time_t) st.st_mtime;
|
||
q = localtime (&itm);
|
||
sprintf (abtime, "%04d-%02d-%02d %02d:%02d:%02d",
|
||
q->tm_year + 1900, q->tm_mon + 1, q->tm_mday, q->tm_hour,
|
||
q->tm_min, q->tm_sec);
|
||
}
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
ulog (LOG_ERROR,
|
||
"Stale lock %s held by process %ld on node %ld created %s",
|
||
zpath, (long) ipid, (long) inid, abtime);
|
||
#else
|
||
ulog (LOG_ERROR, "Stale lock %s held by process %ld created %s",
|
||
zpath, (long) ipid, abtime);
|
||
#endif
|
||
|
||
/* 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);
|
||
fret = TRUE;
|
||
continue;
|
||
}
|
||
|
||
if (lseek (o, (off_t) 0, SEEK_SET) != 0)
|
||
{
|
||
zerr = "lseek";
|
||
break;
|
||
}
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
sprintf (ab, "%10ld %10ld\n", (long) ime, (long) inme);
|
||
cwrote = write (o, ab, strlen (ab));
|
||
#else
|
||
#if HAVE_V2_LOCKFILES
|
||
i = (int) ime;
|
||
cwrote = write (o, &i, sizeof i);
|
||
#else
|
||
sprintf (ab, "%10ld\n", (long) ime);
|
||
cwrote = write (o, ab, strlen (ab));
|
||
#endif
|
||
#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_QNX_LOCKFILES
|
||
ab[cgot] = '\0';
|
||
ipid = (pid_t) strtol (ab, &zend, 10);
|
||
inid = (nid_t) strtol (zend, (char **) NULL, 10);
|
||
#else
|
||
#if HAVE_V2_LOCKFILES
|
||
ipid = (pid_t) i;
|
||
#else
|
||
ab[cgot] = '\0';
|
||
ipid = (pid_t) strtol (ab, (char **) NULL, 10);
|
||
#endif
|
||
#endif
|
||
|
||
if (ipid == ime)
|
||
{
|
||
#if HAVE_QNX_LOCKFILES
|
||
if (inid == inme)
|
||
#endif
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
#if HAVE_QNX_LOCKFILES
|
||
|
||
/* Return TRUE if the lock is stale. */
|
||
|
||
static boolean
|
||
fsqnx_stale (ipid, inme, inid, pferr)
|
||
unsigned long ipid;
|
||
unsigned long inme;
|
||
unsigned long inid;
|
||
boolean *pferr;
|
||
{
|
||
/* A virtual process ID. This virtual process ID, which will exist
|
||
on the local node, will represent the process ID of the process
|
||
manager process (Proc) on the remote node. */
|
||
pid_t ivid;
|
||
/* The return value of the qnx_psinfo function. This is either a
|
||
process ID which might or might not be the same as the process
|
||
being looked for, or -1 to indicate no process found. */
|
||
pid_t ifound_pid;
|
||
/* This holds the actual result of qnx_psinfo. We will ignore
|
||
almost all the fields since we're just checking for existence. */
|
||
struct _psinfo spsdata;
|
||
|
||
/* Establish connection with a remote process manager if necessary. */
|
||
if (inid != inme)
|
||
{
|
||
ivid = qnx_vc_attach (inid /* remote node ID */,
|
||
PROC_PID /* pid of process manager */,
|
||
1000 /* initial buffer size */,
|
||
0 /* flags */);
|
||
if (ivid < 0)
|
||
{
|
||
ulog (LOG_ERROR, "qnx_vc_attach (%lu, PROC_PID): %s",
|
||
inid, strerror (errno));
|
||
if (pferr != NULL)
|
||
*pferr = TRUE;
|
||
return FALSE;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Use the local pid of the local process manager. */
|
||
ivid = PROC_PID;
|
||
}
|
||
|
||
/* Request the process information. */
|
||
ifound_pid = qnx_psinfo (ivid /* process manager handling request */,
|
||
ipid /* get info on this process */,
|
||
&spsdata /* put info in this struct */,
|
||
0 /* unused */,
|
||
(struct _seginfo *) NULL /* unused */);
|
||
|
||
/* Deallocate the virtual connection before continuing. */
|
||
{
|
||
int isaved_errno = errno;
|
||
if (qnx_vc_detach (ivid) < 0)
|
||
ulog (LOG_ERROR, "qnx_vd_detach (%ld): %s", (long) ivid,
|
||
strerror (errno));
|
||
errno = isaved_errno;
|
||
}
|
||
|
||
/* If the returned pid matches then the process still holds the lock. */
|
||
if ((ifound_pid == ipid) && (spsdata.pid == ipid))
|
||
return FALSE;
|
||
|
||
/* If the returned pid is positive and doesn't match, then the
|
||
process doesn't exist and the lock is stale. Continue. */
|
||
|
||
/* If the returned pid is negative (-1) and errno is EINVAL (or ESRCH
|
||
in older versions of QNX), then the process doesn't exist and the
|
||
lock is stale. Continue. */
|
||
|
||
/* Check for impossible errors. */
|
||
if ((ifound_pid < 0) && (errno != ESRCH) && (errno != EINVAL))
|
||
{
|
||
ulog (LOG_ERROR, "qnx_psinfo (%ld, %ld): %s", (long) ivid,
|
||
(long) ipid, strerror (errno));
|
||
/* Since we don't know what the hell this means, and we don't
|
||
want our system to freeze, we treat this case as a stale
|
||
lock. Continue on. */
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
#endif /* HAVE_QNX_LOCKFILES */
|