Imported Upstream version 3.1.0
[debian/amanda] / common-src / amflock.c
index 2a00b38835e33cf4ec3b819b62d41fed8725caf0..177266dd07c52bd4f5e03bac41fa23689b59d00e 100644 (file)
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: amflock.c,v 1.28 2006/05/25 01:47:11 johnfranks Exp $
+ * $Id: amflock.c 7161 2007-07-03 16:27:26Z dustin $
  *
  * file locking routines, put here to hide the system dependant stuff
  * from the rest of the code
  */
-/*
-**
-** Notes:
-** - These are "best effort" routines.
-** - "configure" has four variables that are used to determine which type of
-**   locking to use:
-**     USE_POSIX_FCNTL - use fcntl().  The full job.
-**     USE_FLOCK       - use flock().  Does just as well.
-**     USE_LOCKF       - use lockf().  Only handles advisory, exclusive,
-**                       blocking file locks as used by Amanda.
-**     USE_LNLOCK      - Home brew exclusive, blocking file lock.
-**     <none>          - No locking available.  User beware!
-** - "configure" compiles this with -DCONFIGURE_TEST to try and determine
-**   whether a particular type of locking works.
-*/
 
 #include "amanda.h"
 
-#if defined(USE_POSIX_FCNTL)
-     static struct flock lock; /* zero-initialized */
-#endif
-
-#if !defined(USE_POSIX_FCNTL) && defined(USE_FLOCK)
-#  ifdef HAVE_SYS_FILE_H
-#    include <sys/file.h>
-#  endif
-
-#  if !defined(HAVE_FLOCK_DECL) && !defined(CONFIGURE_TEST)
-     extern int flock(int fd, int operation);
-#  endif
-#endif
-
-
-#if !defined(USE_POSIX_FCNTL) && !defined(USE_FLOCK) && defined(USE_LOCKF)
+/*
+ * New Implementation
+ */
 
-/* XPG4-UNIX (eg, SGI IRIX, DEC DU) has F_ULOCK instead of F_UNLOCK */
-#if defined(F_ULOCK) && !defined(F_UNLOCK)
-#  define F_UNLOCK F_ULOCK
-#endif
+static GStaticMutex lock_lock = G_STATIC_MUTEX_INIT;
+static GHashTable *locally_locked_files = NULL;
 
-/* Lock a file using lockf().
-** Notes:
-** - returns errors for some non-files like pipes.
-** - probably only works for files open for writing.
-*/
-int
-use_lockf(
-    int fd,    /* fd of file to operate on */
-    int op)    /* true to lock; false to unlock */
+file_lock *
+file_lock_new(
+    const char *filename)
 {
-       off_t pos;
+    file_lock *lock = g_new0(file_lock, 1);
+    lock->filename = g_strdup(filename);
+    lock->fd = -1;
 
-       if (op) {
-               /* lock from here on */
-               if (lockf(fd, F_LOCK, (off_t)0) == -1) return -1;
-       }
-       else {
-               /* unlock from here on */
-               if (lockf(fd, F_UNLOCK, (off_t)0) == -1) return -1;
-
-               /* unlock from bof to here */
-               pos = lseek(fd, (off_t)0, SEEK_CUR);
-               if (pos == (off_t)-1) {
-                       if (errno == ESPIPE) pos = (off_t)0;
-                       else return -1;
-               }
-
-               if (pos > (off_t)0 &&
-                   lockf(fd, F_UNLOCK, -pos) == -1) return -1;
-       }
-
-       return 0;
+    return lock;
 }
 
-#endif
-
-#if !defined(USE_POSIX_FCNTL) && !defined(USE_FLOCK) && !defined(USE_LOCKF) && defined(USE_LNLOCK)
-/* XXX - error checking in this section needs to be tightened up */
-
-/* Delete a lock file.
-*/
-int
-delete_lock(
-    char *fn)
+void
+file_lock_free(
+    file_lock *lock)
 {
-       int rc;
+    g_static_mutex_lock(&lock_lock);
+    if (locally_locked_files) {
+       g_hash_table_remove(locally_locked_files,
+                           lock->filename);
+    }
 
-       rc = unlink(fn);
-       if (rc != 0 && errno == ENOENT) rc = 0;
+    if (lock->data)
+       g_free(lock->data);
+    if (lock->filename)
+       g_free(lock->filename);
 
-       return rc;
+    if (lock->fd != -1)
+       close(lock->fd);
+
+    g_static_mutex_unlock(&lock_lock);
 }
 
