/* ** Copyright 2006 Double Precision, Inc. See COPYING for ** distribution information. */ #include "config.h" #include "liblock.h" #include "mail.h" #include "../numlib/numlib.h" #include #include #include #include #include #include #include #include #if HAVE_FCNTL_H #include #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; }