From: Henrique de Moraes Holschuh <hmh@debian.org>
Subject: Use blocking locks and SIGALRM to break hung locks
Author: Henrique de Moraes Holschuh <hmh@debian.org>

Origin: vendor, Debian Cyrus IMAPd 2.1.15-5 (2003-07-26)

RFC PATCH

Change locking system to use blocking locks and signals to break out of
locks.

Is this patch correct?  Will it cause issues with other uses of alarm()
in Cyrus code?

--- a/configure.in
+++ b/configure.in
@@ -1463,7 +1463,11 @@ enum {
     SQUAT_ENGINE = 1,
 
     /* should we have long LMTP error messages? */
-    LMTP_LONG_ERROR_MSGS = 1
+    LMTP_LONG_ERROR_MSGS = 1,
+
+    /* default time to wait, in seconds, before giving up
+     * trying to lock something */
+    LOCK_GIVEUP_TIMER_DEFAULT = 100
 };
 
 #endif /* _CYRUS_IMAPD_CONFIG_H_ */
--- a/lib/lock_fcntl.c
+++ b/lib/lock_fcntl.c
@@ -48,11 +48,43 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <syslog.h>
+#include <signal.h>
 
 #include "cyr_lock.h"
 
 const char *lock_method_desc = "fcntl";
 
+int lock_wait_time = LOCK_GIVEUP_TIMER_DEFAULT;
+
+/* Signal handling. We REQUIRE SYSV abort-syscall behaviour */
+
+static volatile int lock_gotsigalrm = 0;
+void lock_sigalrm_handler(int sig __attribute__((unused)))
+{
+    lock_gotsigalrm = 1;
+}
+
+static int setsigalrm(int enable)
+{
+    struct sigaction action;
+
+    sigemptyset(&action.sa_mask);
+    action.sa_flags = SA_RESETHAND;
+
+    if(enable) {
+	action.sa_handler = lock_sigalrm_handler;
+    } else {
+	action.sa_handler = SIG_IGN;
+    }
+    if (sigaction(SIGALRM, &action, NULL) < 0) {
+	syslog(LOG_ERR, "installing SIGALRM handler: sigaction: %m");
+	return -1;
+    }
+    lock_gotsigalrm = 0;
+    return 0;
+}
+
 /*
  * Block until we obtain an exclusive lock on the file descriptor 'fd',
  * opened for reading and writing on the file named 'filename'.  If
@@ -66,12 +98,10 @@ const char *lock_method_desc = "fcntl";
  * 'failaction' is provided, it is filled in with a pointer to a fixed
  * string naming the action that failed.
  *
+ * We use POSIX semanthics and alarm() to avoid deadlocks
+ *
  */
-int lock_reopen(fd, filename, sbuf, failaction)
-int fd;
-const char *filename;
-struct stat *sbuf;
-const char **failaction;
+int lock_reopen (int fd, const char *filename, struct stat *sbuf, const char **failaction)
 {
     int r;
     struct flock fl;
@@ -80,6 +110,8 @@ const char **failaction;
 
     if (!sbuf) sbuf = &sbufspare;
 
+    setsigalrm(1);
+    alarm(lock_wait_time);
     for (;;) {
 	fl.l_type= F_WRLCK;
 	fl.l_whence = SEEK_SET;
@@ -87,8 +119,10 @@ const char **failaction;
 	fl.l_len = 0;
 	r = fcntl(fd, F_SETLKW, &fl);
 	if (r == -1) {
-	    if (errno == EINTR) continue;
-	    if (failaction) *failaction = "locking";
+	    if (errno == EINTR && !lock_gotsigalrm) continue;
+ 	    if (failaction) *failaction = "locking";
+	    alarm(0);
+	    setsigalrm(0);
 	    return -1;
 	}
 
@@ -101,10 +135,16 @@ const char **failaction;
 	    fl.l_start = 0;
 	    fl.l_len = 0;
 	    r = fcntl(fd, F_SETLKW, &fl);
+	    alarm(0);
+	    setsigalrm(0);
 	    return -1;
 	}
 
-	if (sbuf->st_ino == sbuffile.st_ino) return 0;
+	if (sbuf->st_ino == sbuffile.st_ino) {
+	    alarm(0);
+	    setsigalrm(0);
+	    return 0;
+	}
 
 	newfd = open(filename, O_RDWR);
 	if (newfd == -1) {
@@ -114,11 +154,15 @@ const char **failaction;
 	    fl.l_start = 0;
 	    fl.l_len = 0;
 	    r = fcntl(fd, F_SETLKW, &fl);
+	    alarm(0);
+	    setsigalrm(0);
 	    return -1;
 	}
 	dup2(newfd, fd);
 	close(newfd);
     }