-/* Create a lock file.
-*/
 int
-create_lock(
-    char *fn,
-    pid_t pid)
+file_lock_lock(
+    file_lock *lock)
 {
-       int fd;
-       FILE *f;
-       int mask;
-
-       (void)delete_lock(fn);                  /* that's MY file! */
-
-       mask = umask(0027);
-       fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0640);
-       umask(mask);
-       if (fd == -1) return -1;
+    int rv = -2;
+    int fd = -1;
+    int saved_errno;
+    struct flock lock_buf;
+    struct stat stat_buf;
+
+    g_assert(!lock->locked);
+
+    /* protect from overlapping lock operations within a process */
+    g_static_mutex_lock(&lock_lock);
+    if (!locally_locked_files) {
+       locally_locked_files = g_hash_table_new(g_str_hash, g_str_equal);
+    }
 
-       if((f = fdopen(fd, "w")) == NULL) {
-           aclose(fd);
-           return -1;
-       }
-       fprintf(f, "%ld\n", pid);
-       if (fclose(f) == EOF)
-           return -1;
-       return 0;
-}
+    /* if this filename is in the hash table, then some other thread in this
+     * process has locked it */
+    if (g_hash_table_lookup(locally_locked_files, lock->filename)) {
+       rv = 1;
+       goto done;
+    }
 
-/* Read the pid out of a lock file.
-**   -1=error, otherwise pid.
-*/
-long
-read_lock(
-    char *     fn) /* name of lock file */
-{
-       int save_errno;
-       FILE *f;
-       long pid;
+    /* The locks are advisory, so an error here never means the lock is already
+     * taken. */
+    lock->fd = fd = open(lock->filename, O_CREAT|O_RDWR, 0666);
+    if (fd < 0) {
+       rv = -1;
+       goto done;
+    }
 
-       if ((f = fopen(fn, "r")) == NULL) {
-               return -1;
-       }
-       if (fscanf(f, "%ld", &pid) != 1) {
-               save_errno = errno;
-               afclose(f);
-               errno = save_errno;
-               return -1;
-       }
-       if (fclose(f) != 0) {
-               return -1;
-       }
-       return pid;
-}
+    /* now try locking it */
+    lock_buf.l_type = F_WRLCK;
+    lock_buf.l_start = 0;
+    lock_buf.l_whence = SEEK_SET;
+    lock_buf.l_len = 0; /* to EOF */
+    if (fcntl(fd, F_SETLK, &lock_buf) < 0) {
+       if (errno == EACCES || errno == EAGAIN)
+           rv = 1;
+       else
+           rv = -1;
+       goto done;
+    }
 
-/* Link a lock if we can.
-**   0=done, 1=already locked, -1=error.
-*/
-int
-link_lock(
-    char *     lk,     /* real lock file */
-    char *     tlk)    /* temp lock file */
-{
-       int rc;
-       int serrno;     /* saved errno */
-       struct stat lkstat, tlkstat;
+    /* and read the file in its entirety */
+    if (fstat(fd, &stat_buf) < 0) {
+       rv = -1;
+       goto done;
+    }
 
-       /* an atomic check and set operation */
-       rc = link(tlk, lk);
-       if (rc == 0) return 0; /* XXX do we trust it? */
+    if (!(stat_buf.st_mode & S_IFREG)) {
+       rv = -1;
+       errno = EINVAL;
+       goto done;
+    }
 
-       /* link() says it failed - don't beleive it */
-       serrno = errno;
+    if (stat_buf.st_size) {
+       lock->data = g_malloc(stat_buf.st_size);
+       lock->len = stat_buf.st_size;
+       if (full_read(fd, lock->data, lock->len) < lock->len) {
+           rv = -1;
+           goto done;
+       }
+    }
 
-       if (stat(lk, &lkstat) == 0 &&
-           stat(tlk, &tlkstat) == 0 &&
-           lkstat.st_ino == tlkstat.st_ino)
-               return 0;       /* it did work! */
+    fd = -1; /* we'll keep the file now */
+    lock->locked = TRUE;
 
-       errno = serrno;
+    /* the lock is acquired; record this in the hash table */
+    g_hash_table_insert(locally_locked_files, lock->filename, lock->filename);
 
-       if (errno == EEXIST) rc = 1;
+    rv = 0;
 
-       return rc;
+done:
+    saved_errno = errno;
+    g_static_mutex_unlock(&lock_lock);
+    if (fd >= 0) /* close and unlock if an error occurred */
+       close(fd);
+    errno = saved_errno;
+    return rv;
 }
 
