* 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;
+static int lock_rw_rd(file_lock *lock, short l_type);
-/* 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);
+ }
+
+ if (lock->data)
+ g_free(lock->data);
+ if (lock->filename)
+ g_free(lock->filename);
- rc = unlink(fn);
- if (rc != 0 && errno == ENOENT) rc = 0;
+ if (lock->fd != -1)
+ close(lock->fd);
- return rc;
+ 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 */
+static int
+lock_rw_rd(
+ file_lock *lock,
+ short l_type)
{
- 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;
- }
+ 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);
+
+ /* 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 (pid == mypid) goto steal; /* i'm the locker! */
+ /* now try locking it */
+ lock_buf.l_type = l_type;
+ 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;
+ }
- /* are they still there ? */
- rc = kill((pid_t)pid, 0);
- if (rc != 0) {
- if (errno == ESRCH) goto steal; /* locker has gone */
- goto error;
- }
+ /* and read the file in its entirety */
+ if (fstat(fd, &stat_buf) < 0) {
+ rv = -1;
+ goto done;
+ }
-inuse:
- rc = ln_lock(sres, 0);
- if (rc != 0) goto error;
+ if (!(stat_buf.st_mode & S_IFREG)) {
+ rv = -1;
+ errno = EINVAL;
+ goto done;
+ }
- return 1;
+ fd = -1; /* we'll keep the file now */
+ lock->locked = TRUE;
-steal:
- rc = delete_lock(fn);
- if (rc != 0) goto error;
+ rv = 0;
done:
- rc = ln_lock(sres, 0);
- if (rc != 0) goto error;
+ 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;
+}
- return 0;
+int
+file_lock_lock_wr(
+ file_lock *lock)
+{
+ return lock_rw_rd(lock, F_WRLCK);
+}
-error:
- rc = ln_lock(sres, 0);
+int
+file_lock_lock_rd(
+ file_lock *lock)
+{
+ return lock_rw_rd(lock, F_RDLCK);
+}
- return -1;
+int
+file_lock_locked(
+ file_lock *lock)
+{
+ return lock->locked;
}
-/* 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_write(
+ file_lock *lock,
+ const char *data,
+ size_t len)
{
- long mypid;
- char *lockfile = NULL;
- char *tlockfile = NULL;
- char *mres = NULL;
- int rc;
- char pid_str[NUM_STR_SIZE];
+ int fd = lock->fd;
- mypid = (long)getpid();
+ g_assert(lock->locked);
- lockfile = vstralloc(AMANDA_TMPDIR, "/am", res, ".lock", NULL);
+ /* seek to position 0, rewrite, and truncate */
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return -1;
- if (!op) {
- /* unlock the resource */
- assert(read_lock(lockfile) == mypid);
+ /* from here on out, any errors have corrupted the datafile.. */
+ if (full_write(fd, data, len) < len)
+ return -1;
- (void)delete_lock(lockfile);
- amfree(lockfile);
- return 0;
- }
+ if (lock->len > len) {
+ if (ftruncate(fd, len) < 0)
+ return -1;
+ }
- /* lock the resource */
+ if (lock->data)
+ g_free(lock->data);
+ lock->data = g_strdup(data);
+ lock->len = len;
- snprintf(pid_str, SIZEOF(pid_str), "%ld", mypid);
- tlockfile = vstralloc(AMANDA_TMPDIR, "am", res, ".", pid_str, NULL);
+ return 0;
+}
- (void)create_lock(tlockfile, mypid);
+int
+file_lock_unlock(
+ file_lock *lock)
+{
+ g_assert(lock->locked);
- mres = stralloc2(res, ".");
+ g_static_mutex_lock(&lock_lock);
- while(1) {
- rc = link_lock(lockfile, tlockfile);
- if (rc == -1) break;
- if (rc == 0) break;
+ /* relase the filesystem-level lock */
+ close(lock->fd);
- rc = steal_lock(lockfile, mypid, mres);
- if (rc == -1) break;
- if (rc == 0) continue;
- sleep(1);
- }
+ /* and the hash table entry */
+ if (locally_locked_files) {
+ g_hash_table_remove(locally_locked_files, lock->filename);
+ }
- (void) delete_lock(tlockfile);
+ g_static_mutex_unlock(&lock_lock);
- amfree(mres);
- amfree(tlockfile);
- amfree(lockfile);
+ if (lock->data)
+ g_free(lock->data);
+ lock->data = NULL;
+ lock->len = 0;
+ lock->fd = -1;
+ lock->locked = FALSE;
- return rc;
+ return 0;
}
-#endif
-
/*
- * Get a file lock (for read-only files).
+ * Old Implementation
*/
-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);
+
+/*
+**
+** 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!
+*/
+
+/* Interface to the implementations in common-src/amflock-*.c */
+
+#ifdef WANT_AMFLOCK_POSIX
+extern amflock_impl_t amflock_posix_impl;
+#endif
+#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).
*/
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