+    alarm(0);
+    setsigalrm(0);
 }
 
 /*
@@ -126,22 +170,32 @@ const char **failaction;
  * Returns 0 for success, -1 for failure, with errno set to an
  * appropriate error code.
  */
-int lock_blocking(fd)
-int fd;
+int lock_blocking(int fd)
 {
     int r;
     struct flock fl;
 
+    setsigalrm(1);
+    alarm(lock_wait_time);
     for (;;) {
 	fl.l_type= F_WRLCK;
 	fl.l_whence = SEEK_SET;
 	fl.l_start = 0;
 	fl.l_len = 0;
 	r = fcntl(fd, F_SETLKW, &fl);
-	if (r != -1) return 0;
-	if (errno == EINTR) continue;
+	if (r != -1) {
+	    alarm(0);
+	    setsigalrm(0);
+	    return 0;
+	}
+	if (errno == EINTR && !lock_gotsigalrm) continue;
+	alarm(0);
+	setsigalrm(0);
 	return -1;
     }
+    alarm(0);
+    setsigalrm(0);
+    return 0;
 }
 
 /*
@@ -149,22 +203,32 @@ int fd;
  * Returns 0 for success, -1 for failure, with errno set to an
  * appropriate error code.
  */
-int lock_shared(fd)
-int fd;
+int lock_shared(int fd)
 {
     int r;
     struct flock fl;
 
+    setsigalrm(1);
+    alarm(lock_wait_time);
     for (;;) {
 	fl.l_type= F_RDLCK;
 	fl.l_whence = SEEK_SET;
 	fl.l_start = 0;
 	fl.l_len = 0;
 	r = fcntl(fd, F_SETLKW, &fl);
-	if (r != -1) return 0;
-	if (errno == EINTR) continue;
+	if (r != -1) {
+	    alarm(0);
+	    setsigalrm(0);
+	    return 0;
+	}
+	if (errno == EINTR && !lock_gotsigalrm) continue;
+	alarm(0);
+	setsigalrm(0);
 	return -1;
     }
+    alarm(0);
+    setsigalrm(0);
+    return 0;
 }
 
 /*
@@ -172,8 +236,7 @@ int fd;
  * Returns 0 for success, -1 for failure, with errno set to an
  * appropriate error code.
  */
-int lock_nonblocking(fd)
-int fd;
+int lock_nonblocking(int fd)
 {
     int r;
     struct flock fl;
@@ -188,10 +251,13 @@ int fd;
 	if (errno == EINTR) continue;
 	return -1;
     }
+    return 0;
 }
 
 /*
- * Release any lock on 'fd'.  Always returns success.
+ * Release any lock on 'fd'
+ * Returns 0 for success, -1 for failure, with errno set to an
+ * appropriate error code.
  */
 int lock_unlock(int fd)
 { 
@@ -210,5 +276,6 @@ int lock_unlock(int fd)
         /* xxx help! */
         return -1;
     }
+    return 0;
 }
 
--- a/lib/lock_flock.c
+++ b/lib/lock_flock.c
@@ -47,6 +47,8 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <syslog.h>
+#include <signal.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -55,6 +57,36 @@
 
 const char *lock_method_desc = "flock";
 
+int lock_wait_time = LOCK_GIVEUP_TIMER_DEFAULT;
+
+/* Signal handling. We REQUIRE SYSV abort-syscall behaviour */
+
+static volatile int lock_gotsigalrm = 0;
+void lock_sigalrm_handler(int sig __attribute__((unused)))
+{
+    lock_gotsigalrm = 1;
+}
+
+static int setsigalrm(int enable)
+{
+    struct sigaction action;
+
+    sigemptyset(&action.sa_mask);
+    action.sa_flags = SA_RESETHAND;
+
+    if(enable) {
+	action.sa_handler = lock_sigalrm_handler;
+    } else {
+	action.sa_handler = SIG_IGN;
+    }
+    if (sigaction(SIGALRM, &action, NULL) < 0) {
+	syslog(LOG_ERR, "installing SIGALRM handler: sigaction: %m");
+	return -1;
+    }
+    lock_gotsigalrm = 0;
+    return 0;
+}
+
 /*
  * Block until we obtain an exclusive lock on the file descriptor 'fd',
  * opened for reading and writing on the file named 'filename'.  If
@@ -68,12 +100,10 @@ const char *lock_method_desc = "flock";
  * 'failaction' is provided, it is filled in with a pointer to a fixed
  * string naming the action that failed.
  *
+ * We use POSIX semanthics and alarm() to avoid deadlocks
+ *
  */
