Back to index

courier  0.68.2
maildirwatch.c
Go to the documentation of this file.
00001 /*
00002 ** Copyright 2002-2009 Double Precision, Inc.
00003 ** See COPYING for distribution information.
00004 */
00005 
00006 #include "config.h"
00007 #include "maildirwatch.h"
00008 
00009 #include <unistd.h>
00010 #include <string.h>
00011 #include <limits.h>
00012 #include <stdlib.h>
00013 #include <stdio.h>
00014 #include <errno.h>
00015 
00016 #ifndef PATH_MAX
00017 #define PATH_MAX 4096
00018 #endif
00019 
00020 
00021 #if HAVE_FAM
00022 static struct maildirwatch_fam *maildirwatch_currentfam;
00023 #endif
00024 
00025 struct maildirwatch *maildirwatch_alloc(const char *maildir)
00026 {
00027        char wd[PATH_MAX];
00028        struct maildirwatch *w;
00029 
00030        if (maildir == 0 || *maildir == 0)
00031               maildir=".";
00032 
00033        if (getcwd(wd, sizeof(wd)-1) == NULL)
00034               return NULL;
00035 
00036        if (*maildir == '/')
00037               wd[0]=0;
00038        else
00039               strcat(wd, "/");
00040 
00041        if ((w=malloc(sizeof(struct maildirwatch))) == NULL)
00042               return NULL;
00043 
00044        if ((w->maildir=malloc(strlen(wd)+strlen(maildir)+1)) == NULL)
00045        {
00046               free(w);
00047               return NULL;
00048        }
00049 
00050        strcat(strcpy(w->maildir, wd), maildir);
00051 
00052 #if HAVE_FAM
00053        if (!maildirwatch_currentfam)
00054        {
00055               if ((maildirwatch_currentfam
00056                    =malloc(sizeof(*maildirwatch_currentfam))) != NULL)
00057               {
00058                      maildirwatch_currentfam->broken=0;
00059                      maildirwatch_currentfam->refcnt=0;
00060 
00061                      alarm(15);
00062                      if (FAMOpen(&maildirwatch_currentfam->fc) < 0)
00063                      {
00064                             errno=EIO;
00065                             free(maildirwatch_currentfam);
00066                             maildirwatch_currentfam=NULL;
00067                      }
00068                      alarm(0);
00069               }
00070        }
00071 
00072        if (!maildirwatch_currentfam)
00073        {
00074               free(w->maildir);
00075               free(w);
00076               w=NULL;
00077        }
00078        else
00079        {
00080               w->fam=maildirwatch_currentfam;
00081               ++w->fam->refcnt;
00082        }
00083 #endif
00084        return w;
00085 }
00086 
00087 void maildirwatch_free(struct maildirwatch *w)
00088 {
00089 #if HAVE_FAM
00090        if (--w->fam->refcnt == 0)
00091        {
00092               w->fam->broken=1;
00093               if (maildirwatch_currentfam &&
00094                   maildirwatch_currentfam->broken)
00095               {
00096                      /*
00097                      ** Last reference to the current FAM connection,
00098                      ** keep it active.
00099                      */
00100 
00101                      w->fam->broken=0;
00102               }
00103               else /* Some other connection, with no more refs */
00104               {
00105                      FAMClose(&w->fam->fc);
00106                      free(w->fam);
00107               }
00108        }
00109 #endif
00110 
00111        free(w->maildir);
00112        free(w);
00113 }
00114 
00115 void maildirwatch_cleanup()
00116 {
00117 #if HAVE_FAM
00118 
00119        if (maildirwatch_currentfam && maildirwatch_currentfam->refcnt == 0)
00120        {
00121               FAMClose(&maildirwatch_currentfam->fc);
00122               free(maildirwatch_currentfam);
00123               maildirwatch_currentfam=NULL;
00124        }
00125 #endif
00126 }
00127 
00128 #if HAVE_FAM
00129 static void maildirwatch_fambroken(struct maildirwatch *w)
00130 {
00131        w->fam->broken=1;
00132 
00133        if (maildirwatch_currentfam && maildirwatch_currentfam->broken)
00134               maildirwatch_currentfam=NULL;
00135        /* Broke the current connection, create another one, next time. */
00136 
00137 }
00138 
00139 /*
00140 ** If the current connection is marked as broken, try to reconnect.
00141 */
00142 
00143 static void maildirwatch_famunbreak(struct maildirwatch *w)
00144 {
00145        struct maildirwatch *cpy;
00146 
00147        if (!w->fam->broken)
00148               return;
00149 
00150        if ((cpy=maildirwatch_alloc(w->maildir)) == NULL)
00151               return;
00152 
00153        /*
00154        ** maildirwatch_alloc succeeds only with a good connection.
00155        ** If this is the last reference to the broken connection, close it.
00156        */
00157 
00158        if (--w->fam->refcnt == 0)
00159        {
00160               FAMClose(&w->fam->fc);
00161               free(w->fam);
00162        }
00163 
00164        w->fam=cpy->fam;
00165        ++w->fam->refcnt;
00166 
00167        maildirwatch_free(cpy);
00168 }
00169 
00170 static int waitEvent(struct maildirwatch *w)
00171 {
00172        int fd;
00173        fd_set r;
00174        struct timeval tv;
00175        time_t now2;
00176 
00177        int rc;
00178 
00179        while ((rc=FAMPending(&w->fam->fc)) == 0)
00180        {
00181               if (w->now >= w->timeout)
00182                      return 0;
00183 
00184               fd=FAMCONNECTION_GETFD(&w->fam->fc);
00185 
00186               FD_ZERO(&r);
00187               FD_SET(fd, &r);
00188 
00189               tv.tv_sec= w->timeout - w->now;
00190               tv.tv_usec=0;
00191 
00192               select(fd+1, &r, NULL, NULL, &tv);
00193               now2=time(NULL);
00194 
00195               if (now2 < w->now)
00196                      return 0; /* System clock changed */
00197 
00198               w->now=now2;
00199        }
00200 
00201        return rc;
00202 }
00203 #endif
00204 
00205 
00206 int maildirwatch_unlock(struct maildirwatch *w, int nseconds)
00207 {
00208 #if HAVE_FAM
00209        FAMRequest fr;
00210        FAMEvent fe;
00211        int cancelled=0;
00212        char *p;
00213 
00214        if (w->fam->broken)
00215        {
00216               errno=EIO;
00217               return -1;
00218        }
00219 
00220        p=malloc(strlen(w->maildir)+ sizeof("/" WATCHDOTLOCK));
00221 
00222        if (!p)
00223               return -1;
00224 
00225        strcat(strcpy(p, w->maildir), "/" WATCHDOTLOCK);
00226 
00227        errno=EIO;
00228        if (FAMMonitorFile(&w->fam->fc, p, &fr, NULL) < 0)
00229        {
00230               free(p);
00231               fprintf(stderr, "ERR:FAMMonitorFile: %s\n",
00232                      strerror(errno));
00233               return -1;
00234        }
00235        free(p);
00236 
00237        if (nseconds < 0)
00238               nseconds=0;
00239 
00240        time(&w->now);
00241 
00242        w->timeout=w->now + nseconds;
00243 
00244        for (;;)
00245        {
00246               if (waitEvent(w) != 1)
00247               {
00248                      errno=EIO;
00249 
00250                      if (!cancelled && FAMCancelMonitor(&w->fam->fc, &fr) == 0)
00251                      {
00252                             w->timeout=w->now+15;
00253                             cancelled=1;
00254                             continue;
00255                      }
00256 
00257                      if (!cancelled)
00258                             fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
00259                                    strerror(errno));
00260 
00261                      maildirwatch_fambroken(w);
00262                      break;
00263               }
00264 
00265               errno=EIO;
00266 
00267               if (FAMNextEvent(&w->fam->fc, &fe) != 1)
00268               {
00269                      fprintf(stderr, "ERR:FAMNextEvent: %s\n",
00270                             strerror(errno));
00271                      maildirwatch_fambroken(w);
00272                      break;
00273               }
00274 
00275               if (fe.fr.reqnum != fr.reqnum)
00276                      continue;
00277 
00278               if (fe.code == FAMDeleted && !cancelled)
00279               {
00280                      errno=EIO;
00281                      if (FAMCancelMonitor(&w->fam->fc, &fr) == 0)
00282                      {
00283                             w->timeout=w->now+15;
00284                             cancelled=1;
00285                             continue;
00286                      }
00287                      fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
00288                             strerror(errno));
00289                      maildirwatch_fambroken(w);
00290                      break;
00291               }
00292 
00293               if (fe.code == FAMAcknowledge)
00294                      break;
00295        }
00296 
00297        if (w->fam->broken)
00298               return -1;
00299 
00300        return 0;
00301 #else
00302        return -1;
00303 #endif
00304 }
00305 
00306 #define DIRCNT 3
00307 
00308 int maildirwatch_start(struct maildirwatch *w,
00309                      struct maildirwatch_contents *mc)
00310 {
00311        mc->w=w;
00312 
00313        time(&w->now);
00314        w->timeout = w->now + 60;
00315 
00316 #if HAVE_FAM
00317 
00318        maildirwatch_famunbreak(w);
00319 
00320        if (w->fam->broken)
00321        {
00322               errno=EIO;
00323               return (1);
00324        }
00325 
00326        {
00327               char *s=malloc(strlen(w->maildir)
00328                             +sizeof("/" KEYWORDDIR));
00329 
00330               if (!s)
00331                      return (-1);
00332 
00333               strcat(strcpy(s, w->maildir), "/new");
00334 
00335               mc->endexists_received=0;
00336               mc->ack_received=0;
00337               mc->cancelled=0;
00338 
00339               errno=EIO;
00340 
00341               if (FAMMonitorDirectory(&w->fam->fc, s, &mc->new_req, NULL) < 0)
00342               {
00343                      fprintf(stderr, "ERR:"
00344                             "FAMMonitorDirectory(%s) failed: %s\n",
00345                             s, strerror(errno));
00346                      free(s);
00347                      errno=EIO;
00348                      return (-1);
00349               }
00350 
00351               strcat(strcpy(s, w->maildir), "/cur");
00352               errno=EIO;
00353 
00354               if (FAMMonitorDirectory(&w->fam->fc, s, &mc->cur_req, NULL) < 0)
00355               {
00356                      fprintf(stderr, "ERR:"
00357                             "FAMMonitorDirectory(%s) failed: %s\n",
00358                             s, strerror(errno));
00359 
00360                      errno=EIO;
00361 
00362                      if (FAMCancelMonitor(&mc->w->fam->fc, &mc->new_req) < 0)
00363                      {
00364                             free(s);
00365                             maildirwatch_fambroken(w);
00366                             fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
00367                                    strerror(errno));
00368                             errno=EIO;
00369                             return (-1);
00370                      }
00371                      mc->cancelled=1;
00372                      mc->ack_received=2;
00373               }
00374 
00375               strcat(strcpy(s, w->maildir), "/" KEYWORDDIR);
00376               errno=EIO;
00377 
00378               if (FAMMonitorDirectory(&w->fam->fc, s,
00379                                    &mc->courierimapkeywords_req, NULL)<0)
00380               {
00381                      fprintf(stderr, "ERR:"
00382                             "FAMMonitorDirectory(%s) failed: %s\n",
00383                             s, strerror(errno));
00384 
00385                      errno=EIO;
00386 
00387                      if (FAMCancelMonitor(&mc->w->fam->fc, &mc->new_req)<0)
00388                      {
00389                             free(s);
00390                             maildirwatch_fambroken(w);
00391                             fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
00392                                    strerror(errno));
00393                             errno=EIO;
00394                             return (-1);
00395                      }
00396 
00397                      errno=EIO;
00398 
00399                      if (FAMCancelMonitor(&mc->w->fam->fc, &mc->cur_req)<0)
00400                      {
00401                             free(s);
00402                             maildirwatch_fambroken(w);
00403                             fprintf(stderr, "ERR:FAMCancelMonitor: %s\n",
00404                                    strerror(errno));
00405                             errno=EIO;
00406                             return (-1);
00407                      }
00408 
00409                      mc->cancelled=1;
00410                      mc->ack_received=1;
00411               }
00412 
00413               free(s);
00414        }
00415        return 0;
00416 #else
00417        return 1;
00418 #endif
00419 }
00420 
00421 #define CANCEL(ww) \
00422        errno=EIO; if (FAMCancelMonitor(&w->fam->fc, \
00423                           &ww->new_req) || \
00424            FAMCancelMonitor(&w->fam->fc, \
00425                           &ww->cur_req) || \
00426            FAMCancelMonitor(&w->fam->fc, \
00427                           &ww->courierimapkeywords_req)) \
00428        {\
00429               maildirwatch_fambroken(w); \
00430               fprintf(stderr, \
00431                      "ERR:FAMCancelMonitor: %s\n", \
00432                      strerror(errno)); \
00433               return (-1); \
00434        }
00435 
00436 int maildirwatch_started(struct maildirwatch_contents *mc,
00437                       int *fdret)
00438 {
00439 #if HAVE_FAM
00440        struct maildirwatch *w=mc->w;
00441 
00442        if (w->fam->broken)
00443               return (1);
00444 
00445        *fdret=FAMCONNECTION_GETFD(&w->fam->fc);
00446 
00447        while (FAMPending(&w->fam->fc))
00448        {
00449               FAMEvent fe;
00450 
00451               errno=EIO;
00452 
00453               if (FAMNextEvent(&w->fam->fc, &fe) != 1)
00454               {
00455                      fprintf(stderr, "ERR:FAMNextEvent: %s\n",
00456                             strerror(errno));
00457                      maildirwatch_fambroken(w);
00458                      return (-1);
00459               }
00460 
00461               switch (fe.code) {
00462               case FAMDeleted:
00463                      if (!mc->cancelled)
00464                      {
00465                             mc->cancelled=1;
00466                             CANCEL(mc);
00467                      }
00468                      break;
00469               case FAMAcknowledge:
00470                      if (++mc->ack_received >= DIRCNT)
00471                             return -1;
00472                      break;
00473               case FAMEndExist:
00474                      ++mc->endexists_received;
00475                      break;
00476               default:
00477                      break;
00478               }
00479        }
00480 
00481        return (mc->endexists_received >= DIRCNT && mc->ack_received == 0);
00482 #else
00483        *fdret= -1;
00484 
00485        return 1;
00486 #endif
00487 }
00488 
00489 int maildirwatch_check(struct maildirwatch_contents *mc,
00490                      int *changed,
00491                      int *fdret,
00492                      int *timeout)
00493 {
00494        struct maildirwatch *w=mc->w;
00495        time_t curTime;
00496 
00497        *changed=0;
00498        *fdret=-1;
00499 
00500        curTime=time(NULL);
00501 
00502        if (curTime < w->now)
00503               w->timeout=curTime; /* System clock changed */
00504        w->now=curTime;
00505 
00506 #if HAVE_FAM
00507 
00508        if (!w->fam->broken)
00509        {
00510               *fdret=FAMCONNECTION_GETFD(&w->fam->fc);
00511 
00512               while (FAMPending(&w->fam->fc))
00513               {
00514                      FAMEvent fe;
00515 
00516                      errno=EIO;
00517 
00518                      if (FAMNextEvent(&w->fam->fc, &fe) != 1)
00519                      {
00520                             fprintf(stderr, "ERR:FAMNextEvent: %s\n",
00521                                    strerror(errno));
00522                             maildirwatch_fambroken(w);
00523                             return (-1);
00524                      }
00525 
00526                      switch (fe.code) {
00527                      case FAMDeleted:
00528                      case FAMCreated:
00529                      case FAMMoved:
00530                             if (!mc->cancelled)
00531                             {
00532                                    mc->cancelled=1;
00533                                    CANCEL(mc);
00534                             }
00535                             break;
00536                      case FAMAcknowledge:
00537                             ++mc->ack_received;
00538                      default:
00539                             break;
00540                      }
00541               }
00542 
00543               *changed=mc->ack_received >= DIRCNT;
00544               *timeout=60 * 60;
00545               return 0;
00546        }
00547 #endif
00548        *timeout=60;
00549 
00550        if ( (*changed= w->now >= w->timeout) != 0)
00551               w->timeout = w->now + 60;
00552        return 0;
00553 }
00554 
00555 void maildirwatch_end(struct maildirwatch_contents *mc)
00556 {
00557 #if HAVE_FAM
00558        struct maildirwatch *w=mc->w;
00559 
00560        if (!w->fam->broken)
00561        {
00562               if (!mc->cancelled)
00563               {
00564                      mc->cancelled=1;
00565 
00566 #define return(x)
00567                      CANCEL(mc);
00568 #undef return
00569               }
00570        }
00571 
00572        while (!w->fam->broken && mc->ack_received < DIRCNT)
00573        {
00574               FAMEvent fe;
00575 
00576               time(&w->now);
00577               w->timeout=w->now + 15;
00578 
00579               errno=EIO;
00580 
00581               if (waitEvent(w) != 1)
00582               {
00583                      fprintf(stderr, "ERR:FAMPending: timeout\n");
00584                      maildirwatch_fambroken(w);
00585                      break;
00586               }
00587 
00588               errno=EIO;
00589 
00590               if (FAMNextEvent(&w->fam->fc, &fe) != 1)
00591               {
00592                      fprintf(stderr, "ERR:FAMNextEvent: %s\n",
00593                             strerror(errno));
00594                      maildirwatch_fambroken(w);
00595                      break;
00596               }
00597 
00598               if (fe.code == FAMAcknowledge)
00599                      ++mc->ack_received;
00600        }
00601 #endif
00602 }