From 5cba7ab897d2148b68826379218ce15f115d06c5 Mon Sep 17 00:00:00 2001 From: Sam Varshavchik Date: Thu, 18 Feb 2021 08:28:11 -0500 Subject: Replace FAM/Gamin with inotify --- imap/Makefile.am | 6 +- imap/imapd.sgml | 201 +++++--------- maildir/.gitignore | 4 +- maildir/Makefile.am | 35 ++- maildir/configure.ac | 21 +- maildir/maildirmake.sgml | 1 + maildir/maildirwatch.c | 672 ++++++++++++++++++++------------------------- maildir/maildirwatch.h | 50 +--- maildir/maildirwatch.sgml | 114 ++++++++ maildir/maildirwatchprog.c | 201 ++++++++++++++ maildrop/Makefile.am | 5 +- sqwebmail/Makefile.am | 5 +- 12 files changed, 722 insertions(+), 593 deletions(-) create mode 100644 maildir/maildirwatch.sgml create mode 100644 maildir/maildirwatchprog.c diff --git a/imap/Makefile.am b/imap/Makefile.am index dda6235..a363bc1 100644 --- a/imap/Makefile.am +++ b/imap/Makefile.am @@ -74,11 +74,9 @@ imapd_SOURCES=fetch.c fetchinfo.c fetchinfo.h imapd.c imapd.h \ search.c searchinfo.c searchinfo.h \ storeinfo.c storeinfo.h -imapd_DEPENDENCIES=libimapd.la \ - ../maildir/maildir.libdeps @dblibrary@ +imapd_DEPENDENCIES=libimapd.la @dblibrary@ -imapd_LDADD=libimapd.la `cat ../maildir/maildir.libdeps` \ - @dblibrary@ @DEBUGLIB@ @LDAUTH@ -lcourierauth +imapd_LDADD=libimapd.la @dblibrary@ @DEBUGLIB@ @LDAUTH@ -lcourierauth pop3login_SOURCES=pop3login.c pop3dcapa.c proxy.c proxy.h pop3login_DEPENDENCIES=../tcpd/libspipe.la ../tcpd/libspipe.la libpop3d.la diff --git a/imap/imapd.sgml b/imap/imapd.sgml index 43b8ec3..6df09cf 100644 --- a/imap/imapd.sgml +++ b/imap/imapd.sgml @@ -1,5 +1,5 @@ - + SamVarshavchikAuthorCourier Mail Server @@ -12,12 +12,12 @@ imapd - The Courier IMAP server + The Courier IMAP server - @libexecdir@/couriertcpd + @libexecdir@/couriertcpd couriertcpd options @prefix@/sbin/imaplogin modules @@ -26,7 +26,7 @@ - @prefix@/bin/imapd + @prefix@/bin/imapd ./Maildir @@ -35,48 +35,48 @@ DESCRIPTION -imapd is the Courier +imapd is the Courier IMAP server that provides IMAP access to Maildir mailboxes. -Normally you don't have to worry about it, as imapd +Normally you don't have to worry about it, as imapd runs automatically after receiving a network connection, accompanied by the appropriate userid and password. -couriertcpd opens network ports that receive incoming +couriertcpd opens network ports that receive incoming IMAP connections. After an incoming network connections is established, -couriertcpd +couriertcpd runs the command specified by its first argument, which is -imaplogin passing the remaining arguments to -imaplogin. -imaplogin reads the IMAP login userid and password, +imaplogin passing the remaining arguments to +imaplogin. +imaplogin reads the IMAP login userid and password, then runs the modules specified by its remaining options, which -are Courier +are Courier server authentication modules described in the authlib7 manual page. The last daisy-chained command is -imapd, which is the actual IMAP server, +imapd, which is the actual IMAP server, which is started from the logged-in account's home directory. -The sole argument to imapd is the pathname +The sole argument to imapd is the pathname to the default IMAP mailbox, which is usually -./Maildir. +./Maildir. Some authentication modules are capable of specifying a different filename, by setting the MAILDIR environment variable. -imapd may also be invoked from the shell prompt, in which -case it issues a PREAUTH response, then changes the +imapd may also be invoked from the shell prompt, in which +case it issues a PREAUTH response, then changes the current directory to either its argument, or the contents of the MAILDIR environment variable, then attempts to talk IMAP on standard input and output. -imapd implements IMAP4REV1, as defined by +imapd implements IMAP4REV1, as defined by RFC 2060. @@ -89,11 +89,11 @@ variable, then attempts to talk IMAP on standard input and output. AUTH* -imapd examines several environment variables whose +imapd examines several environment variables whose names start with AUTH - these environment variables are set by -imaplogin and the authentication modules. +imaplogin and the authentication modules. Their absence tells -imapd that it's running from the command line. +imapd that it's running from the command line. @@ -103,15 +103,15 @@ Their absence tells MAILDIR - if defined, -imapd changes its directory to the +imapd changes its directory to the one specified by this environment variable. -Otherwise imapd changes +Otherwise imapd changes its directory to the one specified on the command line. - `pwd`/. + `pwd`/. The current directory is assumed to be the main INBOX @@ -120,7 +120,7 @@ Maildir. - `pwd`/.folder + `pwd`/.folder Maildir folders, each one containing their own @@ -131,25 +131,25 @@ tmp, new, cur, etc... Other environment variables are initialized from the -@sysconfdir@/imapd and -@sysconfdir@/imapd-ssl configuration files. +@sysconfdir@/imapd and +@sysconfdir@/imapd-ssl configuration files. These files are loaded into the environment by the system startup script -that runs couriertcpd. +that runs couriertcpd. Realtime concurrent folder status updates -Setting the IMAP_ENHANCEDIDLE to -1 in -@sysconfdir@/imapd enables realtime concurrent folder +Setting the IMAP_ENHANCEDIDLE to +1 in +@sysconfdir@/imapd enables realtime concurrent folder status updates. When relatime folder status updates are enabled all IMAP mail clients that have the same folder open will be immediately notified of any changes to the folder's contents. -The Courier IMAP server always allows +The Courier IMAP server always allows more than one mail client to have the same folder opened. However, when two or more clients have the same folder opened, the mail @@ -161,79 +161,24 @@ No provisions exists to notify the mail client immediately when the folder's contents are modified by another mail client. -The IDLE extension to the base IMAP protocol provides +The IDLE extension to the base IMAP protocol provides a delivery mechanism for notifying mail clients of changes to the mail folder's contents. Although at this time it's not known to which extent -the IDLE extension is supported by IMAP mail clients, -the Courier IMAP server fully implements -the IDLE +the IDLE extension is supported by IMAP mail clients, +the Courier IMAP server fully implements +the IDLE extension provided that the following requirements are met: - Gamin or FAM + IDLE IMAP capability -Either Gamin or -FAM -must be properly installed and -configured prior to installing -the Courier IMAP server. - - -Gamin/FAM -is an application library that -provides an interface to the operating system's kernel -that applications can use to be notified when specific files -or directories are changed, and the -Courier IMAP server leverages this API to -implement realtime concurrent folder status updates. -According to the most recently available documentation, -Gamin is a Linux-specific library, and -FAM -builds and runs on Linux and IRIX. -FAM -should also build on other platforms, but without a supported kernel monitor -FAM will fall back to a polling mode. -At press time, -FAM's -web site reports that -FAM -succesfully builds (in polling mode) on FreeBSD and Solaris. - - -FAM (but not Gamin) -also works with NFS filesystems. -On NFS clients fam transparently forwards file monitoring -requests to a peer fam process on the NFS server. - - -Installation and configuration of -Gamin or -FAM is beyond -the scope of this document. This documentation presumes that Gamin or FAM is -succesfully installed. Use the resources and tools on -Gamin's or -FAM's web site -for assistance with setting them up. -Systems that use GNOME or KDE desktops already have -FAM or -Gamin -installed, as -FAM or Gamin -is used by the current versions of both desktops. - - - - - IDLE IMAP capability - - -IDLE +IDLE must be listed in the IMAP_CAPABILITY -setting in the @sysconfdir@/imapd +setting in the @sysconfdir@/imapd configuration file. @@ -242,7 +187,7 @@ configuration file. IMAP_USELOCKS -This setting in @sysconfdir@/imapd +This setting in @sysconfdir@/imapd must be enabled. This setting uses dot-lock files to synchronize updates to folder indexes between multiple IMAP clients that have the same folder opened. @@ -256,13 +201,13 @@ daemon. An IMAP mail client that fully supports the -IDLE +IDLE protocol extension. -Of course, an IMAP client that supports the IDLE +Of course, an IMAP client that supports the IDLE protocol extension is required. -At press time the status and extent of IDLE support +At press time the status and extent of IDLE support in most IMAP mail clients is not known. @@ -271,14 +216,12 @@ in most IMAP mail clients is not known. IMAP_ENHANCEDIDLE -This setting in @sysconfdir@/imapd +This setting in @sysconfdir@/imapd actually enables concurrent realtime folder status updates using the -IDLE extension. -Note that it is possible to enable the IDLE extension -even if -FAM or -Gamin -is not available, or without +IDLE extension and the inotify kernel interface. +Note that it is possible to enable the IDLE extension +even when Courier-IMAP is compiled on a platform without the inotify +kernel interface, or without enabling either the IMAP_USELOCKS and/or IMAP_ENHANCEDIDLE settings. The resulting consequences are described are as follows: @@ -297,9 +240,9 @@ confusion that results from this situation. -Without FAM or Gamin, and +Without inotify kernel interface, and IMAP_ENHANCEDIDLE set, the -Courier IMAP server will +Courier IMAP server will manually check for changes to the folder's contents every 60 seconds, in IDLE mode (instead of in real time). @@ -321,8 +264,8 @@ commands. The following instructions describe the procedure for connecting to the IMAP server, and manually issuing IMAP protocol commands, as if they originate from an IMAP client. -The following instructions use "C:" to indicate IMAP -client commands that must be entered, and "S:" to +The following instructions use "C:" to indicate IMAP +client commands that must be entered, and "S:" to indicate the expected replies from the server. @@ -360,11 +303,11 @@ S:+ entering ENHANCED idle mode -The default Courier IMAP server +The default Courier IMAP server configuration permits a maximum of four connections from the same IP address. It may be necessary to adjust this setting in -@sysconfdir@/imapd +@sysconfdir@/imapd for the duration of this test. @@ -374,14 +317,7 @@ for the duration of this test. The last message from the server must be "entering ENHANCED idle mode". Otherwise, it means that some of the necessary prerequisites have not been -met. -Verify that FAM -or Gamin was set up prior to installing -The Courier IMAP server -(use ldd1 -to verify that the imapd executable is linked with -the libfam library), and verify the settings in the -@sysconfdir@/imapd. +met. @@ -410,14 +346,11 @@ a OK STORE completed. -The last command sets the \Deleted flag on the first +The last command sets the \Deleted flag on the first message in the folder. Immediately after entering the last command, -"* 1 FETCH (FLAGS (\Deleted))" should also appear +"* 1 FETCH (FLAGS (\Deleted))" should also appear in all other terminal windows. -On systems where FAM uses the fall-back polling -mode this response may appear after a brief delay of a few seconds. -The delay should never exceed 15-20 seconds. @@ -425,9 +358,9 @@ The delay should never exceed 15-20 seconds. Verify that all terminal windows reliably receive folder status updates in real time by alternatively entering the commands -"a STORE 1 -FLAGS (\Deleted)" +"a STORE 1 -FLAGS (\Deleted)" and -"a STORE 1 +FLAGS (\Deleted)", +"a STORE 1 +FLAGS (\Deleted)", to toggle the deleted flag on the first message. Observe that the message is received by all terminal windows quickly, and reliably. @@ -435,8 +368,8 @@ and reliably. -With the \Deleted flag set on the first message, -enter the EXPUNGE command, which removes the deleted +With the \Deleted flag set on the first message, +enter the EXPUNGE command, which removes the deleted message from the folder: @@ -450,7 +383,7 @@ S:a OK EXPUNGE completed The lines that begin with the "*" character should also appear in all other terminal windows (depending on the initial folder state one of the terminal -windows may have a different RECENT message, which is +windows may have a different RECENT message, which is fine). @@ -470,13 +403,13 @@ messages should appear in every terminal window: The numbers in these messages may be different, depending upon the initial contents of the test mail folder. -One of the terminal windows should have a different RECENT +One of the terminal windows should have a different RECENT count, and one of the terminal windows should include a -\Recent flag in the untagged -FLAGS message. +\Recent flag in the untagged +FLAGS message. These difference are acceptable; the important thing is to make sure that -all terminal windows have the same EXISTS message. +all terminal windows have the same EXISTS message. @@ -487,7 +420,7 @@ all terminal windows have the same EXISTS mes authlib7, - + userdb8 diff --git a/maildir/.gitignore b/maildir/.gitignore index 9939f4f..7fab6ba 100644 --- a/maildir/.gitignore +++ b/maildir/.gitignore @@ -9,7 +9,6 @@ /mailbot.h /maildir.5 /maildir.html -/maildir.libdeps /maildiracl /maildiracl.1 /maildiracl.1.in @@ -29,6 +28,9 @@ /maildirquota.7 /maildirquota.html /maildirsharedrc.h +/maildirwatch +/maildirwatch.1 +/maildirwatch.html /quotawarnmsg.h /sharedindexinstall /sharedindexsplit diff --git a/maildir/Makefile.am b/maildir/Makefile.am index 60f0681..54c0602 100644 --- a/maildir/Makefile.am +++ b/maildir/Makefile.am @@ -12,6 +12,7 @@ DOCS= deliverquota.html.in deliverquota.8.in \ maildir.html maildir.5 \ maildiracl.html.in \ maildirmake.html.in maildirmake.1.in maildirquota.html maildirquota.7 \ + maildirwatch.html maildirwatch.1 \ maildirkw.html maildirkw.1 if HAVE_SGML @@ -23,7 +24,7 @@ BUILT_SOURCES=maildirsharedrc.h maildirfilterconfig.h quotawarnmsg.h \ endif noinst_DATA=deliverquota.html maildirmake.html deliverquota.8 maildirmake.1 \ - maildiracl.html maildiracl.1 maildir.libdeps + maildiracl.html maildiracl.1 libmaildir_la_SOURCES=autoresponse.c autoresponse.h \ maildiraclt.c maildiraclt.h \ @@ -49,7 +50,7 @@ libmaildir_la_SOURCES=autoresponse.c autoresponse.h \ maildirwatch.c maildirwatch.h loginexec.c loginexec.h noinst_PROGRAMS=deliverquota maildirmake testmaildirfilter maildirkwtest \ - maildirkw maildiracl maildiraclttest testmaildirsearch + maildirkw maildiracl maildiraclttest testmaildirsearch maildirwatch deliverquota_SOURCES=deliverquota.c deliverquota_DEPENDENCIES=libmaildir.la ../rfc822/librfc822.la \ @@ -77,24 +78,28 @@ maildirkwtest_DEPENDENCIES=libmaildir.la maildirkwtest_LDFLAGS=-static maildirkw_SOURCES=maildirkw.c -maildirkw_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la \ - `cat maildir.libdeps` +maildirkw_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la maildirkw_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ - ../numlib/libnumlib.la maildir.libdeps + ../numlib/libnumlib.la maildirkw_LDFLAGS=-static +maildirwatch_SOURCES=maildirwatchprog.c +maildirwatch_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la +maildirwatch_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ + ../numlib/libnumlib.la +maildirwatch_LDFLAGS=-static + maildiracl=maildiracl.c -maildiracl_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la \ - `cat maildir.libdeps` +maildiracl_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la maildiracl_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ - ../numlib/libnumlib.la maildir.libdeps + ../numlib/libnumlib.la maildiracl_LDFLAGS=-static maildiraclttest_SOURCES=testmaildiraclt.c maildiraclttest_LDADD=libmaildir.la ../liblock/liblock.la \ - ../numlib/libnumlib.la `cat maildir.libdeps` + ../numlib/libnumlib.la maildiraclttest_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ - ../numlib/libnumlib.la maildir.libdeps + ../numlib/libnumlib.la maildiraclttest_LDFLAGS=-static testmaildirsearch_SOURCES=testmaildirsearch.c @@ -130,11 +135,9 @@ autoresponsequota.h: config.status quotawarnmsg.h: config.status echo '#define QUOTAWARNMSG "$(sysconfdir)/quotawarnmsg"' >quotawarnmsg.h -maildir.libdeps: config.status - echo @LIBFAM@ >maildir.libdeps clean-local: - rm -rf maildir.libdeps testmd + rm -rf testmd check-am: @SHELL@ $(srcdir)/testsuite 2>&1 | cmp - $(srcdir)/testsuite.txt @@ -188,6 +191,12 @@ maildirkw.html: maildirkw.sgml ../docbook/sgml2html maildirkw.1: maildirkw.sgml ../docbook/sgml2man ../docbook/sgml2man maildirkw.sgml maildirkw.1 "--stringparam man.base.url.for.relative.links http://www.courier-mta.org/" +maildirwatch.html: maildirwatch.sgml ../docbook/sgml2html + ../docbook/sgml2html maildirwatch.sgml maildirwatch.html + +maildirwatch.1: maildirwatch.sgml ../docbook/sgml2man + ../docbook/sgml2man maildirwatch.sgml maildirwatch.1 + endif deliverquota.html: deliverquota.html.in diff --git a/maildir/configure.ac b/maildir/configure.ac index d35162a..fdbf576 100644 --- a/maildir/configure.ac +++ b/maildir/configure.ac @@ -74,26 +74,9 @@ AX_COURIER_UNICODE_CXXFLAGS AC_SUBST(COURIER_UNICODE_CXXFLAGS) dnl Checks for library functions. -AC_CHECK_HEADER(fam.h, :, :) AC_CHECK_FUNCS(symlink readlink strcasecmp utime utimes) -AC_CHECK_LIB(fam, FAMOpen, [ - LIBFAM=-lfam - AC_DEFINE_UNQUOTED(HAVE_FAM,1, - [ Whether libfam.a is available ]) - - AC_CHECK_HEADER(fam.h, : , [ -AC_MSG_WARN([[The development header files and libraries for fam,]]) -AC_MSG_WARN([[the File Alteration Monitor, are not installed.]]) -AC_MSG_WARN([[You appear to have the FAM runtime libraries installed,]]) -AC_MSG_WARN([[so you need to simply install the additional development]]) -AC_MSG_WARN([[package for your operating system.]]) -AC_MSG_ERROR([[FAM development libraries not found.]]) ] - ) - ]) - -AC_SUBST(LIBFAM) - -echo "$LIBFAM" >maildir.libdeps + +AC_CHECK_FUNCS(inotify_init inotify_init1) AC_CACHE_CHECK([for missing gethostname prototype],maildir_cv_SYS_GETHOSTNAME, diff --git a/maildir/maildirmake.sgml b/maildir/maildirmake.sgml index e8f4d70..57aebd6 100644 --- a/maildir/maildirmake.sgml +++ b/maildir/maildirmake.sgml @@ -584,6 +584,7 @@ Updating /home/mrsam/.mailfilter maildir5, maildiracl1, maildirkw1, +maildirwatch1, maildrop1, maildirquota7, deliverquota8, 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 #include #include +#if HAVE_INOTIFY_INIT +#include +#include +#include +#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; nmaildir)+ 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; nhandles)/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; nmc->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; nmc->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; nhandles)/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 } diff --git a/maildir/maildirwatch.h b/maildir/maildirwatch.h index 330baac..5f49623 100644 --- a/maildir/maildirwatch.h +++ b/maildir/maildirwatch.h @@ -1,7 +1,7 @@ #ifndef maildirwatch_h #define maildirwatch_h /* -** Copyright 2002 Double Precision, Inc. +** Copyright 2002-2021 Double Precision, Inc. ** See COPYING for distribution information. */ @@ -15,16 +15,9 @@ extern "C" { #endif /* -** These function leverage libfam.a to watch for maildir changes. -** -** If libfam.a is not available, these functions are compiled to no-ops +** These function use inotify to watch for maildir changes. */ -#if HAVE_FAM -#include -#endif - - #if TIME_WITH_SYS_TIME #include #include @@ -36,20 +29,11 @@ extern "C" { #endif #endif -#if HAVE_FAM -struct maildirwatch_fam { - FAMConnection fc; - int broken; - unsigned refcnt; -}; - -#endif - struct maildirwatch { char *maildir; -#if HAVE_FAM - struct maildirwatch_fam *fam; +#if HAVE_INOTIFY_INIT + int inotify_fd; #endif time_t now; time_t timeout; @@ -79,27 +63,16 @@ int maildirwatch_unlock(struct maildirwatch *w, int nseconds); struct maildirwatch_contents { struct maildirwatch *w; -#if HAVE_FAM - FAMRequest new_req; - FAMRequest cur_req; - FAMRequest courierimapkeywords_req; - - unsigned short endexists_received; - unsigned short ack_received; - - unsigned short cancelled; - +#if HAVE_INOTIFY_INIT + int handles[3]; #endif - }; /* ** maildirwatch_start() initiates the process of monitoring the maildir. -** The monitoring process does not get started right away, since FAM needs -** to acknowledge th monitoring requests first. ** -** Returns: 0 - monitoring request sent. -** 1 - FAM is not available, will fall back to 60 second polls. +** Returns: 0 - monitoring started. +** 1 - inotify not available, will fall back to 60 second polls. ** -1 - Fatal error. */ @@ -107,10 +80,11 @@ int maildirwatch_start(struct maildirwatch *p, struct maildirwatch_contents *w); /* -** Check if FAM started monitoring yet. +** Check the status of inotify monitoring. ** ** Returns: 1 - Monitoring has started, or we're in fallback mode. ** 0 - Not yet, *fdret is initialized to file descriptor to wait on. +** (not used at this time). ** -1 - A fatal error occured, fall back to polling mode. ** ** maildirwatch_started() returns right away, without blocking. @@ -127,7 +101,9 @@ int maildirwatch_started(struct maildirwatch_contents *w, ** -1 - Fatal error. ** ** *fdret and *timeout get initialized to the file descriptor to wait on, -** and the requested timeout. *fdret may be negative in polling mode. +** and the requested timeout. *fdret may be negative in polling mode, this +** should be interpreted as: if *changed is not set, sleep for this period of +** time. */ int maildirwatch_check(struct maildirwatch_contents *w, diff --git a/maildir/maildirwatch.sgml b/maildir/maildirwatch.sgml new file mode 100644 index 0000000..3787434 --- /dev/null +++ b/maildir/maildirwatch.sgml @@ -0,0 +1,114 @@ + + + + + SamVarshavchikAuthorCourier Mail Server + + + maildirwatch + 1 + Double Precision, Inc. + + + + maildirwatch + wait for changes to a maildir + + + + + maildirwatch + maildir + command + + argument + + + + + DESCRIPTION + + + maildirwatch repeatedly invokes the + command, with any optional + arguments, an external + command, as follows: + + + + + + The first time the + command gets executed + is immediately after + maildirwatch starts. + + + + + + Subsequent invocatons of + command occur whenever + there may be some kind of a change + to the contents of the + maildir. + + + + maildirwatch does not determine what, if anything, + changed in the + maildir. + maildirwatch + might + occasionally raise a false alarm and run the + command even though + nothing changed, but that's rare. It's also possible that by + the time command + actually runs, then whatever was changed in the + maildir is no longer + changed; it became what it was before it was changed (a mystery + that will remain unsolved forever). + It's up to the + command to + intelligently figure out + if it needs to do something about whatever it finds in the + maildir. + + + + + + maildirwatch is a rudimentary mechanism for writing + shell scripts that deal with newly-delivered mail, in + some form or fashion. + + + + + EXIT CODE + + + A non-zero exit code indicates that the specified + maildir does not + exist or is corrupted. + + + + Otherwise + maildirwatch runs until the + command exits with a + non-zero exit code. + maildirwatch prints + command's exit code on + standard output, and terminates with an exit code of 0. + + + + + SEE ALSO + + +maildirmake1. + + + diff --git a/maildir/maildirwatchprog.c b/maildir/maildirwatchprog.c new file mode 100644 index 0000000..f169da8 --- /dev/null +++ b/maildir/maildirwatchprog.c @@ -0,0 +1,201 @@ +/* +** Copyright 2021 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#if HAVE_SYS_WAIT_H +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#include +#include "maildirwatch.h" + +static void usage() +{ + printf("Usage: maildirwatch maildir program arguments...\n"); + exit(1); +} + +static int forkexec(int argc, char **argv) +{ + pid_t p=fork(); + int s; + + if (p < 0) + { + perror("fork"); + return 0; + } + + if (p == 0) + { + char **argvptr=malloc(sizeof(char *)*(argc+1)); + int n; + + if (!argvptr) + { + perror("malloc"); + exit(1); + } + + for (n=0; n