-/* Steal a lock if we can.
-**   0=done; 1=still in use; -1 = error.
-*/
 int
-steal_lock(
-    char *     fn,     /* name of lock file to steal */
-    pid_t      mypid,  /* my process id */
-    char *     sres)   /* name of steal-resource to lock */
+file_lock_write(
+    file_lock *lock,
+    const char *data,
+    size_t len)
 {
-       int fd;
-       char buff[64];
-       long pid;
-       int rc;
-
-       /* prevent a race with another stealer */
-       rc = ln_lock(sres, 1);
-       if (rc != 0) goto error;
-
-       pid = read_lock(fn);
-       if (pid == -1) {
-               if (errno == ENOENT) goto done;
-               goto error;
-       }
-
-       if (pid == mypid) goto steal; /* i'm the locker! */
-
-       /* are they still there ? */
-       rc = kill((pid_t)pid, 0);
-       if (rc != 0) {
-               if (errno == ESRCH) goto steal; /* locker has gone */
-               goto error;
-       }
-
-inuse:
-       rc = ln_lock(sres, 0);
-       if (rc != 0) goto error;
+    int fd = lock->fd;
 
-       return 1;
+    g_assert(lock->locked);
 
-steal:
-       rc = delete_lock(fn);
-       if (rc != 0) goto error;
+    /* seek to position 0, rewrite, and truncate */
+    if (lseek(fd, 0, SEEK_SET) < 0)
+       return -1;
 
-done:
-       rc = ln_lock(sres, 0);
-       if (rc != 0) goto error;
+    /* from here on out, any errors have corrupted the datafile.. */
+    if (full_write(fd, data, len) < len)
+       return -1;
 
-       return 0;
+    if (lock->len > len) {
+       if (ftruncate(fd, len) < 0)
+           return -1;
+    }
 
-error:
-       rc = ln_lock(sres, 0);
+    if (lock->data)
+       g_free(lock->data);
+    lock->data = g_strdup(data);
+    lock->len = len;
 
-       return -1;
+    return 0;
 }
 
-/* Locking using existance of a file.
-*/
 int
-ln_lock(
-    char *     res, /* name of resource to lock */
-    int                op)  /* true to lock; false to unlock */
+file_lock_unlock(
+    file_lock *lock)
 {
-       long mypid;
-       char *lockfile = NULL;
-       char *tlockfile = NULL;
-       char *mres = NULL;
-       int rc;
-       char pid_str[NUM_STR_SIZE];
+    g_assert(lock->locked);
 
-       mypid = (long)getpid();
+    g_static_mutex_lock(&lock_lock);
 
-       lockfile = vstralloc(AMANDA_TMPDIR, "/am", res, ".lock", NULL);
+    /* relase the filesystem-level lock */
+    close(lock->fd);
 
-       if (!op) {
-               /* unlock the resource */
-               assert(read_lock(lockfile) == mypid);
-
-               (void)delete_lock(lockfile);
-               amfree(lockfile);
-               return 0;
-       }
+    /* and the hash table entry */
+    g_hash_table_remove(locally_locked_files, lock->filename);
 
-       /* lock the resource */
+    g_static_mutex_unlock(&lock_lock);
 
-       snprintf(pid_str, SIZEOF(pid_str), "%ld", mypid);
-       tlockfile = vstralloc(AMANDA_TMPDIR, "am", res, ".", pid_str, NULL);
+    if (lock->data)
+       g_free(lock->data);
+    lock->data = NULL;
+    lock->len = 0;
+    lock->fd = -1;
+    lock->locked = FALSE;
 
-       (void)create_lock(tlockfile, mypid);
-
-       mres = stralloc2(res, ".");
-
-       while(1) {
-               rc = link_lock(lockfile, tlockfile);
-               if (rc == -1) break;
-               if (rc == 0) break;
+    return 0;
+}
 
-               rc = steal_lock(lockfile, mypid, mres);
-               if (rc == -1) break;
-               if (rc == 0) continue;
-               sleep(1);
-       }
+/*
+ * Old Implementation
+ */
 
