]> diplodocus.org Git - nmh/blobdiff - sbr/lock_file.c
I had forgotten that nesec_set_tls() has to be called AFTER the
[nmh] / sbr / lock_file.c
index bbfc8fc7cf4249089f4c0ae4c44dcc89d9c9cdb1..a8a98118d1b1e5194f51cda4765c6ccdfca5538b 100644 (file)
 #include <h/mh.h>
 #include <h/signals.h>
 #include <h/utils.h>
+#include <h/mts.h>
 
 #ifdef HAVE_SYS_TIME_H
 # include <sys/time.h>
 #endif
 #include <time.h>
-#include <errno.h>
-
-#ifdef HAVE_FCNTL_H
-# include <fcntl.h>
-#else
+#include <fcntl.h>
+#ifdef HAVE_FLOCK
 # include <sys/file.h>
 #endif
 
-#if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING)
-# include <sys/file.h>
-#endif
-
-#include <signal.h>
-
 #if defined(HAVE_LIBLOCKFILE)
-#include <lockfile.h>
+# include <lockfile.h>
 #endif
 
 #ifdef LOCKDIR
 char *lockdir = LOCKDIR;
 #endif
 
-/* Are we using any kernel locking? */
-#if defined (FLOCK_LOCKING) || defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
-# define KERNEL_LOCKING
-#endif
-
-#ifdef DOT_LOCKING
-
 /* struct for getting name of lock file to create */
 struct lockinfo {
     char curlock[BUFSIZ];
@@ -60,6 +45,11 @@ struct lockinfo {
 #endif
 };
 
+/*
+ * Number of tries to retry locking
+ */
+#define LOCK_RETRIES 60
+
 /*
  * Amount of time to wait before
  * updating ctime of lock file.
@@ -81,20 +71,36 @@ struct lock {
     struct lock *l_next;
 };
 
-/* top of list containing all open locks */
-static struct lock *l_top = NULL;
-#endif /* DOT_LOCKING */
+enum locktype { FCNTL_LOCKING, FLOCK_LOCKING, LOCKF_LOCKING, DOT_LOCKING };
 
 /*
- * static prototypes
+ * Flags to indicate whether we've initialized the lock types, and
+ * our saved lock types
  */
-#ifdef KERNEL_LOCKING
-static int lkopen_kernel (char *, int, mode_t);
-#endif
+static int datalockinit = 0;
+static int spoollockinit = 0;
+static enum locktype datalocktype, spoollocktype;
+
+
+/* top of list containing all open locks */
+static struct lock *l_top = NULL;
 
-#ifdef DOT_LOCKING
-static int lkopen_dot (char *, int, mode_t);
-static void lockname (char *, struct lockinfo *, int);
+static int lkopen(const char *, int, mode_t, enum locktype, int *);
+static int str2accbits(const char *);
+
+static int lkopen_fcntl (const char *, int, mode_t, int *);
+#ifdef HAVE_LOCKF
+static int lkopen_lockf (const char *, int, mode_t, int *);
+#endif /* HAVE_LOCKF */
+#ifdef HAVE_FLOCK
+static int lkopen_flock (const char *, int, mode_t, int *);
+#endif /* HAVE_FLOCK */
+
+static enum locktype init_locktype(const char *);
+
+static int lkopen_dot (const char *, int, mode_t, int *);
+static void lkclose_dot (int, const char *);
+static void lockname (const char *, struct lockinfo *, int);
 static void timerON (char *, int);
 static void timerOFF (int);
 static void alrmser (int);
@@ -102,108 +108,89 @@ static void alrmser (int);
 #if !defined(HAVE_LIBLOCKFILE)
 static int lockit (struct lockinfo *);
 #endif
-#endif
 
 /*
- * Base routine to open and lock a file,
- * and return a file descriptor.
+ * Base functions: determine the data type used to lock files and
+ * call the underlying function.
  */
 
 int
-lkopen (char *file, int access, mode_t mode)
+lkopendata(const char *file, int access, mode_t mode, int *failed_to_lock)
 {
-#ifdef KERNEL_LOCKING
-    return lkopen_kernel(file, access, mode);
-#endif
+    if (! datalockinit) {
+       char *cp = context_find("datalocking");
+
+       if (cp) {
+           datalocktype = init_locktype(cp);
+       } else {
+           /* We default to fcntl locking for data files */
+           datalocktype = FCNTL_LOCKING;
+       }
 
-#ifdef DOT_LOCKING
-    return lkopen_dot(file, access, mode);
-#endif
+       datalockinit = 1;
+    }
+
+    return lkopen(file, access, mode, datalocktype, failed_to_lock);
 }
 
 
 /*
- * Base routine to close and unlock a file,
- * given a file descriptor.
+ * Locking using the spool locking algorithm
  */
 
-int
-lkclose (int fd, char *file)
+int lkopenspool(const char *file, int access, mode_t mode, int *failed_to_lock)
 {
-#ifdef FCNTL_LOCKING
-    struct flock buf;
-#endif
+    if (! spoollockinit) {
+       spoollocktype = init_locktype(spoollocking);
 
-#ifdef DOT_LOCKING
-    struct lockinfo lkinfo;
-#endif
+       spoollockinit = 1;
+    }
 
-    if (fd == -1)
-       return 0;
+    return lkopen(file, access, mode, spoollocktype, failed_to_lock);
+}
 
-#ifdef FCNTL_LOCKING
-    buf.l_type   = F_UNLCK;
-    buf.l_whence = SEEK_SET;
-    buf.l_start  = 0;
-    buf.l_len    = 0;
-    fcntl(fd, F_SETLK, &buf);
-#endif
 
-#ifdef FLOCK_LOCKING
-    flock (fd, LOCK_UN);
-#endif
+/*
+ * Versions of lkopen that return a FILE *
+ */
 
-#ifdef LOCKF_LOCKING
-    /* make sure we unlock the whole thing */
-    lseek (fd, (off_t) 0, SEEK_SET);
-    lockf (fd, F_ULOCK, 0L);
-#endif 
+FILE *
+lkfopendata(const char *file, const char *mode, int *failed_to_lock)
+{
+    FILE *fp;
+    int oflags = str2accbits(mode);
+    int fd;
 
-#ifdef DOT_LOCKING
-    lockname (file, &lkinfo, 0);       /* get name of lock file */
-#if !defined(HAVE_LIBLOCKFILE)
-    unlink (lkinfo.curlock);           /* remove lock file      */
-#else
-    lockfile_remove(lkinfo.curlock);
-#endif /* HAVE_LIBLOCKFILE */
-    timerOFF (fd);                     /* turn off lock timer   */
-#else  /* DOT_LOCKING */
-    NMH_UNUSED (file);
-#endif /* DOT_LOCKING */
+    if (oflags == -1) {
+       errno = EINVAL;
+       return NULL;
+    }
 
-    return (close (fd));
-}
+    if ((fd = lkopendata(file, oflags, 0666, failed_to_lock)) == -1)
+       return NULL;
 
+    if ((fp = fdopen (fd, mode)) == NULL) {
+       close (fd);
+       return NULL;
+    }
 
-/*
- * Base routine to open and lock a file,
- * and return a FILE pointer
- */
+    return fp;
+}
 
 FILE *
