diff options
Diffstat (limited to 'maildir/maildirwatch.c')
| -rw-r--r-- | maildir/maildirwatch.c | 602 | 
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 +} | 