-       (void) delete_lock(tlockfile);
+/*
+**
+** Notes:
+** - These are "best effort" routines.
+** - "configure" has four variables that are used to determine which type of
+**   locking to use:
+**     USE_POSIX_FCNTL - use fcntl().  The full job.
+**     USE_FLOCK       - use flock().  Does just as well.
+**     USE_LOCKF       - use lockf().  Only handles advisory, exclusive,
+**                       blocking file locks as used by Amanda.
+**     USE_LNLOCK      - Home brew exclusive, blocking file lock.
+**     <none>          - No locking available.  User beware!
+*/
 
-       amfree(mres);
-       amfree(tlockfile);
-       amfree(lockfile);
+/* Interface to the implementations in common-src/amflock-*.c */
 
-       return rc;
-}
+#ifdef WANT_AMFLOCK_POSIX
+extern amflock_impl_t amflock_posix_impl;
 #endif
-
-
-/*
- * Get a file lock (for read-only files).
- */
-int
-amroflock(
-    int                fd,
-    char *     resource)
-{
-       int r;
-
-#ifdef USE_POSIX_FCNTL
-       (void)resource; /* Quiet unused paramater warning */
-       lock.l_type = F_RDLCK;
-       lock.l_whence = SEEK_SET;
-       r = fcntl(fd, F_SETLKW, &lock);
-#else
-       (void)fd; /* Quiet unused paramater warning */
-       r = amflock(fd, resource);
+#ifdef WANT_AMFLOCK_FLOCK
+extern amflock_impl_t amflock_flock_impl;
+#endif
+#ifdef WANT_AMFLOCK_LOCKF
+extern amflock_impl_t amflock_lockf_impl;
+#endif
+#ifdef WANT_AMFLOCK_LNLOCK
+extern amflock_impl_t amflock_lnlock_impl;
 #endif
 
-       return r;
-}
+amflock_impl_t *amflock_impls[] = {
+#ifdef WANT_AMFLOCK_POSIX
+    &amflock_posix_impl,
+#endif
+#ifdef WANT_AMFLOCK_FLOCK
+    &amflock_flock_impl,
+#endif
+#ifdef WANT_AMFLOCK_LOCKF
+    &amflock_lockf_impl,
+#endif
+#ifdef WANT_AMFLOCK_LNLOCK
+    &amflock_lnlock_impl,
+#endif
+    NULL
+};
 
+/* Interface functions */
+/* FIXME: for now, these just use the first non-NULL implementation
+ */
 
 /* Get a file lock (for read/write files).
 */
