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 } |
