diff options
Diffstat (limited to 'maildir/maildirwatch.c')
| -rw-r--r-- | maildir/maildirwatch.c | 672 | 
1 files changed, 293 insertions, 379 deletions
| diff --git a/maildir/maildirwatch.c b/maildir/maildirwatch.c index 36b85ab..4faac13 100644 --- a/maildir/maildirwatch.c +++ b/maildir/maildirwatch.c @@ -1,5 +1,5 @@  /* -** Copyright 2002-2009 Double Precision, Inc. +** Copyright 2002-2021 Double Precision, Inc.  ** See COPYING for distribution information.  */ @@ -14,25 +14,16 @@  #include <errno.h>  #include <signal.h>  #include <sys/signal.h> +#if HAVE_INOTIFY_INIT +#include <sys/inotify.h> +#include <poll.h> +#include <fcntl.h> +#endif  #ifndef PATH_MAX  #define PATH_MAX 4096  #endif - -#if HAVE_FAM -static struct maildirwatch_fam *maildirwatch_currentfam; - -static void alarm_handler(int signum) -{ -	static const char msg[]= -		"Timeout initializing the FAM library. Your FAM library is broken.\n"; - -	(void)write(2, msg, sizeof(msg)-1); -	kill(getpid(), SIGKILL); -} -#endif -  struct maildirwatch *maildirwatch_alloc(const char *maildir)  {  	char wd[PATH_MAX]; @@ -60,38 +51,36 @@ struct maildirwatch *maildirwatch_alloc(const char *maildir)  	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; +#if HAVE_INOTIFY_INIT +#if HAVE_INOTIFY_INIT1 +#ifdef IN_CLOEXEC +#else +#undef HAVE_INOTIFY_INIT1 +#endif +#ifdef IN_NONBLOCK +#else +#undef HAVE_INOTIFY_INIT1 +#endif +#endif -			signal(SIGALRM, alarm_handler); -			alarm(15); -			if (FAMOpen(&maildirwatch_currentfam->fc) < 0) -			{ -				errno=EIO; -				free(maildirwatch_currentfam); -				maildirwatch_currentfam=NULL; -			} -			alarm(0); -			signal(SIGALRM, SIG_DFL); -		} -	} +#if HAVE_INOTIFY_INIT1 +	w->inotify_fd=inotify_init1(IN_CLOEXEC | IN_NONBLOCK); +#else +	w->inotify_fd=inotify_init(); -	if (!maildirwatch_currentfam) +	if (w->inotify_fd >= 0 && +	    (fcntl(w->inotify_fd, F_SETFL, O_NONBLOCK) < 0 || +	     fcntl(w->inotify_fd, F_SETFD, FD_CLOEXEC)))  	{ -		free(w->maildir); -		free(w); -		w=NULL; +		close(w->inotify_fd); +		w->inotify_fd=-1;  	} -	else +#endif + +	if (w->inotify_fd < 0)  	{ -		w->fam=maildirwatch_currentfam; -		++w->fam->refcnt; +		maildirwatch_free(w); +		w=NULL;  	}  #endif  	return w; @@ -99,25 +88,10 @@ struct maildirwatch *maildirwatch_alloc(const char *maildir)  void maildirwatch_free(struct maildirwatch *w)  { -#if HAVE_FAM -	if (--w->fam->refcnt == 0) +#if HAVE_INOTIFY_INIT +	if (w->inotify_fd >= 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); -		} +		close(w->inotify_fd);  	}  #endif @@ -127,125 +101,128 @@ void maildirwatch_free(struct maildirwatch *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 HAVE_INOTIFY_INIT  /* -** If the current connection is marked as broken, try to reconnect. +** Poll the inotify file descriptor. Returns 0 on timeout, non-0 if +** the inotify file descriptor becomes ready before the timeout expires.  */ -static void maildirwatch_famunbreak(struct maildirwatch *w) +static int poll_inotify(struct maildirwatch *w)  { -	struct maildirwatch *cpy; - -	if (!w->fam->broken) -		return; +	time_t now2; -	if ((cpy=maildirwatch_alloc(w->maildir)) == NULL) -		return; +	int rc; -	/* -	** maildirwatch_alloc succeeds only with a good connection. -	** If this is the last reference to the broken connection, close it. -	*/ +	struct pollfd pfd; -	if (--w->fam->refcnt == 0) +	while (w->now < w->timeout)  	{ -		FAMClose(&w->fam->fc); -		free(w->fam); -	} +		pfd.fd=w->inotify_fd; +		pfd.events=POLLIN; -	w->fam=cpy->fam; -	++w->fam->refcnt; +		rc=poll(&pfd, 1, (w->timeout - w->now)*1000); -	maildirwatch_free(cpy); -} +		now2=time(NULL); -static int waitEvent(struct maildirwatch *w) -{ -	int fd; -	fd_set r; -	struct timeval tv; -	time_t now2; +		if (now2 < w->now) +			return 1; /* System clock changed */ -	int rc; +		w->now=now2; -	while ((rc=FAMPending(&w->fam->fc)) == 0) -	{ -		if (w->now >= w->timeout) -			return 0; +		if (rc && pfd.revents & POLLIN) +			return 1; +	} -		fd=FAMCONNECTION_GETFD(&w->fam->fc); +	return 0; +} -		FD_ZERO(&r); -		FD_SET(fd, &r); +/* +** read() inotify_events from the inotify handler. +*/ -		tv.tv_sec= w->timeout - w->now; -		tv.tv_usec=0; +static int read_inotify_events(int fd, +			       void (*callback)(struct inotify_event *ie, +						void *arg), +			       void *arg) +{ +	char inotify_buffer[sizeof(struct inotify_event)+NAME_MAX+1]; +	int l; +	char *iecp; -		select(fd+1, &r, NULL, NULL, &tv); -		now2=time(NULL); +	l=read(fd, inotify_buffer, sizeof(inotify_buffer)); -		if (now2 < w->now) -			return 0; /* System clock changed */ +	if (l < 0 && +	    (errno == EAGAIN || errno == EWOULDBLOCK)) +		l=0; /* Non-blocking socket timeout */ -		w->now=now2; +	if (l < 0) +	{ +		fprintf(stderr, "ERR:inotify read: %s\n", strerror(errno)); +		return -1;  	} -	return rc; +	iecp=inotify_buffer; + +	while (iecp < inotify_buffer+l) +	{ +		struct inotify_event *ie= +			(struct inotify_event *)iecp; + +		iecp += sizeof(struct inotify_event)+ie->len; + +		(*callback)(ie, arg); +	} +	return 0;  } -#endif +struct unlock_info { +	int handle; +	int removed; +	int deleted; +}; -int maildirwatch_unlock(struct maildirwatch *w, int nseconds) +static void unlock_handler(struct inotify_event *ie, +			   void *arg)  { -#if HAVE_FAM -	FAMRequest fr; -	FAMEvent fe; -	int cancelled=0; -	char *p; +	struct unlock_info *ui=(struct unlock_info *)arg; -	if (w->fam->broken) +	if (ie->wd == ui->handle)  	{ -		errno=EIO; -		return -1; +		if (ie->mask & IN_DELETE_SELF) +			ui->removed=1; + +		if (ie->mask & IN_IGNORED) +			ui->deleted=1;  	} +} +#endif -	p=malloc(strlen(w->maildir)+ sizeof("/" WATCHDOTLOCK)); +static int do_maildirwatch_unlock(struct maildirwatch *w, int nseconds, +				  const char *p) +{ +#if HAVE_INOTIFY_INIT +	int cancelled=0; -	if (!p) -		return -1; +	struct unlock_info ui; -	strcat(strcpy(p, w->maildir), "/" WATCHDOTLOCK); +	ui.handle=inotify_add_watch(w->inotify_fd, p, IN_DELETE_SELF); +	ui.removed=0; +	ui.deleted=0; -	errno=EIO; -	if (FAMMonitorFile(&w->fam->fc, p, &fr, NULL) < 0) +	if (ui.handle < 0)  	{ -		free(p); -		fprintf(stderr, "ERR:FAMMonitorFile: %s\n", -			strerror(errno)); +		if (errno == ENOENT) +		{ +			/* Doesn't exist anymore, that's ok */ +			return 0; +		} + +		fprintf(stderr, "ERR: %s: %s\n", p, strerror(errno));  		return -1;  	} -	free(p);  	if (nseconds < 0)  		nseconds=0; @@ -254,69 +231,74 @@ int maildirwatch_unlock(struct maildirwatch *w, int nseconds)  	w->timeout=w->now + nseconds; -	for (;;) +	do  	{ -		if (waitEvent(w) != 1) -		{ -			errno=EIO; +		errno=ETIMEDOUT; -			if (!cancelled && FAMCancelMonitor(&w->fam->fc, &fr) == 0) +		if (!poll_inotify(w)) +		{ +			if (!cancelled)  			{ +				/* +				** Timeout on the lock, cancel the inotify. +				*/  				w->timeout=w->now+15;  				cancelled=1; +				inotify_rm_watch(w->inotify_fd, ui.handle);  				continue;  			} -			if (!cancelled) -				fprintf(stderr, "ERR:FAMCancelMonitor: %s\n", -					strerror(errno)); +			fprintf(stderr, "ERR:inotify timeout: %s\n", +				strerror(errno)); -			maildirwatch_fambroken(w);  			break;  		} -		errno=EIO; +		read_inotify_events(w->inotify_fd, unlock_handler, &ui); -		if (FAMNextEvent(&w->fam->fc, &fe) != 1) +		if (ui.removed && !cancelled)  		{ -			fprintf(stderr, "ERR:FAMNextEvent: %s\n", -				strerror(errno)); -			maildirwatch_fambroken(w); -			break; +			w->timeout=w->now+15; +			cancelled=1; +			inotify_rm_watch(w->inotify_fd, ui.handle);  		} -		if (fe.fr.reqnum != fr.reqnum) -			continue; +		/* We don't terminate the loop until we get IN_IGNORED */ -		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; -		} +	} while (!ui.deleted); -		if (fe.code == FAMAcknowledge) -			break; -	} +	return ui.removed; +#else -	if (w->fam->broken) -		return -1; +	int n; +	for (n=0; n<nseconds; ++n) +	{ +		if (access(p, 0)) +			return 1; +		sleep(1); +	}  	return 0; -#else -	return -1;  #endif  } -#define DIRCNT 3 +int maildirwatch_unlock(struct maildirwatch *w, int nseconds) +{ +	char *p; +	int rc; + +	p=malloc(strlen(w->maildir)+ sizeof("/" WATCHDOTLOCK)); + +	if (!p) +		return -1; + +	strcat(strcpy(p, w->maildir), "/" WATCHDOTLOCK); + +	rc=do_maildirwatch_unlock(w, nseconds, p); + +	free(p); +	return rc; +}  int maildirwatch_start(struct maildirwatch *w,  		       struct maildirwatch_contents *mc) @@ -326,15 +308,7 @@ int maildirwatch_start(struct maildirwatch *w,  	time(&w->now);  	w->timeout = w->now + 60; -#if HAVE_FAM - -	maildirwatch_famunbreak(w); - -	if (w->fam->broken) -	{ -		errno=EIO; -		return (1); -	} +#if HAVE_INOTIFY_INIT  	{  		char *s=malloc(strlen(w->maildir) @@ -345,153 +319,58 @@ int maildirwatch_start(struct maildirwatch *w,  		strcat(strcpy(s, w->maildir), "/new"); -		mc->endexists_received=0; -		mc->ack_received=0; -		mc->cancelled=0; - -		errno=EIO; +		mc->handles[0]=inotify_add_watch(w->inotify_fd, s, +						 IN_CREATE | +						 IN_DELETE | +						 IN_MOVED_FROM | +						 IN_MOVED_TO); -		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; -		} +		mc->handles[1]=inotify_add_watch(w->inotify_fd, s, +						 IN_CREATE | +						 IN_DELETE | +						 IN_MOVED_FROM | +						 IN_MOVED_TO);  		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; -		} +		mc->handles[2]=inotify_add_watch(w->inotify_fd, s, +						 IN_CREATE | +						 IN_DELETE | +						 IN_MOVED_FROM | +						 IN_MOVED_TO);  		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 HAVE_INOTIFY_INIT +	int n; +#endif -	if (w->fam->broken) -		return (1); +	*fdret= -1; -	*fdret=FAMCONNECTION_GETFD(&w->fam->fc); +#if HAVE_INOTIFY_INIT -	while (FAMPending(&w->fam->fc)) +	for (n=0; n<sizeof(mc->handles)/sizeof(mc->handles[0]); ++n)  	{ -		FAMEvent fe; - -		errno=EIO; +		if (mc->handles[n] < 0) +			return -1; +	} -		if (FAMNextEvent(&w->fam->fc, &fe) != 1) -		{ -			fprintf(stderr, "ERR:FAMNextEvent: %s\n", -				strerror(errno)); -			maildirwatch_fambroken(w); -			return (-1); -		} +	*fdret=mc->w->inotify_fd; -		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 1; -	return (mc->endexists_received >= DIRCNT && mc->ack_received == 0);  #else  	*fdret= -1; @@ -499,6 +378,31 @@ int maildirwatch_started(struct maildirwatch_contents *mc,  #endif  } +#if HAVE_INOTIFY_INIT + +struct check_info { +	struct maildirwatch_contents *mc; +	int *changed; +	int handled; +}; + +static void check_handler(struct inotify_event *ie, +			  void *arg) +{ +	struct check_info *ci=(struct check_info *)arg; +	int n; + +	ci->handled=1; + +	for (n=0; n<sizeof(ci->mc->handles)/sizeof(ci->mc->handles[0]); ++n) +	{ +		if (ie->wd == ci->mc->handles[n]) +			*ci->changed=1; +	} + +} +#endif +  int maildirwatch_check(struct maildirwatch_contents *mc,  		       int *changed,  		       int *fdret, @@ -506,6 +410,12 @@ int maildirwatch_check(struct maildirwatch_contents *mc,  {  	struct maildirwatch *w=mc->w;  	time_t curTime; +#if HAVE_INOTIFY_INIT +	struct check_info ci; + +	ci.mc=mc; +	ci.changed=changed; +#endif  	*changed=0;  	*fdret=-1; @@ -516,100 +426,104 @@ int maildirwatch_check(struct maildirwatch_contents *mc,  		w->timeout=curTime; /* System clock changed */  	w->now=curTime; -#if HAVE_FAM +	*timeout=60; -	if (!w->fam->broken) +#if HAVE_INOTIFY_INIT +	if (maildirwatch_started(mc, fdret) > 0)  	{ -		*fdret=FAMCONNECTION_GETFD(&w->fam->fc); - -		while (FAMPending(&w->fam->fc)) -		{ -			FAMEvent fe; +		*timeout=60 * 60; -			errno=EIO; +		*fdret=w->inotify_fd; -			if (FAMNextEvent(&w->fam->fc, &fe) != 1) -			{ -				fprintf(stderr, "ERR:FAMNextEvent: %s\n", -					strerror(errno)); -				maildirwatch_fambroken(w); -				return (-1); -			} +		ci.handled=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; -			} +		while (ci.handled) +		{ +			ci.handled=0; +			read_inotify_events(w->inotify_fd, check_handler, &ci);  		} - -		*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; +	if (w->now >= w->timeout) +	{ +                w->timeout = w->now + *timeout; +		*changed=1; +	}  	return 0;  } -void maildirwatch_end(struct maildirwatch_contents *mc) +#if HAVE_INOTIFY_INIT + +struct end_info { +	struct maildirwatch_contents *mc; +	int unwatched; +}; + +static void end_handler(struct inotify_event *ie, +			void *arg)  { -#if HAVE_FAM -	struct maildirwatch *w=mc->w; +	struct end_info *ei=(struct end_info *)arg; +	int n; -	if (!w->fam->broken) +	for (n=0; n<sizeof(ei->mc->handles)/sizeof(ei->mc->handles[0]); ++n)  	{ -		if (!mc->cancelled) +		if (ie->wd == ei->mc->handles[n] && +		    ie->mask & IN_IGNORED)  		{ -			mc->cancelled=1; - -#define return(x) -			CANCEL(mc); -#undef return +			ei->mc->handles[n]=-1; +			ei->unwatched=1;  		}  	} +} + +static int maildir_end_unwatch(struct maildirwatch_contents *mc) +{ +	int n; -	while (!w->fam->broken && mc->ack_received < DIRCNT) +	for (n=0; n<sizeof(mc->handles)/sizeof(mc->handles[0]); ++n)  	{ -		FAMEvent fe; +		if (mc->handles[n] >= 0) +		{ +			inotify_rm_watch(mc->w->inotify_fd, +					 mc->handles[n]); +			return 1; +		} +	} +	return 0; +} +#endif -		time(&w->now); -		w->timeout=w->now + 15; +void maildirwatch_end(struct maildirwatch_contents *mc) +{ +#if HAVE_INOTIFY_INIT +	struct end_info ei; -		errno=EIO; +	time(&mc->w->now); +	mc->w->timeout=mc->w->now + 15; -		if (waitEvent(w) != 1) +	if (maildir_end_unwatch(mc)) /* Send the first inotify_rm_watch */ +	{ +		while (1)  		{ -			fprintf(stderr, "ERR:FAMPending: timeout\n"); -			maildirwatch_fambroken(w); -			break; -		} +			if (poll_inotify(mc->w) != 1) +			{ +				fprintf(stderr, "ERR:inotify timeout: %s\n", +					strerror(errno)); +				break; +			} -		errno=EIO; +			ei.mc=mc; +			ei.unwatched=0; +			read_inotify_events(mc->w->inotify_fd, +						end_handler, &ei); -		if (FAMNextEvent(&w->fam->fc, &fe) != 1) -		{ -			fprintf(stderr, "ERR:FAMNextEvent: %s\n", -				strerror(errno)); -			maildirwatch_fambroken(w); -			break; +			if (ei.unwatched) +			{ +				/* Send the next inotify_rm_watch? */ +				if (!maildir_end_unwatch(mc)) +					break; /* Nope, all done! */ +			}  		} - -		if (fe.code == FAMAcknowledge) -			++mc->ack_received;  	}  #endif  } | 