@@ -351,139 +272,30 @@ amflock(
     int                fd,
     char *     resource)
 {
-       int r;
-
-#ifdef USE_POSIX_FCNTL
-       (void)resource; /* Quiet unused paramater warning */
-       lock.l_type = F_WRLCK;
-       lock.l_whence = SEEK_SET;
-       r = fcntl(fd, F_SETLKW, &lock);
-#else
-#ifdef USE_FLOCK
-       (void)resource; /* Quiet unused paramater warning */
-       r = flock(fd, LOCK_EX);
-#else
-#ifdef USE_LOCKF
-       (void)resource; /* Quiet unused paramater warning */
-       r = use_lockf(fd, 1);
-#else
-#ifdef USE_LNLOCK
-       (void)fd; /* Quiet unused paramater warning */
-       r = ln_lock(resource, 1);
-#else
-       (void)fd; /* Quiet unused paramater warning */
-       (void)resource; /* Quiet unused paramater warning */
-       r = 0;
-#endif
-#endif
-#endif
-#endif
-
-       return r;
+    if (!amflock_impls[0]) return 0; /* no locking */
+    return amflock_impls[0]->amflock_impl(fd, resource);
 }
 
-
-/* Release a file lock.
-*/
+/*
+ * Get a file lock (for read-only files).
+ */
 int
-amfunlock(
+amroflock(
     int                fd,
     char *     resource)
 {
-       int r;
-
-#ifdef USE_POSIX_FCNTL
-       (void)resource; /* Quiet unused paramater warning */
-       lock.l_type = F_UNLCK;
-       lock.l_whence = SEEK_SET;
-       r = fcntl(fd, F_SETLK, &lock);
-#else
-#ifdef USE_FLOCK
-       (void)resource; /* Quiet unused paramater warning */
-       r = flock(fd, LOCK_UN);
-#else
-#ifdef USE_LOCKF
-       (void)fd; /* Quiet unused paramater warning */
-       r = use_lockf(fd, 0);
-#else
-#ifdef USE_LNLOCK
-       (void)fd; /* Quiet unused paramater warning */
-       r = ln_lock(resource, 0);
-#else
-       (void)fd; /* Quiet unused paramater warning */
-       (void)resource; /* Quiet unused paramater warning */
-       r = 0;
-#endif
-#endif
-#endif
-#endif
-
-       return r;
+    if (!amflock_impls[0]) return 0; /* no locking */
+    return amflock_impls[0]->amroflock_impl(fd, resource);
 }
 
-
-/* Test routine for use by configure.
-** (I'm not sure why we use both return and exit!)
-** XXX the testing here should be a lot more comprehensive.
-**     - lock the file and then try and lock it from another process
-**     - lock the file from another process and check that process
-**       termination unlocks it.
-**     The hard part is to find a system independent way to not block
-**     for ever.
-*/
-#ifdef CONFIGURE_TEST
+/*
+ * Release a file lock.
+ */
 int
-main(
-    int argc,
-    char **argv)
+amfunlock(
+    int                fd,
+    char *     resource)
 {
-    int lockfd;
-    char *filen = "/tmp/conftest.lock";
-    char *resn = "test";
-    int fd;
-
-    (void)argc;                /* Quiet compiler warning */
-    (void)argv;                /* Quiet compiler warning */
-
-    unlink(filen);
-    if ((lockfd = open(filen, O_RDONLY | O_CREAT | O_EXCL, 0600)) == -1) {
-       perror (filen);
-       exit(10);
-    }
-
-    if (amroflock(lockfd, resn) != 0) {
-       perror ("amroflock");
-       exit(1);
-    }
-    if (amfunlock(lockfd, resn) != 0) {
-       perror ("amfunlock/2");
-       exit(2);
-    }
-
-    /*
-     * Do not use aclose() here.  During configure we do not have
-     * areads_relbuf() available and it makes configure think all
-     * the tests have failed.
-     */
-    close(lockfd);
-
-    unlink(filen);
-    if ((lockfd = open(filen, O_WRONLY | O_CREAT | O_EXCL, 0600)) == -1) {
-       perror (filen);
-       exit(20);
-    }
-
-    if (amflock(lockfd, resn) != 0) {
-       perror ("amflock");
-       exit(3);
-    }
-    if (amfunlock(lockfd, resn) != 0) {
-       perror ("amfunlock/4");
-       exit(4);
-    }
-
-    close(lockfd);
-
-    exit(0);
+    if (!amflock_impls[0]) return 0; /* no locking */
+    return amflock_impls[0]->amfunlock_impl(fd, resource);
 }
-#endif