diff options
Diffstat (limited to 'liblock/mail.c')
| -rw-r--r-- | liblock/mail.c | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/liblock/mail.c b/liblock/mail.c new file mode 100644 index 0000000..82d380c --- /dev/null +++ b/liblock/mail.c @@ -0,0 +1,503 @@ +/* +** Copyright 2006 Double Precision, Inc. See COPYING for +** distribution information. +*/ + +#include "config.h" +#include "liblock.h" +#include "mail.h" +#include "../numlib/numlib.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif + + +struct ll_mail *ll_mail_alloc(const char *filename) +{ + struct ll_mail *p=(struct ll_mail *)malloc(sizeof(struct ll_mail)); + + if (!p) + return NULL; + + if ((p->file=strdup(filename)) == NULL) + { + free(p); + return NULL; + } + + p->cclientfd= -1; + p->cclientfile=NULL; + + p->dotlock=NULL; + + return p; +} + +#define IDBUFSIZE 512 + +/* +** For extra credit, we mark our territory. +*/ + +static void getid(char *idbuf) +{ + libmail_str_pid_t(getpid(), idbuf); + + while (*idbuf) + idbuf++; + + *idbuf++=':'; + + idbuf[IDBUFSIZE-NUMBUFSIZE-10]=0; + + if (gethostname(idbuf, IDBUFSIZE-NUMBUFSIZE-10) < 0) + strcpy(idbuf, "localhost"); +} + +static int writeid(char *idbuf, int fd) +{ + int l=strlen(idbuf); + + while (l) + { + int n=write(fd, idbuf, l); + + if (n <= 0) + return (-1); + + l -= n; + idbuf += n; + } + return 0; +} + +static int readid(char *p, int fd) +{ + int l=IDBUFSIZE-1; + + while (l) + { + int n=read(fd, p, l); + + if (n < 0) + return (-1); + + if (n == 0) + break; + + p += n; + l -= n; + } + *p=0; + return 0; +} + +static pid_t getpidid(char *idbuf, char *myidbuf) +{ + pid_t p=atol(idbuf); + + if ((idbuf=strchr(idbuf, ':')) == NULL || + (myidbuf=strchr(myidbuf, ':')) == NULL || + strcmp(idbuf, myidbuf)) + return 0; + + return p; +} + +int ll_mail_lock(struct ll_mail *p) +{ + struct stat stat_buf; + char idbuf[IDBUFSIZE]; + char idbuf2[IDBUFSIZE]; + + char fn[NUMBUFSIZE*2 + 20]; + char *f; + int fd; + + getid(idbuf); + + if (p->cclientfd >= 0) + return 0; + + if (stat(p->file, &stat_buf) < 0) + return -1; + + if (snprintf(fn, sizeof(fn), "/tmp/.%lx.%lx", + (unsigned long)stat_buf.st_dev, + (unsigned long)stat_buf.st_ino) < 0) + { + errno=ENOSPC; + return (-1); + } + + if ((f=strdup(fn)) == NULL) + return (-1); + + /* We do things a bit differently. First, try O_EXCL */ + + if ((fd=open(f, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0) + { + struct stat stat_buf2; + + if (ll_lockfd(fd, ll_writelock, ll_whence_start, 0) < 0 || + fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 || + writeid(idbuf, fd) < 0) + { + /* This shouldn't happen */ + + close(fd); + free(f); + return (-1); + } + + /* Rare race condition: */ + + if (fstat(fd, &stat_buf) < 0 || + lstat(f, &stat_buf2) < 0 || + stat_buf.st_dev != stat_buf2.st_dev || + stat_buf.st_ino != stat_buf2.st_ino) + { + errno=EAGAIN; + close(fd); + free(f); + return (-1); + } + + p->cclientfd=fd; + p->cclientfile=f; + return 0; + } + + /* + ** An existing lockfile. See if it's tagged with another + ** pid on this server, which no longer exists. + */ + + if ((fd=open(f, O_RDONLY)) >= 0) + { + pid_t p=-1; + + if (readid(idbuf2, fd) == 0 && + (p=getpidid(idbuf2, idbuf)) != 0 && + kill(p, 0) < 0 && errno == ESRCH) + { + errno=EAGAIN; + close(fd); + unlink(f); /* Don't try again right away */ + free(f); + return (-1); + } + + /* If we can't lock, someone must have it open, game over. */ + + if (p == getpid() /* It's us! */ + + || ll_lockfd(fd, ll_readlock, ll_whence_start, 0) < 0) + { + errno=EEXIST; + close(fd); + free(f); + return (-1); + } + + close(fd); + } + + /* Stale 0-length lockfiles are blown away after 5 mins */ + + if (lstat(f, &stat_buf) == 0 && stat_buf.st_size == 0 && + stat_buf.st_mtime + 300 < time(NULL)) + { + errno=EAGAIN; + unlink(f); + free(f); + return (-1); + } + + errno=EAGAIN; + free(f); + return (-1); +} + +/* Try to create a dot-lock */ + +static int try_dotlock(const char *tmpfile, + const char *dotlock, + char *idbuf); + +static int try_mail_dotlock(const char *dotlock, char *idbuf) +{ + char timebuf[NUMBUFSIZE]; + char pidbuf[NUMBUFSIZE]; + char *tmpname; + int rc; + + libmail_str_time_t(time(NULL), timebuf); + libmail_str_pid_t(getpid(), pidbuf); + + tmpname=malloc(strlen(dotlock) + strlen(timebuf) + strlen(pidbuf) + + strlen(idbuf) + 10); + + if (!tmpname) + return -1; + + strcpy(tmpname, dotlock); + strcat(tmpname, "."); + strcat(tmpname, timebuf); + strcat(tmpname, "."); + strcat(tmpname, pidbuf); + strcat(tmpname, "."); + strcat(tmpname, strchr(idbuf, ':')+1); + + rc=try_dotlock(tmpname, dotlock, idbuf); + free(tmpname); + return (rc); +} + +static int try_dotlock(const char *tmpname, + const char *dotlock, + char *idbuf) +{ + struct stat stat_buf; + + int fd; + + fd=open(tmpname, O_RDWR | O_CREAT, 0644); + + if (fd < 0) + return (-1); + + if (writeid(idbuf, fd)) + { + close(fd); + unlink(tmpname); + return (-1); + } + close(fd); + + if (link(tmpname, dotlock) < 0 || stat(tmpname, &stat_buf) || + stat_buf.st_nlink != 2) + { + if (errno != EEXIST) + errno=EIO; + + unlink(tmpname); + return (-1); + } + unlink(tmpname); + return (0); +} + +static void dotlock_exists(const char *dotlock, char *myidbuf, + int timeout) +{ + char idbuf[IDBUFSIZE]; + struct stat stat_buf; + int fd; + + if ((fd=open(dotlock, O_RDONLY)) >= 0) + { + pid_t p; + + /* + ** Where the locking process is on the same server, + ** the decision is easy: does the process still exist, + ** or not? + */ + + if (readid(idbuf, fd) == 0 && (p=getpidid(idbuf, myidbuf))) + { + if (p == getpid() /* Possibly recycled PID */ + || (kill(p, 0) < 0 && errno == ESRCH)) + { + close(fd); + if (unlink(dotlock) == 0) + errno=EAGAIN; + else + errno=EEXIST; + return; + } + } + else if (timeout > 0 && fstat(fd, &stat_buf) >= 0 && + stat_buf.st_mtime < time(NULL) - timeout) + { + close(fd); + + if (unlink(dotlock) == 0) + errno=EAGAIN; + else + errno=EEXIST; + return; + } + + close(fd); + } + + errno=EEXIST; +} + +static int ll_mail_open_do(struct ll_mail *p, int ro) +{ + char *dotlock; + char myidbuf[IDBUFSIZE]; + int save_errno; + int fd; + + getid(myidbuf); + + if (p->dotlock) /* Already locked */ + { + fd=open(p->file, ro ? O_RDONLY:O_RDWR); + + if (fd >= 0 && + (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) < 0 || + fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)) + { + close(fd); + fd= -1; + } + return fd; + } + + if ((dotlock=malloc(strlen(p->file)+sizeof(".lock"))) == NULL) + return -1; + + strcat(strcpy(dotlock, p->file), ".lock"); + + if (try_mail_dotlock(dotlock, myidbuf) == 0) + { + fd=open(p->file, ro ? O_RDONLY:O_RDWR); + + if (fd >= 0 && + (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) || + fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)) + { + close(fd); + fd= -1; + } + + p->dotlock=dotlock; + return fd; + } + + save_errno=errno; + + /* + ** Last fallback: for EEXIST, a read-only lock should suffice + ** In all other instances, we'll fallback to read/write or read-only + ** flock as last resort. + */ + + if ((errno == EEXIST && ro) || errno == EPERM || errno == EACCES) + { + fd=open(p->file, ro ? O_RDONLY:O_RDWR); + + if (fd >= 0) + { + if (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, + 0, 0) == 0 && + fcntl(fd, F_SETFD, FD_CLOEXEC) == 0) + { + free(dotlock); + return fd; + } + close(fd); + } + } + + /* + ** If try_dotlock blew up for anything other than EEXIST, we don't + ** know what the deal is, so punt. + */ + + if (save_errno != EEXIST) + { + free(dotlock); + return (-1); + } + + dotlock_exists(dotlock, myidbuf, 300); + free(dotlock); + return (-1); +} + +int ll_mail_open_ro(struct ll_mail *p) +{ + return ll_mail_open_do(p, 1); +} + +int ll_mail_open(struct ll_mail *p) +{ + return ll_mail_open_do(p, 0); +} + +void ll_mail_free(struct ll_mail *p) +{ + char myid[IDBUFSIZE]; + char idbuf[IDBUFSIZE]; + + getid(myid); + + if (p->cclientfd >= 0) + { + if (lseek(p->cclientfd, 0L, SEEK_SET) == 0 && + readid(idbuf, p->cclientfd) == 0 && + strcmp(myid, idbuf) == 0) + { + if (ftruncate(p->cclientfd, 0) >= 0) + unlink(p->cclientfile); + } + close(p->cclientfd); + free(p->cclientfile); + } + + if (p->dotlock) + { + int fd=open(p->dotlock, O_RDONLY); + + if (fd >= 0) + { + if (readid(idbuf, fd) == 0 && + strcmp(myid, idbuf) == 0) + { + close(fd); + unlink(p->dotlock); + free(p->dotlock); + free(p->file); + free(p); + return; + } + close(fd); + } + + free(p->dotlock); + } + free(p->file); + free(p); +} + +int ll_dotlock(const char *dotlock, const char *tmpfile, + int timeout) +{ + char myidbuf[IDBUFSIZE]; + + getid(myidbuf); + + if (try_dotlock(tmpfile, dotlock, myidbuf)) + { + if (errno == EEXIST) + dotlock_exists(dotlock, myidbuf, timeout); + return -1; + } + return 0; +} + + |
