summaryrefslogtreecommitdiff
path: root/gnu/libexec/uucp/libunix/lock.c
diff options
context:
space:
mode:
authorTheo de Raadt <deraadt@cvs.openbsd.org>1995-10-18 08:53:40 +0000
committerTheo de Raadt <deraadt@cvs.openbsd.org>1995-10-18 08:53:40 +0000
commitd6583bb2a13f329cf0332ef2570eb8bb8fc0e39c (patch)
treeece253b876159b39c620e62b6c9b1174642e070e /gnu/libexec/uucp/libunix/lock.c
initial import of NetBSD tree
Diffstat (limited to 'gnu/libexec/uucp/libunix/lock.c')
-rw-r--r--gnu/libexec/uucp/libunix/lock.c690
1 files changed, 690 insertions, 0 deletions
diff --git a/gnu/libexec/uucp/libunix/lock.c b/gnu/libexec/uucp/libunix/lock.c
new file mode 100644
index 00000000000..30bed926aee
--- /dev/null
+++ b/gnu/libexec/uucp/libunix/lock.c
@@ -0,0 +1,690 @@
+/* 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[] = "$Id: lock.c,v 1.1 1995/10/18 08:38:29 deraadt Exp $";
+#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 */