Back to index

courier  0.68.2
smapsnapshot.c
Go to the documentation of this file.
00001 /*
00002 ** Copyright 2003 Double Precision, Inc.
00003 ** See COPYING for distribution information.
00004 */
00005 
00006 #if    HAVE_CONFIG_H
00007 #include      "config.h"
00008 #endif
00009 #include      <stdio.h>
00010 #include      <stdlib.h>
00011 #include      <string.h>
00012 #include      <errno.h>
00013 #include      <ctype.h>
00014 #include      <signal.h>
00015 #include      <fcntl.h>
00016 #if    HAVE_UNISTD_H
00017 #include      <unistd.h>
00018 #endif
00019 #if    HAVE_UTIME_H
00020 #include      <utime.h>
00021 #endif
00022 #if TIME_WITH_SYS_TIME
00023 #include      <sys/time.h>
00024 #include      <time.h>
00025 #else
00026 #if HAVE_SYS_TIME_H
00027 #include      <sys/time.h>
00028 #else
00029 #include      <time.h>
00030 #endif
00031 #endif
00032 #if HAVE_LOCALE_H
00033 #include      <locale.h>
00034 #endif
00035 
00036 #include      <sys/types.h>
00037 #include      <sys/stat.h>
00038 
00039 #include      "mysignal.h"
00040 #include      "imapd.h"
00041 #include      "imapscanclient.h"
00042 #include      "imapwrite.h"
00043 
00044 #include      "maildir/config.h"
00045 #include      "maildir/maildircreate.h"
00046 #include      "maildir/maildirrequota.h"
00047 #include      "maildir/maildirgetquota.h"
00048 #include      "maildir/maildirquota.h"
00049 #include      "maildir/maildirmisc.h"
00050 #include      "maildir/maildirwatch.h"
00051 
00052 #if HAVE_DIRENT_H
00053 #include <dirent.h>
00054 #define NAMLEN(dirent) strlen((dirent)->d_name)
00055 #else
00056 #define dirent direct
00057 #define NAMLEN(dirent) (dirent)->d_namlen
00058 #if HAVE_SYS_NDIR_H
00059 #include <sys/ndir.h>
00060 #endif
00061 #if HAVE_SYS_DIR_H
00062 #include <sys/dir.h>
00063 #endif
00064 #if HAVE_NDIR_H
00065 #include <ndir.h>
00066 #endif
00067 #endif
00068 
00069 
00070 extern int keywords();
00071 
00072 /*
00073 ** Implement SMAP snapshots.  A snapshot is implemented, essentially, by
00074 ** saving the current folder index, restoring it, then doing a noop().
00075 **
00076 ** The snapshot file saves uids, not complete filenames.  The complete
00077 ** filenames are already in courierimapuiddb.  Filenames are long, and saving
00078 ** them can result in huge snapshot files for large folders.  So only uids
00079 ** are saved, and when the snapshot is restored the courierimapuiddb file is
00080 ** read to obtain the filenames.
00081 */
00082 
00083 extern char *current_mailbox;
00084 extern struct imapscaninfo current_maildir_info;
00085 extern char *readline(unsigned i, FILE *);
00086 
00087 static char *snapshot_dir; /* Directory with snapshots */
00088 
00089 static char *snapshot_last; /* Last snapshot */
00090 static char *snapshot_cur;  /* Current snapshot */
00091 
00092 static int index_dirty;
00093 static int snapshots_enabled;
00094 
00095 extern void set_time(const char *tmpname, time_t timestamp);
00096 extern void smapword(const char *);
00097 
00098 struct snapshot_list {
00099        struct snapshot_list *next;
00100 
00101        char *filename;
00102        char *prev;
00103        time_t mtime;
00104 };
00105 
00106 /*
00107 ** When cleaning up a snapshot directory, we need to know whether there's
00108 ** a later snapshot that claims that this snapshot is the previous
00109 ** snapshot (we can safely dump snapshots that were previous snapshots
00110 ** of previous snapshots).
00111 */
00112 
00113 static struct snapshot_list *find_next_snapshot(struct snapshot_list *s,
00114                                           const char *n)
00115 {
00116        const char *p, *q;
00117 
00118        p=strrchr(n, '/');
00119 
00120        if (p)
00121               n=p+1;
00122 
00123        while (s)
00124        {
00125               p=s->prev;
00126               q=p ? strrchr(p, '/'):NULL;
00127 
00128               if (q && strcmp(q+1, n) == 0)
00129                      return s;
00130 
00131               s=s->next;
00132        }
00133        return NULL;
00134 }
00135 
00136 /*
00137 ** Delete a snapshot structure, and the actual file
00138 */
00139 
00140 static void delete_snapshot(struct snapshot_list *snn)
00141 {
00142        char *p=malloc(strlen(snapshot_dir)+strlen(snn->filename)+2);
00143 
00144        if (p)
00145        {
00146               strcat(strcat(strcpy(p, snapshot_dir), "/"), snn->filename);
00147               unlink(p);
00148        }
00149 
00150        free(snn->filename);
00151        free(snn->prev);
00152        free(snn);
00153 }
00154 
00155 /*
00156 ** Restore a snapshot
00157 */
00158 
00159 static int restore_snapshot2(const char *snapshot_dir,
00160                           FILE *fp,
00161                           struct imapscaninfo *new_index);
00162 
00163 /*
00164 ** Part 1: process the first header line of a snapshot file, and allocate a
00165 ** new folder index list.
00166 */
00167 
00168 static int restore_snapshot(const char *dir, FILE *snapshot_fp,
00169                          char **last_snapshot)
00170 {
00171        int format;
00172        unsigned long s_nmessages, s_uidv, s_nextuid;
00173        char *p;
00174        char *buf;
00175        struct imapscaninfo new_index;
00176 
00177        if ((buf=readline(0, snapshot_fp)) == NULL)
00178               return 0;
00179 
00180        p=strchr(buf, ':');
00181        if (p)
00182               *p++=0;
00183 
00184        *last_snapshot=NULL;
00185 
00186        if (sscanf(buf, "%d %lu %lu %lu", &format, &s_nmessages, &s_uidv,
00187                  &s_nextuid) != 4 || format != SNAPSHOTVERSION)
00188               return 0; /* Don't recognize the header */
00189 
00190        /* Save the previous snapshot ID */
00191 
00192        if (p)
00193        {
00194               *last_snapshot=malloc(strlen(dir)+strlen(p)+2);
00195 
00196               if (!last_snapshot)
00197               {
00198                      write_error_exit(0);
00199                      return 0;
00200               }
00201 
00202               strcat(strcat(strcpy(*last_snapshot, dir), "/"), p);
00203        }
00204 
00205        imapscan_init(&new_index);
00206 
00207        if (s_nmessages && (new_index.msgs=(struct imapscanmessageinfo *)
00208                          malloc(s_nmessages * sizeof(*new_index.msgs)))
00209            == 0)
00210        {
00211               write_error_exit(0);
00212               return (0);
00213        }
00214        memset(new_index.msgs, 0, s_nmessages * sizeof(*new_index.msgs));
00215 
00216        new_index.nmessages=s_nmessages;
00217        new_index.uidv=s_uidv;
00218        new_index.nextuid=s_nextuid;
00219 
00220        if (restore_snapshot2(dir, snapshot_fp, &new_index))
00221        {
00222               imapscan_copy(&current_maildir_info, &new_index);
00223               imapscan_free(&new_index);
00224               return 1;
00225        }
00226        imapscan_free(&new_index);
00227 
00228        if (*last_snapshot)
00229        {
00230               free(*last_snapshot);
00231               *last_snapshot=0;
00232        }
00233 
00234        return 0;
00235 }
00236 
00237 /*
00238 ** Part 2: combine the snapshot and courierimapuiddb, create a halfbaked
00239 ** index from the combination.
00240 */
00241 
00242 static int restore_snapshot2(const char *snapshot_dir,
00243                           FILE *fp,
00244                           struct imapscaninfo *new_index)
00245 {
00246        unsigned long i;
00247        char *p=malloc(strlen(snapshot_dir) + sizeof("/../" IMAPDB));
00248        FILE *courierimapuiddb;
00249        int version;
00250        unsigned long uidv;
00251        unsigned long nextuid;
00252        char *uid_line;
00253        unsigned long uid=0;
00254 
00255        if (!p)
00256        {
00257               write_error_exit(0);
00258               return 0;
00259        }
00260 
00261        strcat(strcpy(p, snapshot_dir), "/../" IMAPDB);
00262 
00263        courierimapuiddb=fopen(p, "r");
00264        free(p);
00265 
00266        if (!courierimapuiddb)
00267               return 0; /* Can't open the uiddb file, no dice */
00268 
00269        if ((p=readline(0, courierimapuiddb)) == NULL ||
00270            sscanf(p, "%d %lu %lu", &version, &uidv, &nextuid) != 3 ||
00271            version != IMAPDBVERSION /* Do not recognize the uiddb file */
00272 
00273            || uidv != new_index->uidv /* Something major happened, abort */ )
00274        {
00275               fclose(courierimapuiddb);
00276               return 0;
00277        }
00278 
00279        uid_line=readline(0, courierimapuiddb);
00280 
00281        if (uid_line)
00282        {
00283               if (sscanf(uid_line, "%lu", &uid) != 1)
00284               {
00285                      fclose(courierimapuiddb);
00286                      return 0;
00287               }
00288        }
00289 
00290        /*
00291        ** Both the snapshot file and courierimapuiddb should be in sorted
00292        ** order, by UIDs, rely on that and do what amounts to a merge sort.
00293        */
00294 
00295        for (i=0; i<new_index->nmessages; i++)
00296        {
00297               unsigned long s_uid;
00298               char flag_buf[128];
00299 
00300               p=fgets(flag_buf, sizeof(flag_buf)-1, fp);
00301 
00302               if (p == NULL || (p=strchr(p, '\n')) == NULL ||
00303                   (*p = 0, sscanf(flag_buf, "%lu", &s_uid)) != 1 ||
00304                   (p=strchr(flag_buf, ':')) == NULL) /* Corrupted file */
00305               {
00306                      fclose(courierimapuiddb);
00307                      return 0;
00308               }
00309 
00310               new_index->msgs[i].uid=s_uid;
00311 
00312               /* Try to fill in the filenames to as much of an extent as
00313               ** possible.  If IMAPDB no longer has a particular uid listed,
00314               ** that's ok, because the message is now gone, so we just
00315               ** insert an empty filename, which will be expunged by
00316               ** noop() processing, after the snapshot is restored.
00317               */
00318 
00319               while (uid_line && uid <= s_uid)
00320               {
00321                      if (uid == s_uid &&
00322                          (uid_line=strchr(uid_line, ' ')) != NULL)
00323                             /* Jackpot */
00324                      {
00325                             new_index->msgs[i].filename=
00326                                    malloc(strlen(uid_line)+
00327                                           strlen(flag_buf)+2);
00328 
00329                             if (!new_index->msgs[i].filename)
00330                             {
00331                                    fclose(courierimapuiddb);
00332                                    write_error_exit(0);
00333                                    return 0;
00334                             }
00335 
00336                             strcpy(new_index->msgs[i].filename,
00337                                    uid_line+1);
00338 
00339                             if (p)
00340                             {
00341                                    strcat(strcat(new_index->msgs[i]
00342                                                 .filename, MDIRSEP),
00343                                           p+1);
00344                             }
00345                      }
00346 
00347                      uid_line=readline(0, courierimapuiddb);
00348 
00349                      if (uid_line)
00350                      {
00351                             if (sscanf(uid_line, "%lu", &uid) != 1)
00352                             {
00353                                    fclose(courierimapuiddb);
00354                                    return 0;
00355                             }
00356                      }
00357               }
00358 
00359 
00360               if (new_index->msgs[i].filename == 0)
00361               {
00362                      new_index->msgs[i].filename=strdup("");
00363                      /* A noop should get rid of this entry anyway */
00364 
00365                      if (!new_index->msgs[i].filename)
00366                      {
00367                             fclose(courierimapuiddb);
00368                             write_error_exit(0);
00369                             return 0;
00370                      }
00371               }
00372        }
00373 
00374        fclose(courierimapuiddb);
00375        if (keywords())
00376               imapscan_restoreKeywordSnapshot(fp, new_index);
00377        return 1;
00378 }
00379 
00380 void snapshot_select(int flag)
00381 {
00382        snapshots_enabled=flag;
00383 }
00384 
00385 /*
00386 ** Initialize snapshots for an opened folder.
00387 **
00388 ** Parameters:
00389 **
00390 **     folder - the path to a folder that's in the process of opening.
00391 **
00392 **     snapshot - not NULL if the client requested a snapshot restore.
00393 **
00394 ** Exit code:
00395 **
00396 ** When a snapshot is requested, a non-zero exit code means that the
00397 ** snapshot has been succesfully restored, and current_mailbox is now
00398 ** initialized based on the snapshot.  A zero exit code means that the
00399 ** snapshot has not been restored, and snapshot_init() needs to be called
00400 ** again with snapshot=NULL in order to initialize the snapshot structures.
00401 **
00402 ** When a snapshot is not requested, the exit code is always 0
00403 */
00404 
00405 int snapshot_init(const char *folder, const char *snapshot)
00406 {
00407        struct snapshot_list *sl=NULL;
00408        DIR *dirp;
00409        struct dirent *de;
00410        struct snapshot_list *snn, **ptr;
00411        int cnt;
00412        char *new_dir;
00413        int rc=0;
00414        char *new_snapshot_cur=NULL;
00415        char *new_snapshot_last=NULL;
00416 
00417        if ((new_dir=malloc(strlen(folder)+sizeof("/" SNAPSHOTDIR)))
00418            == NULL)
00419        {
00420               write_error_exit(0);
00421               return rc;
00422        }
00423 
00424        strcat(strcpy(new_dir, folder), "/" SNAPSHOTDIR);
00425        mkdir(new_dir, 0755); /* Create, if doesn't exist */
00426 
00427        if (snapshot)
00428        {
00429               FILE *fp;
00430 
00431               if (*snapshot == 0 || strchr(snapshot, '/') ||
00432                   *snapshot == '.') /* Monkey business */
00433               {
00434                      free(new_dir);
00435                      return 0;
00436               }
00437 
00438               new_snapshot_cur=malloc(strlen(new_dir) +
00439                                    strlen(snapshot) + 2);
00440 
00441               if (!new_snapshot_cur)
00442               {
00443                      free(new_dir);
00444                      write_error_exit(0);
00445                      return rc;
00446               }
00447 
00448               strcat(strcat(strcpy(new_snapshot_cur, new_dir), "/"),
00449                      snapshot);
00450 
00451               if ((fp=fopen(new_snapshot_cur, "r")) != NULL &&
00452                   restore_snapshot(new_dir, fp, &new_snapshot_last))
00453               {
00454                      set_time(new_snapshot_cur, time(NULL));
00455                      rc=1; /* We're good to go.  Finish everything else */
00456               }
00457 
00458               if (fp)
00459               {
00460                      fclose(fp);
00461                      fp=NULL;
00462               }
00463 
00464               if (!rc) /* Couldn't get the snapshot, abort */
00465               {
00466                      free(new_snapshot_cur);
00467                      free(new_dir);
00468                      return 0;
00469               }
00470        }
00471 
00472        if (snapshot_dir) free(snapshot_dir);
00473        if (snapshot_last) free(snapshot_last);
00474        if (snapshot_cur) free(snapshot_cur);
00475 
00476        snapshot_dir=NULL;
00477        snapshot_last=new_snapshot_last;
00478        snapshot_cur=new_snapshot_cur;
00479 
00480        snapshot_dir=new_dir;
00481 
00482        index_dirty=1;
00483 
00484        /* Get rid of old snapshots as follows */
00485 
00486        /* Step 1, compile a list of snapshots, sorted in mtime order */
00487 
00488        dirp=opendir(snapshot_dir);
00489 
00490        while (dirp && (de=readdir(dirp)) != NULL)
00491        {
00492               FILE *fp;
00493               struct stat stat_buf;
00494 
00495               char *n;
00496 
00497               if (de->d_name[0] == '.') continue;
00498 
00499               n=malloc(strlen(snapshot_dir)+strlen(de->d_name)+2);
00500               if (!n) break; /* Furrfu */
00501 
00502               strcat(strcat(strcpy(n, snapshot_dir), "/"), de->d_name);
00503 
00504               fp=fopen(n, "r");
00505 
00506               if (fp)
00507               {
00508                      char buf[1024];
00509 
00510                      if (fgets(buf, sizeof(buf)-1, fp) != NULL &&
00511                          fstat(fileno(fp), &stat_buf) == 0)
00512                      {
00513                             char *p=strchr(buf, '\n');
00514                             int fmt;
00515 
00516                             if (p) *p=0;
00517 
00518                             p=strchr(buf, ':');
00519 
00520                             if (p)
00521                                    *p++=0;
00522 
00523 
00524                             if (sscanf(buf, "%d", &fmt) == 1 &&
00525                                 fmt == SNAPSHOTVERSION)
00526                             {
00527                                    snn=malloc(sizeof(*sl));
00528 
00529                                    if (snn) memset(snn, 0, sizeof(*snn));
00530 
00531                                    if (snn == NULL ||
00532                                        (snn->filename=strdup(de->d_name))
00533                                        == NULL ||
00534                                        (snn->prev=strdup(p ? p:""))
00535                                        == NULL)
00536                                    {
00537                                           if (snn && snn->filename)
00538                                                  free(snn->filename);
00539                                           if (snn)
00540                                                  free(snn);
00541 
00542                                           snn=NULL;
00543                                    }
00544 
00545                                    if (snn)
00546                                    {
00547                                           snn->mtime=stat_buf.st_mtime;
00548 
00549                                           for (ptr= &sl; *ptr;
00550                                                ptr=&(*ptr)->next)
00551                                           {
00552                                                  if ( (*ptr)->mtime >
00553                                                       snn->mtime)
00554                                                         break;
00555                                           }
00556 
00557                                           snn->next= *ptr;
00558                                           *ptr=snn;
00559                                    }
00560                                    free(n);
00561                                    n=NULL;
00562                             }
00563 
00564                      }
00565                      fclose(fp);
00566               }
00567               if (n)
00568               {
00569                      unlink(n);
00570                      free(n);
00571               }
00572        }
00573        if (dirp)
00574               closedir(dirp);
00575 
00576        /* Step 2: drop snapshots that are definitely obsolete */
00577 
00578        for (ptr= &sl; *ptr; )
00579        {
00580               if ((snn=find_next_snapshot(sl, (*ptr)->filename)) &&
00581                   find_next_snapshot(sl, snn->filename))
00582               {
00583                      snn= *ptr;
00584 
00585                      *ptr=snn->next;
00586 
00587                      delete_snapshot(snn);
00588               }
00589               else
00590                      ptr=&(*ptr)->next;
00591 
00592        }
00593 
00594        /* If there are more than 10 snapshots, drop older snapshots */
00595 
00596        cnt=0;
00597        for (snn=sl; snn; snn=snn->next)
00598               ++cnt;
00599 
00600        if (cnt > 10)
00601        {
00602               time_t now=time(NULL);
00603 
00604               while (sl && sl->mtime < now &&
00605                      (now - sl->mtime) > 60 * 60 * 24 * (7 + (cnt-10)*2))
00606               {
00607                      snn=sl;
00608                      sl=sl->next;
00609                      delete_snapshot(snn);
00610                      --cnt;
00611               }
00612        }
00613 
00614        /* All right, put a lid on 50 snapshots */
00615 
00616        while (cnt > 50)
00617        {
00618               snn=sl;
00619               sl=sl->next;
00620               delete_snapshot(snn);
00621               --cnt;
00622        }
00623 
00624        return rc;
00625 }
00626 
00627 /*
00628 ** Something changed in the folder, so next time snapshot_save() was called,
00629 ** take a snapshot.
00630 */
00631 
00632 void snapshot_needed()
00633 {
00634        index_dirty=1;
00635 }
00636 
00637 /*
00638 ** Save a snapshot, if the folder was changed.
00639 */
00640 
00641 void snapshot_save()
00642 {
00643        int rc;
00644        struct maildir_tmpcreate_info createInfo;
00645        FILE *fp;
00646        unsigned long i;
00647        const char *q;
00648 
00649        if (!index_dirty || !snapshots_enabled)
00650               return;
00651 
00652        index_dirty=0;
00653 
00654        maildir_tmpcreate_init(&createInfo);
00655 
00656        createInfo.maildir=current_mailbox;
00657        createInfo.uniq="snapshot";
00658        createInfo.hostname=getenv("HOSTNAME");
00659        createInfo.doordie=1;
00660 
00661        if ((rc=maildir_tmpcreate_fd(&createInfo)) < 0)
00662        {
00663               perror("maildir_tmpcreate_fd");
00664               return;
00665        }
00666        close(rc);
00667 
00668        q=strrchr(createInfo.tmpname, '/'); /* Always there */
00669 
00670        free(createInfo.newname);
00671        createInfo.newname=malloc(strlen(snapshot_dir)+strlen(q)+2);
00672 
00673        if (!createInfo.newname)
00674        {
00675               unlink(createInfo.tmpname);
00676               maildir_tmpcreate_free(&createInfo);
00677               perror("malloc");
00678               return;
00679        }
00680 
00681        strcat(strcat(strcpy(createInfo.newname, snapshot_dir), "/"), q);
00682 
00683        if ((fp=fopen(createInfo.tmpname, "w")) == NULL)
00684        {
00685               perror(createInfo.tmpname);
00686               maildir_tmpcreate_free(&createInfo);
00687               return;
00688        }
00689 
00690        fprintf(fp, "%d %lu %lu %lu", SNAPSHOTVERSION,
00691               current_maildir_info.nmessages,
00692               current_maildir_info.uidv,
00693               current_maildir_info.nextuid);
00694        if (snapshot_cur)
00695               fprintf(fp, ":%s", strrchr(snapshot_cur, '/')+1);
00696        fprintf(fp, "\n");
00697 
00698        for (i=0; i<current_maildir_info.nmessages; i++)
00699        {
00700               struct imapscanmessageinfo *p=current_maildir_info.msgs + i;
00701               q=strrchr(p->filename, MDIRSEP[0]);
00702 
00703               fprintf(fp, "%lu:%s\n", p->uid, q ? q+1:"");
00704        }
00705 
00706        if (keywords())
00707               imapscan_saveKeywordSnapshot(fp, &current_maildir_info);
00708 
00709        if (fflush(fp) < 0 || ferror(fp) < 0)
00710        {
00711               fclose(fp);
00712               perror(createInfo.tmpname);
00713               unlink(createInfo.tmpname);
00714               maildir_tmpcreate_free(&createInfo);
00715               return;
00716        }
00717        fclose(fp);
00718        if (rename(createInfo.tmpname, createInfo.newname) < 0)
00719        {
00720               perror(createInfo.tmpname);
00721               unlink(createInfo.tmpname);
00722               maildir_tmpcreate_free(&createInfo);
00723               return;
00724        }
00725        if (snapshot_last)
00726        {
00727               unlink(snapshot_last); /* Obsolete snapshot */
00728               free(snapshot_last);
00729        }
00730 
00731        snapshot_last=snapshot_cur;
00732        snapshot_cur=createInfo.newname;
00733        createInfo.newname=NULL;
00734        maildir_tmpcreate_free(&createInfo);
00735 
00736        writes("* SNAPSHOT ");
00737        smapword(strrchr(snapshot_cur, '/')+1);
00738        writes("\n");
00739 }