-lkfopen (char *file, char *mode)
+lkfopenspool(const char *file, const char *mode)
 {
-    int fd, access;
     FILE *fp;
+    int oflags = str2accbits(mode);
+    int failed_to_lock = 0;
+    int fd;
 
-    if (strcmp (mode, "r") == 0)
-       access = O_RDONLY;
-    else if (strcmp (mode, "r+") == 0)
-       access = O_RDWR;
-    else if (strcmp (mode, "w") == 0)
-       access = O_WRONLY | O_CREAT | O_TRUNC;
-    else if (strcmp (mode, "w+") == 0)
-       access = O_RDWR | O_CREAT | O_TRUNC;
-    else if (strcmp (mode, "a") == 0)
-       access = O_WRONLY | O_CREAT | O_APPEND;
-    else if (strcmp (mode, "a+") == 0)
-       access = O_RDWR | O_CREAT | O_APPEND;
-    else {
-       errno = EINVAL;
+    if (oflags == -1) {
+       errno = EINVAL;
        return NULL;
     }
 
-    if ((fd = lkopen (file, access, 0666)) == -1)
+    if ((fd = lkopenspool(file, oflags, 0666, &failed_to_lock)) == -1)
        return NULL;
 
     if ((fp = fdopen (fd, mode)) == NULL) {
@@ -216,140 +203,281 @@ lkfopen (char *file, char *mode)
 
 
 /*
- * Base routine to close and unlock a file,
- * given a FILE pointer
+ * Corresponding close functions.
+ *
+ * A note here: All of the kernel locking functions terminate the lock
+ * when the descriptor is closed, so why write the code to explicitly
+ * unlock the file?  We only need to do this in the dot-locking case.
  */
 
 int
-lkfclose (FILE *fp, char *file)
+lkclosedata(int fd, const char *name)
 {
-#ifdef FCNTL_LOCKING
-    struct flock buf;
-#endif
+    int rc = close(fd);
 
-#ifdef DOT_LOCKING
-    struct lockinfo lkinfo;
-#endif
+    if (datalocktype == DOT_LOCKING)
+       lkclose_dot(fd, name);
 
-    if (fp == NULL)
-       return 0;
+    return rc;
+}
 
-#ifdef FCNTL_LOCKING
-    buf.l_type   = F_UNLCK;
-    buf.l_whence = SEEK_SET;
-    buf.l_start  = 0;
-    buf.l_len    = 0;
-    fcntl(fileno(fp), F_SETLK, &buf);
-#endif
+int
+lkfclosedata(FILE *f, const char *name)
+{
+    int fd, rc;
 
-#ifdef FLOCK_LOCKING
-    flock (fileno(fp), LOCK_UN);
-#endif
+    if (f == NULL)
+       return 0;
+    
+    fd = fileno(f);
+    rc = fclose(f);
 
-#ifdef LOCKF_LOCKING
-    /* make sure we unlock the whole thing */
-    fseek (fp, 0L, SEEK_SET);
-    lockf (fileno(fp), F_ULOCK, 0L);
-#endif
+    if (datalocktype == DOT_LOCKING)
+       lkclose_dot(fd, name);
+
+    return rc;
+}
+
+int
+lkclosespool(int fd, const char *name)
+{
+    int rc = close(fd);
+
+    if (spoollocktype == DOT_LOCKING)
+       lkclose_dot(fd, name);
+
+    return rc;
+}
+
+int
+lkfclosespool(FILE *f, const char *name)
+{
+    int fd, rc;
+
+    if (f == NULL)
+       return 0;
+    
+    fd = fileno(f);
+    rc = fclose(f);
+
+    if (spoollocktype == DOT_LOCKING)
+       lkclose_dot(fd, name);
+
+    return rc;
+}
+
+
+/*
+ * Convert fopen() mode argument to open() bits
+ */
+
+static int
+str2accbits(const char *mode)
+{
+    if (strcmp (mode, "r") == 0)
+       return O_RDONLY;
+    else if (strcmp (mode, "r+") == 0)
+       return O_RDWR;
+    else if (strcmp (mode, "w") == 0)
+       return O_WRONLY | O_CREAT | O_TRUNC;
+    else if (strcmp (mode, "w+") == 0)
+       return O_RDWR | O_CREAT | O_TRUNC;
+    else if (strcmp (mode, "a") == 0)
+       return O_WRONLY | O_CREAT | O_APPEND;
+    else if (strcmp (mode, "a+") == 0)
+       return O_RDWR | O_CREAT | O_APPEND;
+    else {
+       errno = EINVAL;
+       return -1;
+    }
+}
+
+/*
+ * Internal routine to switch between different locking types.
+ */
+
+static int
+lkopen (const char *file, int access, mode_t mode, enum locktype ltype,
+        int *failed_to_lock)
+{
+    switch (ltype) {
+
+    case FCNTL_LOCKING:
+       return lkopen_fcntl(file, access, mode, failed_to_lock);
+
+    case DOT_LOCKING:
+       return lkopen_dot(file, access, mode, failed_to_lock);
+
+#ifdef HAVE_FLOCK
+    case FLOCK_LOCKING:
+       return lkopen_flock(file, access, mode, failed_to_lock);
+#endif /* HAVE_FLOCK */
+
+#ifdef HAVE_LOCKF
+    case LOCKF_LOCKING:
+       return lkopen_lockf(file, access, mode, failed_to_lock);
+#endif /* HAVE_FLOCK */
+
+    default:
+       adios(NULL, "Internal locking error: unsupported lock type used!");
+    }
+
+    return -1;
+}
+
+
+/*
+ * Routine to clean up the dot locking file
+ */
+
+static void
+lkclose_dot (int fd, const char *file)
+{
+    struct lockinfo lkinfo;
 
-#ifdef DOT_LOCKING
     lockname (file, &lkinfo, 0);       /* get name of lock file */
 #if !defined(HAVE_LIBLOCKFILE)
-    unlink (lkinfo.curlock);           /* remove lock file      */
+    (void) m_unlink (lkinfo.curlock);  /* remove lock file      */
 #else
     lockfile_remove(lkinfo.curlock);
 #endif /* HAVE_LIBLOCKFILE */
-    timerOFF (fileno(fp));             /* turn off lock timer   */
-#else  /* DOT_LOCKING */
-    NMH_UNUSED (file);
-#endif /* DOT_LOCKING */
-
-    return (fclose (fp));
+    timerOFF (fd);                     /* turn off lock timer   */
 }
 
 
-#ifdef KERNEL_LOCKING
-
 /*
- * open and lock a file, using kernel locking
+ * Open and lock a file, using fcntl locking
  */
 
 static int
-lkopen_kernel (char *file, int access, mode_t mode)
+lkopen_fcntl(const char *file, int access, mode_t mode, int *failed_to_lock)
 {
-    int fd, i, j;
+    int fd, i, saved_errno;
+    struct flock flk;
+
+    /*
+     * The assumption here is that if you open the file for writing, you
+     * need an exclusive lock.
+     */
 
-# ifdef FCNTL_LOCKING
-    struct flock buf;
-# endif /* FCNTL_LOCKING */
+    for (i = 0; i < LOCK_RETRIES; i++) {
+       if ((fd = open(file, access, mode)) == -1)
+           return -1;
 
-    for (i = 0; i < 5; i++) {
+       flk.l_start = 0;
+       flk.l_len = 0;
+       flk.l_type = (access & O_ACCMODE) == O_RDONLY ? F_RDLCK : F_WRLCK;
+       flk.l_whence = SEEK_SET;
 
-# if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING)
-       /* remember the original mode */
-       j = access;
+       if (fcntl(fd, F_SETLK, &flk) != -1)
+           return fd;
 
-       /* make sure we open at the beginning */
-       access &= ~O_APPEND;
+       saved_errno = errno;
+       close(fd);
+       sleep(1);
+    }
 
-       /*
-        * We MUST have write permission or
-        * lockf/fcntl() won't work
-        */
-       if ((access & 03) == O_RDONLY) {
-           access &= ~O_RDONLY;
-           access |= O_RDWR;
-       }
-# endif /* LOCKF_LOCKING || FCNTL_LOCKING */
+    *failed_to_lock = 1;
+    errno = saved_errno;
+    return -1;
+}
+
+
+#ifdef HAVE_FLOCK
+/*
+ * Open and lock a file, using flock locking
+ */
+
+static int
+lkopen_flock(const char *file, int access, mode_t mode, int *failed_to_lock)
+{
+    int fd, i, saved_errno, locktype;
+
+    /*
+     * The assumption here is that if you open the file for writing, you
+     * need an exclusive lock.
+     */
+
+    locktype = (((access & O_ACCMODE) == O_RDONLY) ? LOCK_SH : LOCK_EX) |
+                                                       LOCK_NB;
 
-       if ((fd = open (file, access | O_NDELAY, mode)) == -1)
+    for (i = 0; i < LOCK_RETRIES; i++) {
+       if ((fd = open(file, access, mode)) == -1)
            return -1;
 
-# ifdef FCNTL_LOCKING
-       buf.l_type   = F_WRLCK;
-       buf.l_whence = SEEK_SET;
-       buf.l_start  = 0;
-       buf.l_len    = 0;
-       if (fcntl (fd, F_SETLK, &buf) != -1)
+       if (flock(fd, locktype) != -1)
            return fd;
-# endif
 
-# ifdef FLOCK_LOCKING
-       if (flock (fd, (((access & 03) == O_RDONLY) ? LOCK_SH : LOCK_EX)
-                  | LOCK_NB) != -1)
-           return fd;
-# endif
+       saved_errno = errno;
+       close(fd);
+       sleep(1);
+    }
+
+    *failed_to_lock = 1;
+    errno = saved_errno;
+    return -1;
+}
+#endif /* HAVE_FLOCK */
+
+/*
+ * Open and lock a file, using lockf locking
+ */
+
+static int
+lkopen_lockf(const char *file, int access, mode_t mode, int *failed_to_lock)
+{
+    int fd, i, saved_errno, saved_access;
 
-# ifdef LOCKF_LOCKING
-       if (lockf (fd, F_TLOCK, 0L) != -1) {
-           /* see if we should be at the end */
-           if (j & O_APPEND)
-               lseek (fd, (off_t) 0, SEEK_END);
+    /*
+     * Two notes:
+     *
+     * Because lockf locks start from the current offset, mask off O_APPEND
+     * and seek to the end of the file later if it was requested.
+     *
+     * lockf locks require write access to the file, so always add it
+     * even if it wasn't requested.
+     */
+
+    saved_access = access;
+
+    access &= ~O_APPEND;
+
+    if ((access & O_ACCMODE) == O_RDONLY) {
+       access &= ~O_RDONLY;
+       access |= O_RDWR;
+    }
+
+    for (i = 0; i < LOCK_RETRIES; i++) {
+       if ((fd = open(file, access, mode)) == -1)
+           return -1;
+
+       if (lockf(fd, F_TLOCK, 0) != -1) {
+           /*
+            * Seek to end if requested
+            */
+           if (saved_access & O_APPEND) {
+               lseek(fd, 0, SEEK_END);
+           }
            return fd;
        }
-# endif
 
-       j = errno;
-       close (fd);
-       sleep (5);
+       saved_errno = errno;
+       close(fd);
+       sleep(1);
     }
 
-    close (fd);
-    errno = j;
+    *failed_to_lock = 1;
+    errno = saved_errno;
     return -1;
 }
 
-#endif /* KERNEL_LOCKING */
-
-
-#ifdef DOT_LOCKING
 
 /*
  * open and lock a file, using dot locking
  */
 
 static int
-lkopen_dot (char *file, int access, mode_t mode)
+lkopen_dot (const char *file, int access, mode_t mode, int *failed_to_lock)
 {
     int fd;
     struct lockinfo lkinfo;
@@ -367,7 +495,7 @@ lkopen_dot (char *file, int access, mode_t mode)
 #if !defined(HAVE_LIBLOCKFILE)
     {
        int i;
-       for (i = 0;;) {
+       for (i = 0; i < LOCK_RETRIES; ++i) {
            /* attempt to create lock file */
            if (lockit (&lkinfo) == 0) {
                /* if successful, turn on timer and return */
@@ -376,27 +504,30 @@ lkopen_dot (char *file, int access, mode_t mode)
            } else {
                /*
                 * Abort locking, if we fail to lock after 5 attempts
-                * and are never able to stat the lock file.
+                * and are never able to stat the lock file.  Or, if
+                * we can stat the lockfile but exceed LOCK_RETRIES
+                * seconds waiting for it (by falling out of the loop).
                 */
                struct stat st;
                if (stat (lkinfo.curlock, &st) == -1) {
-                   if (i++ > 5)
-                       return -1;
-                   sleep (5);
+                   if (i++ > 5) break;
+                   sleep (1);
                } else {
                    time_t curtime;
-                   i = 0;
                    time (&curtime);
                    
                    /* check for stale lockfile, else sleep */
                    if (curtime > st.st_ctime + RSECS)
-                       unlink (lkinfo.curlock);
+                       (void) m_unlink (lkinfo.curlock);
                    else
-                       sleep (5);
+                       sleep (1);
                }
                lockname (file, &lkinfo, 1);
            }
        }
+
+        *failed_to_lock = 1;
+        return -1;
     }
 #else
     if (lockfile_create(lkinfo.curlock, 5, 0) == L_SUCCESS) {
@@ -405,6 +536,7 @@ lkopen_dot (char *file, int access, mode_t mode)
     }
     else {
         close(fd);
+       *failed_to_lock = 1;
         return -1;
     }
 #endif /* HAVE_LIBLOCKFILE */
@@ -420,17 +552,18 @@ static int
 lockit (struct lockinfo *li)
 {
     int fd;
-    char *curlock, *tmplock;
+    char *curlock, *tmpfile;
 
 #if 0
     char buffer[128];
 #endif
 
     curlock = li->curlock;
-    tmplock = li->tmplock;
 
-    if ((fd = mkstemp(tmplock)) == -1)
+    if ((tmpfile = m_mktemp(li->tmplock, &fd, NULL)) == NULL) {
+        advise(NULL, "unable to create temporary file in %s", li->tmplock);
        return -1;
+    }
 
 #if 0
     /* write our process id into lock file */
@@ -444,8 +577,8 @@ lockit (struct lockinfo *li)
      * Now try to create the real lock file
      * by linking to the temporary file.
      */
-    fd = link(tmplock, curlock);
-    unlink(tmplock);
+    fd = link(tmpfile, curlock);
+    (void) m_unlink(tmpfile);
 
     return (fd == -1 ? -1 : 0);
 }
@@ -456,10 +589,11 @@ lockit (struct lockinfo *li)
  */
 
 static void
-lockname (char *file, struct lockinfo *li, int isnewlock)
+lockname (const char *file, struct lockinfo *li, int isnewlock)
 {
     int bplen, tmplen;
-    char *bp, *cp;
+    char *bp;
+    const char *cp;
 
 #if 0
     struct stat st;
@@ -608,4 +742,33 @@ alrmser (int sig)
     alarm (NSECS);
 }
 
-#endif /* DOT_LOCKING */
+
+/*
+ * Return a locking algorithm based on the string name
+ */
+
+static enum locktype
+init_locktype(const char *lockname)
+{
+    if (strcasecmp(lockname, "fcntl") == 0) {
+       return FCNTL_LOCKING;
+    } else if (strcasecmp(lockname, "lockf") == 0) {
+#ifdef HAVE_LOCKF
+       return LOCKF_LOCKING;
+#else /* ! HAVE_LOCKF */
+       adios(NULL, "lockf not supported on this system");
+#endif /* HAVE_LOCKF */
+    } else if (strcasecmp(lockname, "flock") == 0) {
+#ifdef HAVE_FLOCK
+       return FLOCK_LOCKING;
+#else /* ! HAVE_FLOCK */
+       adios(NULL, "flock not supported on this system");
+#endif /* HAVE_FLOCK */
+    } else if (strcasecmp(lockname, "dot") == 0) {
+       return DOT_LOCKING;
+    } else {
+       adios(NULL, "Unknown lock type: \"%s\"", lockname);
+       /* NOTREACHED */
+       return 0;
+    }
+}