summaryrefslogtreecommitdiffstats
path: root/maildir/maildirwatch.c
diff options
context:
space:
mode:
Diffstat (limited to 'maildir/maildirwatch.c')
-rw-r--r--maildir/maildirwatch.c602
1 files changed, 602 insertions, 0 deletions
diff --git a/maildir/maildirwatch.c b/maildir/maildirwatch.c
new file mode 100644
index 0000000..cc73782
--- /dev/null
+++ b/maildir/maildirwatch.c
@@ -0,0 +1,602 @@
+/*
+** Copyright 2002-2009 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#include "config.h"
+#include "maildirwatch.h"
+
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+
+#if HAVE_FAM
+static struct maildirwatch_fam *maildirwatch_currentfam;
+#endif
+
+struct maildirwatch *maildirwatch_alloc(const char *maildir)
+{
+ char wd[PATH_MAX];
+ struct maildirwatch *w;
+
+ if (maildir == 0 || *maildir == 0)
+ maildir=".";
+
+ if (getcwd(wd, sizeof(wd)-1) == NULL)
+ return NULL;
+
+ if (*maildir == '/')
+ wd[0]=0;
+ else
+ strcat(wd, "/");
+
+ if ((w=malloc(sizeof(struct maildirwatch))) == NULL)
+ return NULL;
+
+ if ((w->maildir=malloc(strlen(wd)+strlen(maildir)+1)) == NULL)
+ {
+ free(w);
+ return NULL;
+ }
+
+ strcat(strcpy(w->maildir, wd), maildir);
+
+#if HAVE_FAM
+ if (!maildirwatch_currentfam)
+ {
+ if ((maildirwatch_currentfam
+ =malloc(sizeof(*maildirwatch_currentfam))) != NULL)
+ {
+ maildirwatch_currentfam->broken=0;
+ maildirwatch_currentfam->refcnt=0;
+
+ alarm(15);
+ if (FAMOpen(&maildirwatch_currentfam->fc) < 0)
+ {
+ errno=EIO;
+ free(maildirwatch_currentfam);
+ maildirwatch_currentfam=NULL;
+ }
+ alarm(0);
+ }
+ }
+
+ if (!maildirwatch_currentfam)
+ {
+ free(w->maildir);
+ free(w);
+ w=NULL;
+ }
+ else
+ {
+ w->fam=maildirwatch_currentfam;
+ ++w->fam->refcnt;
+ }
+#endif
+ return w;
+}
+
+void maildirwatch_free(struct maildirwatch *w)
+{
+#if HAVE_FAM
+ if (--w->fam->refcnt == 0)
+ {
+ w->fam->broken=1;
+ if (maildirwatch_currentfam &&
+ maildirwatch_currentfam->broken)
+ {
+ /*
+ ** Last reference to the current FAM connection,
+ ** keep it active.
+ */
+
+ w->fam->broken=0;
+ }
+ else /* Some other connection, with no more refs */
+ {
+ FAMClose(&w->fam->fc);
+ free(w->fam);
+ }
+ }
+#endif
+
+ free(w->maildir);
+ free(w);
+}
+
+void maildirwatch_cleanup()
+{
+#if HAVE_FAM
+
+ if (maildirwatch_currentfam && maildirwatch_currentfam->refcnt == 0)
+ {
+ FAMClose(&maildirwatch_currentfam->fc);
+ free(maildirwatch_currentfam);
+ maildirwatch_currentfam=NULL;
+ }
+#endif
+}
+
+#if HAVE_FAM
+static void maildirwatch_fambroken(struct maildirwatch *w)
+{
+ w->fam->broken=1;
+
+ if (maildirwatch_currentfam && maildirwatch_currentfam->broken)
+ maildirwatch_currentfam=NULL;
+ /* Broke the current connection, create another one, next time. */
+
+}
+
+/*
+** If the current connection is marked as broken, try to reconnect.
+*/
+
+static void maildirwatch_famunbreak(struct maildirwatch *w)
+{
+ struct maildirwatch *cpy;
+
+ if (!w->fam->broken)
+ return;
+
+ if ((cpy=maildirwatch_alloc(w->maildir)) == NULL)
+ return;
+
+ /*
+ ** maildirwatch_alloc succeeds only with a good connection.
+ ** If this is the last reference to the broken connection, close it.
+ */
+
+ if (--w->fam->refcnt == 0)
+ {
+ FAMClose(&w->fam->fc);
+ free(w->fam);
+ }
+
+ w->fam=cpy->fam;
+ ++w->fam->refcnt;
+
+ maildirwatch_free(cpy);
+}
+
+static int waitEvent(struct maildirwatch *w)
+{
+ int fd;
+ fd_set r;
+ struct timeval tv;
+ time_t now2;
+
+ int rc;
+
+ while ((rc=FAMPending(&w->fam->fc)) == 0)
+ {
+ if (w->now >= w->timeout)
+ return 0;
+
+ fd=FAMCONNECTION_GETFD(&w->fam->fc);
+
+ FD_ZERO(&r);
+ FD_SET(fd, &r);
+
+ tv.tv_sec= w->timeout - w->now;
+ tv.tv_usec=0;
+
+ select(fd+1, &r, NULL, NULL, &tv);
+ now2=time(NULL);
+
+ if (now2 < w->now)
+ return 0; /* System clock changed */
+
+ w->now=now2;
+ }
+
+ return rc;
+}
+#endif
+
+
+int maildirwatch_unlock(struct maildirwatch *w, int nseconds)
+{
+#if HAVE_FAM
+ FAMRequest fr;
+ FAMEvent fe;
+ int cancelled=0;
+ char *p;
+
+ if (w->fam->broken)
+ {
+ errno=EIO;
+ return -1;
+ }
+
+ p=malloc(strlen(w->maildir)+ sizeof("/" WATCHDOTLOCK));
+
+ if (!p)
+ return -1;
+
+ strcat(strcpy(p, w->maildir), "/" WATCHDOTLOCK);
+
+ errno=EIO;
+ if (FAMMonitorFile(&w->fam->fc, p, &fr, NULL) < 0)
+ {
+ free(p);
+ fprintf(stderr, "ERR:FAMMonitorFile: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ free(p);
+
+ if (nseconds < 0)
+ nseconds=0;
+
+ time(&w->now);
+
+ w->timeout=w->now + nseconds;
+
+ for (;;)
+ {
+ if (waitEvent(w) != 1)
+ {
+ errno=EIO;
+
+ if (!cancelled && FAMCancelMonitor(&w->fam->fc, &fr) == 0)
+ {
+ w->timeout=w->now+15;
+ cancelled=1;
+ continue;
+ }
+
+ if (!cancelled)
+ fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
+ strerror(errno));
+
+ maildirwatch_fambroken(w);
+ break;
+ }
+
+ errno=EIO;
+
+ if (FAMNextEvent(&w->fam->fc, &fe) != 1)
+ {
+ fprintf(stderr, "ERR:FAMNextEvent: %s\n",
+ strerror(errno));
+ maildirwatch_fambroken(w);
+ break;
+ }
+
+ if (fe.fr.reqnum != fr.reqnum)
+ continue;
+
+ if (fe.code == FAMDeleted && !cancelled)
+ {
+ errno=EIO;
+ if (FAMCancelMonitor(&w->fam->fc, &fr) == 0)
+ {
+ w->timeout=w->now+15;
+ cancelled=1;
+ continue;
+ }
+ fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
+ strerror(errno));
+ maildirwatch_fambroken(w);
+ break;
+ }
+
+ if (fe.code == FAMAcknowledge)
+ break;
+ }
+
+ if (w->fam->broken)
+ return -1;
+
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+#define DIRCNT 3
+
+int maildirwatch_start(struct maildirwatch *w,
+ struct maildirwatch_contents *mc)
+{
+ mc->w=w;
+
+ time(&w->now);
+ w->timeout = w->now + 60;
+
+#if HAVE_FAM
+
+ maildirwatch_famunbreak(w);
+
+ if (w->fam->broken)
+ {
+ errno=EIO;
+ return (1);
+ }
+
+ {
+ char *s=malloc(strlen(w->maildir)
+ +sizeof("/" KEYWORDDIR));
+
+ if (!s)
+ return (-1);
+
+ strcat(strcpy(s, w->maildir), "/new");
+
+ mc->endexists_received=0;
+ mc->ack_received=0;
+ mc->cancelled=0;
+
+ errno=EIO;
+
+ if (FAMMonitorDirectory(&w->fam->fc, s, &mc->new_req, NULL) < 0)
+ {
+ fprintf(stderr, "ERR:"
+ "FAMMonitorDirectory(%s) failed: %s\n",
+ s, strerror(errno));
+ free(s);
+ errno=EIO;
+ return (-1);
+ }
+
+ strcat(strcpy(s, w->maildir), "/cur");
+ errno=EIO;
+
+ if (FAMMonitorDirectory(&w->fam->fc, s, &mc->cur_req, NULL) < 0)
+ {
+ fprintf(stderr, "ERR:"
+ "FAMMonitorDirectory(%s) failed: %s\n",
+ s, strerror(errno));
+
+ errno=EIO;
+
+ if (FAMCancelMonitor(&mc->w->fam->fc, &mc->new_req) < 0)
+ {
+ free(s);
+ maildirwatch_fambroken(w);
+ fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
+ strerror(errno));
+ errno=EIO;
+ return (-1);
+ }
+ mc->cancelled=1;
+ mc->ack_received=2;
+ }
+
+ strcat(strcpy(s, w->maildir), "/" KEYWORDDIR);
+ errno=EIO;
+
+ if (FAMMonitorDirectory(&w->fam->fc, s,
+ &mc->courierimapkeywords_req, NULL)<0)
+ {
+ fprintf(stderr, "ERR:"
+ "FAMMonitorDirectory(%s) failed: %s\n",
+ s, strerror(errno));
+
+ errno=EIO;
+
+ if (FAMCancelMonitor(&mc->w->fam->fc, &mc->new_req)<0)
+ {
+ free(s);
+ maildirwatch_fambroken(w);
+ fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
+ strerror(errno));
+ errno=EIO;
+ return (-1);
+ }
+
+ errno=EIO;
+
+ if (FAMCancelMonitor(&mc->w->fam->fc, &mc->cur_req)<0)
+ {
+ free(s);
+ maildirwatch_fambroken(w);
+ fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
+ strerror(errno));
+ errno=EIO;
+ return (-1);
+ }
+
+ mc->cancelled=1;
+ mc->ack_received=1;
+ }
+
+ free(s);
+ }
+ return 0;
+#else
+ return 1;
+#endif
+}
+
+#define CANCEL(ww) \
+ errno=EIO; if (FAMCancelMonitor(&w->fam->fc, \
+ &ww->new_req) || \
+ FAMCancelMonitor(&w->fam->fc, \
+ &ww->cur_req) || \
+ FAMCancelMonitor(&w->fam->fc, \
+ &ww->courierimapkeywords_req)) \
+ {\
+ maildirwatch_fambroken(w); \
+ fprintf(stderr, \
+ "ERR:FAMCancelMonitor: %s\n", \
+ strerror(errno)); \
+ return (-1); \
+ }
+
+int maildirwatch_started(struct maildirwatch_contents *mc,
+ int *fdret)
+{
+#if HAVE_FAM
+ struct maildirwatch *w=mc->w;
+
+ if (w->fam->broken)
+ return (1);
+
+ *fdret=FAMCONNECTION_GETFD(&w->fam->fc);
+
+ while (FAMPending(&w->fam->fc))
+ {
+ FAMEvent fe;
+
+ errno=EIO;
+
+ if (FAMNextEvent(&w->fam->fc, &fe) != 1)
+ {
+ fprintf(stderr, "ERR:FAMNextEvent: %s\n",
+ strerror(errno));
+ maildirwatch_fambroken(w);
+ return (-1);
+ }
+
+ switch (fe.code) {
+ case FAMDeleted:
+ if (!mc->cancelled)
+ {
+ mc->cancelled=1;
+ CANCEL(mc);
+ }
+ break;
+ case FAMAcknowledge:
+ if (++mc->ack_received >= DIRCNT)
+ return -1;
+ break;
+ case FAMEndExist:
+ ++mc->endexists_received;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (mc->endexists_received >= DIRCNT && mc->ack_received == 0);
+#else
+ *fdret= -1;
+
+ return 1;
+#endif
+}
+
+int maildirwatch_check(struct maildirwatch_contents *mc,
+ int *changed,
+ int *fdret,
+ int *timeout)
+{
+ struct maildirwatch *w=mc->w;
+ time_t curTime;
+
+ *changed=0;
+ *fdret=-1;
+
+ curTime=time(NULL);
+
+ if (curTime < w->now)
+ w->timeout=curTime; /* System clock changed */
+ w->now=curTime;
+
+#if HAVE_FAM
+
+ if (!w->fam->broken)
+ {
+ *fdret=FAMCONNECTION_GETFD(&w->fam->fc);
+
+ while (FAMPending(&w->fam->fc))
+ {
+ FAMEvent fe;
+
+ errno=EIO;
+
+ if (FAMNextEvent(&w->fam->fc, &fe) != 1)
+ {
+ fprintf(stderr, "ERR:FAMNextEvent: %s\n",
+ strerror(errno));
+ maildirwatch_fambroken(w);
+ return (-1);
+ }
+
+ switch (fe.code) {
+ case FAMDeleted:
+ case FAMCreated:
+ case FAMMoved:
+ if (!mc->cancelled)
+ {
+ mc->cancelled=1;
+ CANCEL(mc);
+ }
+ break;
+ case FAMAcknowledge:
+ ++mc->ack_received;
+ default:
+ break;
+ }
+ }
+
+ *changed=mc->ack_received >= DIRCNT;
+ *timeout=60 * 60;
+ return 0;
+ }
+#endif
+ *timeout=60;
+
+ if ( (*changed= w->now >= w->timeout) != 0)
+ w->timeout = w->now + 60;
+ return 0;
+}
+
+void maildirwatch_end(struct maildirwatch_contents *mc)
+{
+#if HAVE_FAM
+ struct maildirwatch *w=mc->w;
+
+ if (!w->fam->broken)
+ {
+ if (!mc->cancelled)
+ {
+ mc->cancelled=1;
+
+#define return(x)
+ CANCEL(mc);
+#undef return
+ }
+ }
+
+ while (!w->fam->broken && mc->ack_received < DIRCNT)
+ {
+ FAMEvent fe;
+
+ time(&w->now);
+ w->timeout=w->now + 15;
+
+ errno=EIO;
+
+ if (waitEvent(w) != 1)
+ {
+ fprintf(stderr, "ERR:FAMPending: timeout\n");
+ maildirwatch_fambroken(w);
+ break;
+ }
+
+ errno=EIO;
+
+ if (FAMNextEvent(&w->fam->fc, &fe) != 1)
+ {
+ fprintf(stderr, "ERR:FAMNextEvent: %s\n",
+ strerror(errno));
+ maildirwatch_fambroken(w);
+ break;
+ }
+
+ if (fe.code == FAMAcknowledge)
+ ++mc->ack_received;
+ }
+#endif
+}