-int lock_reopen(fd, filename, sbuf, failaction)
-int fd;
-const char *filename;
-struct stat *sbuf;
-const char **failaction;
+int lock_reopen(int fd, const char *filename, struct stat *sbuf, const char **failaction)
 {
     int r;
     struct stat sbuffile, sbufspare;
@@ -81,11 +111,15 @@ const char **failaction;
 
     if (!sbuf) sbuf = &sbufspare;
 
+    setsigalrm(1);
+    alarm(lock_wait_time);
     for (;;) {
 	r = flock(fd, LOCK_EX);
 	if (r == -1) {
-	    if (errno == EINTR) continue;
+	    if (errno == EINTR && !lock_gotsigalrm) continue;
 	    if (failaction) *failaction = "locking";
+	    alarm(0);
+	    setsigalrm(0);
 	    return -1;
 	}
 
@@ -94,20 +128,31 @@ const char **failaction;
 	if (r == -1) {
 	    if (failaction) *failaction = "stating";
 	    flock(fd, LOCK_UN);
+	    alarm(0);
+	    setsigalrm(0);
 	    return -1;
 	}
 
-	if (sbuf->st_ino == sbuffile.st_ino) return 0;
+	if (sbuf->st_ino == sbuffile.st_ino) {
+	    alarm(0);
+	    setsigalrm(0);
+	    return 0;
+	}
 
 	newfd = open(filename, O_RDWR);
 	if (newfd == -1) {
 	    if (failaction) *failaction = "opening";
 	    flock(fd, LOCK_UN);
+	    alarm(0);
+	    setsigalrm(0);
 	    return -1;
 	}
 	dup2(newfd, fd);
 	close(newfd);
     }
+    alarm(0);
+    setsigalrm(0);
+    return 0;
 }
 
 /*
@@ -115,17 +160,27 @@ const char **failaction;
  * Returns 0 for success, -1 for failure, with errno set to an
  * appropriate error code.
  */
-int lock_blocking(fd)
-int fd;
+int lock_blocking(int fd)
 {
     int r;
 
+    setsigalrm(1);
+    alarm(lock_wait_time);
     for (;;) {
 	r = flock(fd, LOCK_EX);
-	if (r != -1) return 0;
-	if (errno == EINTR) continue;
+	if (r != -1) {
+	    alarm(0);
+	    setsigalrm(0);
+	    return 0;
+	}
+	if (errno == EINTR && !lock_gotsigalrm) continue;
+	alarm(0);
+	setsigalrm(0);
 	return -1;
     }
+    alarm(0);
+    setsigalrm(0);
+    return 0;
 }
 
 /*
@@ -133,17 +188,27 @@ int fd;
  * Returns 0 for success, -1 for failure, with errno set to an
  * appropriate error code.
  */
-int lock_shared(fd)
-int fd;
+int lock_shared(int fd)
 {
     int r;
 
+    setsigalrm(1);
+    alarm(lock_wait_time);
     for (;;) {
 	r = flock(fd, LOCK_SH);
-	if (r != -1) return 0;
-	if (errno == EINTR) continue;
+	if (r != -1) {
+	    alarm(0);
+	    setsigalrm(0);
+	    return 0;
+	}
+	if (errno == EINTR && !lock_gotsigalrm) continue;
+	alarm(0);
+	setsigalrm(0);
 	return -1;
     }
+    alarm(0);
+    setsigalrm(0);
+    return 0;
 }
 
 /*
@@ -165,7 +230,9 @@ int fd;
 }
 
 /*
- * Release any lock on 'fd'.  Always returns success.
+ * Release any lock on 'fd'.
+ * Returns 0 for success, -1 for failure, with errno set to an
+ * appropriate error code.
  */
 int lock_unlock(int fd)
 {
@@ -175,8 +242,8 @@ int lock_unlock(int fd)
         r = flock(fd, LOCK_UN);
         if (r != -1) return 0;
         if (errno == EINTR) continue;
-        /* xxx help! */
         return -1;
     }
+    return 0;
 }
 
--- a/lib/cyr_lock.h
+++ b/lib/cyr_lock.h
@@ -55,6 +55,8 @@
 
 #include <sys/stat.h>
 
+extern int lock_wait_time;
+
 extern const char *lock_method_desc;
 
 extern int lock_reopen P((int fd, const char *filename,
