/*
* locks.c:
* Various means of locking BSD mailspools.
*
* Some or all of fcntl, flock and .lock locking are done, along with a rather
* comedy attempt at cclient locking, which is only there so that PINE figures
* out when the user is attempting to pick up her mail using POP3 in the
* middle of a PINE session. cclient locks aren't made, just stolen from PINE
* using the wacky `Kiss Of Death' described in the cclient documentation.
*
* Note also that we lock the whole mailspool for reading and writing. This is
* pretty crap, but it makes it easier to make the program fast. In principle,
* we could just lock the existing section of the file, so that the MTA could
* deliver new messages on to the end of it, and then stat it when we were
* about to apply changes in the UPDATE state, to see whether it had grown.
*
* Copyright (c) 2001 Chris Lightfoot. All rights reserved.
*
*/
static const char rcsid[] = "$Id: locks.c,v 1.10 2003/01/24 11:31:24 chris Exp $";
#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */
#ifdef MBOX_BSD
#include "locks.h"
#include <errno.h>
#include <fcntl.h>
#ifdef WITH_CCLIENT_LOCKING
# include <signal.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include "util.h"
#ifdef WITH_FCNTL_LOCKING
/* fcntl_lock:
* Attempt to lock a file using fcntl(2) locking. Returns 0 or success, or -1
* on error. */
int fcntl_lock(int fd) {
struct flock fl = {0};
/* Set up flock structure to lock entire file. */
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
return fcntl(fd, F_SETLK, &fl);
}
/* fcntl_unlock:
* Attempt to unlock a file using fcntl(2) locking. Returns 0 on success, or
* -1 on error. */
int fcntl_unlock(int fd) {
struct flock fl = {0};
/* Set up flock structure to unlock entire file. */
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
return fcntl(fd, F_SETLK, &fl);
}
#endif /* WITH_FCNTL_LOCKING */
#if defined(WITH_FLOCK_LOCKING) || (defined(WITH_CCLIENT_LOCKING) && !defined(CCLIENT_USES_FCNTL))
/* flock_lock:
* Attempt to lock a file using flock(2) locking. Returns 0 on success, or -1
* on failure. */
int flock_lock(int fd) {
return flock(fd, LOCK_EX | LOCK_NB);
}
/* flock_unlock:
* Attempt to unlock a file using flock(2) locking. Returns 0 on success or -1
* on failure. */
int flock_unlock(int fd) {
return flock(fd, LOCK_UN);
}
#endif /* WITH_FLOCK_LOCKING */
#ifdef WITH_DOTFILE_LOCKING
/* dotfile_check_stale:
* If the named lockfile exists, then check whether the PID inside it is one
* for an extant process. If it is not, remove it. */
static void dotfile_check_stale(const char *file) {
int fd;
char buf[16] = {0};
fd = open(file, O_RDONLY);
if (fd == -1) return;
if (read(fd, buf, sizeof(buf) - 1) > 0) {
int i;
i = strspn(buf, "0123456789");
if (i > 0 && buf[i] == '\n') {
pid_t p;
/* Have a valid PID, possibly. */
buf[i] = 0;
p = (pid_t)atoi(buf);
if (p > 1 && kill(p, 0) == -1 && errno == ESRCH
&& unlink(file) == 0)
/* File exists but process doesn't. */
log_print(LOG_INFO, _("dotfile_check_stale: removed stale lockfile `%s' (pid was %d)"), file, (int)p);
}
}
close(fd);
}
/* dotfile_lock:
* Attempt to lock a file by constructing a lockfile having the name of the
* file with ".lock" appended. Returns 0 on success or -1 on failure. */
int dotfile_lock(const char *name) {
char *lockfile = xmalloc(strlen(name) + 6), *hitchfile = NULL;
char pidstr[16];
struct utsname uts;
int fd = -1, rc, r = -1;
struct stat st;
sprintf(pidstr, "%d\n", (int)getpid());
/* Make name for lockfile. */
if (!lockfile) goto fail;
sprintf(lockfile, "%s.lock", name);
dotfile_check_stale(lockfile);
/* Make a name for a hitching-post file. */
if (uname(&uts) == -1) goto fail;
hitchfile = xmalloc(strlen(name) + strlen(uts.nodename) + 24);
if (!hitchfile) goto fail;
sprintf(hitchfile, "%s.%ld.%ld.%s", name, (long)getpid(), (long)time(NULL), uts.nodename);
fd = open(hitchfile, O_EXCL | O_CREAT | O_WRONLY, 0440);
if (fd == -1) {
log_print(LOG_ERR, _("dotfile_lock(%s): unable to create hitching post: %m"), name);
goto fail;
}
if (xwrite(fd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
log_print(LOG_ERR, _("dotfile_lock(%s): unable to write PID to hitching post: %m"), name);
goto fail;
}
/* Attempt to link the hitching post to the lockfile. */
if ((rc = link(hitchfile, lockfile)) != 0) fstat(fd, &st);
close(fd);
fd = -1;
unlink(hitchfile);
/* Were we able to link the hitching post to the lockfile, and if we were,
* did it have exactly 2 links when we were done? */
if (rc != 0 && st.st_nlink != 2) {
log_print(LOG_ERR, _("dotfile_lock(%s): unable to link hitching post to lock file: %m"), name);
goto fail;
}
/* Success. */
r = 0;
fail:
if (lockfile) xfree(lockfile);
if (hitchfile) xfree(hitchfile);
if (fd != -1) close(fd);
return r;
}
/* dotfile_unlock:
* Unlock a file which has been locked using dotfile locking. Returns 0 on
* success or -1 on failure.
*
* XXX We try to check that this is _our_ lockfile. Is this correct? */
int dotfile_unlock(const char *name) {
char pidstr[16], pidstr2[16] = {0};
char *lockfile = xmalloc(strlen(name) + 6);
int fd = -1, r = -1;
sprintf(pidstr, "%d\n", (int)getpid());
if (!lockfile) goto fail;
sprintf(lockfile, "%s.lock", name);
/* Try to open the lockfile. */
fd = open(lockfile, O_RDONLY);
if (fd == -1) {
log_print(LOG_ERR, "dotfile_unlock(%s): open: %m", name);
goto fail;
}
if (read(fd, pidstr2, strlen(pidstr)) != strlen(pidstr)) {
log_print(LOG_ERR, "dotfile_unlock(%s): read: %m", name);
goto fail;
}
/* XXX is this correct? */
if (strncmp(pidstr, pidstr2, strlen(pidstr)) != 0) {
log_print(LOG_ERR, _("dotfile_unlock(%s): lockfile does not have our PID"), name);
goto fail;
}
if (unlink(lockfile) == -1) {
log_print(LOG_ERR, "dotfile_unlock(%s): unlink: %m", name);
goto fail;
}
/* Success. */
r = 0;
fail:
if (lockfile) xfree(lockfile);
if (fd != -1) close(fd);
return r;
}
#endif /* WITH_DOTFILE_LOCKING */
#ifdef WITH_CCLIENT_LOCKING
/* cclient_steal_lock:
* Attempt to steal a c-client lock (if any) applied to the file. Returns 0 on
* success, or -1 on failure. This is fairly comedy, but it is good enough to
* get PINE to get out of the way when necessary. */
int cclient_steal_lock(int fd) {
struct stat st;
char cclient_lockfile[64], other_pid[128] = {0};
int fd_cc = -1, r = -1;
pid_t p;
if (fstat(fd, &st) == -1) return -1;
sprintf(cclient_lockfile, "/tmp/.%lx.%lx", (unsigned long)st.st_dev, (unsigned long)st.st_ino);
/* Although we never write to the lockfile, we need to open it RDWR since
* we _may_ flock it in LOCK_EX mode.
*
* XXX exim lstats the /tmp/... file to ensure that it is not a symbolic
* link. Since we don't actually write to the file, it is probably not
* necessary to make this check. */
fd_cc = open(cclient_lockfile, O_RDWR);
if (fd_cc == -1) {
if (errno == ENOENT) /* File did not exist; this is OK. */
r = 0;
else
log_print(LOG_ERR, "cclient_steal_lock: open: %m");
goto fail;
}
/* On most systems, the c-client library uses flock(2) to lock files. Some
* systems do not have flock(2) (Solaris <cough>), or patch PINE to use
* fcntl(2) locking (RedHat <cough>). */
#ifdef CCLIENT_USES_FCNTL
if (fcntl_lock(fd_cc) == -1) {
#else
if (flock_lock(fd_cc) == -1) {
#endif /* CCLIENT_USES_FLOCK */
if (read(fd_cc, other_pid, sizeof(other_pid) - 1) == -1) {
log_print(LOG_ERR, "cclient_steal_lock: read: %m");
goto fail;
}
p = (pid_t)atoi(other_pid);
if (p) {
log_print(LOG_DEBUG, _("cclient_steal_lock: attempting to grab c-client lock from PID %d"), (int)p);
kill(p, SIGUSR2);
}
sleep(2); /* Give PINE a moment to sort itself out. */
/* Have another go. */
#ifdef CCLIENT_USES_FCNTL
if (fcntl_lock(fd_cc) == -1)
#else
if (flock_lock(fd_cc) == -1)
#endif /* CCLIENT_USES_FLOCK */
/* No good. */
log_print(LOG_ERR, _("cclient_steal_lock: failed to grab c-client lock from PID %d"), (int)p);
else {
/* It worked; unlink and close the c-client lockfile. */
unlink(cclient_lockfile);
r = 0;
}
} else {
/* Managed to lock the file OK. */
unlink(cclient_lockfile);
r = 0;
}
fail:
if (fd_cc != -1) close(fd_cc);
return r;
}
#endif /* WITH_CCLIENT_LOCKING */
#endif /* MBOX_BSD */
syntax highlighted by Code2HTML, v. 0.9.1