summaryrefslogtreecommitdiffstats
path: root/imap/smapsnapshot.c
diff options
context:
space:
mode:
authorSam Varshavchik2013-08-19 16:39:41 -0400
committerSam Varshavchik2013-08-25 14:43:51 -0400
commit9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch)
tree7a81a04cb51efb078ee350859a64be2ebc6b8813 /imap/smapsnapshot.c
parenta9520698b770168d1f33d6301463bb70a19655ec (diff)
downloadcourier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2
Initial checkin
Imported from subversion report, converted to git. Updated all paths in scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'imap/smapsnapshot.c')
-rw-r--r--imap/smapsnapshot.c739
1 files changed, 739 insertions, 0 deletions
diff --git a/imap/smapsnapshot.c b/imap/smapsnapshot.c
new file mode 100644
index 0000000..805fd07
--- /dev/null
+++ b/imap/smapsnapshot.c
@@ -0,0 +1,739 @@
+/*
+** Copyright 2003 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <signal.h>
+#include <fcntl.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_UTIME_H
+#include <utime.h>
+#endif
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "mysignal.h"
+#include "imapd.h"
+#include "imapscanclient.h"
+#include "imapwrite.h"
+
+#include "maildir/config.h"
+#include "maildir/maildircreate.h"
+#include "maildir/maildirrequota.h"
+#include "maildir/maildirgetquota.h"
+#include "maildir/maildirquota.h"
+#include "maildir/maildirmisc.h"
+#include "maildir/maildirwatch.h"
+
+#if HAVE_DIRENT_H
+#include <dirent.h>
+#define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+#define dirent direct
+#define NAMLEN(dirent) (dirent)->d_namlen
+#if HAVE_SYS_NDIR_H
+#include <sys/ndir.h>
+#endif
+#if HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif
+#if HAVE_NDIR_H
+#include <ndir.h>
+#endif
+#endif
+
+
+extern int keywords();
+
+/*
+** Implement SMAP snapshots. A snapshot is implemented, essentially, by
+** saving the current folder index, restoring it, then doing a noop().
+**
+** The snapshot file saves uids, not complete filenames. The complete
+** filenames are already in courierimapuiddb. Filenames are long, and saving
+** them can result in huge snapshot files for large folders. So only uids
+** are saved, and when the snapshot is restored the courierimapuiddb file is
+** read to obtain the filenames.
+*/
+
+extern char *current_mailbox;
+extern struct imapscaninfo current_maildir_info;
+extern char *readline(unsigned i, FILE *);
+
+static char *snapshot_dir; /* Directory with snapshots */
+
+static char *snapshot_last; /* Last snapshot */
+static char *snapshot_cur; /* Current snapshot */
+
+static int index_dirty;
+static int snapshots_enabled;
+
+extern void set_time(const char *tmpname, time_t timestamp);
+extern void smapword(const char *);
+
+struct snapshot_list {
+ struct snapshot_list *next;
+
+ char *filename;
+ char *prev;
+ time_t mtime;
+};
+
+/*
+** When cleaning up a snapshot directory, we need to know whether there's
+** a later snapshot that claims that this snapshot is the previous
+** snapshot (we can safely dump snapshots that were previous snapshots
+** of previous snapshots).
+*/
+
+static struct snapshot_list *find_next_snapshot(struct snapshot_list *s,
+ const char *n)
+{
+ const char *p, *q;
+
+ p=strrchr(n, '/');
+
+ if (p)
+ n=p+1;
+
+ while (s)
+ {
+ p=s->prev;
+ q=p ? strrchr(p, '/'):NULL;
+
+ if (q && strcmp(q+1, n) == 0)
+ return s;
+
+ s=s->next;
+ }
+ return NULL;
+}
+
+/*
+** Delete a snapshot structure, and the actual file
+*/
+
+static void delete_snapshot(struct snapshot_list *snn)
+{
+ char *p=malloc(strlen(snapshot_dir)+strlen(snn->filename)+2);
+
+ if (p)
+ {
+ strcat(strcat(strcpy(p, snapshot_dir), "/"), snn->filename);
+ unlink(p);
+ }
+
+ free(snn->filename);
+ free(snn->prev);
+ free(snn);
+}
+
+/*
+** Restore a snapshot
+*/
+
+static int restore_snapshot2(const char *snapshot_dir,
+ FILE *fp,
+ struct imapscaninfo *new_index);
+
+/*
+** Part 1: process the first header line of a snapshot file, and allocate a
+** new folder index list.
+*/
+
+static int restore_snapshot(const char *dir, FILE *snapshot_fp,
+ char **last_snapshot)
+{
+ int format;
+ unsigned long s_nmessages, s_uidv, s_nextuid;
+ char *p;
+ char *buf;
+ struct imapscaninfo new_index;
+
+ if ((buf=readline(0, snapshot_fp)) == NULL)
+ return 0;
+
+ p=strchr(buf, ':');
+ if (p)
+ *p++=0;
+
+ *last_snapshot=NULL;
+
+ if (sscanf(buf, "%d %lu %lu %lu", &format, &s_nmessages, &s_uidv,
+ &s_nextuid) != 4 || format != SNAPSHOTVERSION)
+ return 0; /* Don't recognize the header */
+
+ /* Save the previous snapshot ID */
+
+ if (p)
+ {
+ *last_snapshot=malloc(strlen(dir)+strlen(p)+2);
+
+ if (!last_snapshot)
+ {
+ write_error_exit(0);
+ return 0;
+ }
+
+ strcat(strcat(strcpy(*last_snapshot, dir), "/"), p);
+ }
+
+ imapscan_init(&new_index);
+
+ if (s_nmessages && (new_index.msgs=(struct imapscanmessageinfo *)
+ malloc(s_nmessages * sizeof(*new_index.msgs)))
+ == 0)
+ {
+ write_error_exit(0);
+ return (0);
+ }
+ memset(new_index.msgs, 0, s_nmessages * sizeof(*new_index.msgs));
+
+ new_index.nmessages=s_nmessages;
+ new_index.uidv=s_uidv;
+ new_index.nextuid=s_nextuid;
+
+ if (restore_snapshot2(dir, snapshot_fp, &new_index))
+ {
+ imapscan_copy(&current_maildir_info, &new_index);
+ imapscan_free(&new_index);
+ return 1;
+ }
+ imapscan_free(&new_index);
+
+ if (*last_snapshot)
+ {
+ free(*last_snapshot);
+ *last_snapshot=0;
+ }
+
+ return 0;
+}
+
+/*
+** Part 2: combine the snapshot and courierimapuiddb, create a halfbaked
+** index from the combination.
+*/
+
+static int restore_snapshot2(const char *snapshot_dir,
+ FILE *fp,
+ struct imapscaninfo *new_index)
+{
+ unsigned long i;
+ char *p=malloc(strlen(snapshot_dir) + sizeof("/../" IMAPDB));
+ FILE *courierimapuiddb;
+ int version;
+ unsigned long uidv;
+ unsigned long nextuid;
+ char *uid_line;
+ unsigned long uid=0;
+
+ if (!p)
+ {
+ write_error_exit(0);
+ return 0;
+ }
+
+ strcat(strcpy(p, snapshot_dir), "/../" IMAPDB);
+
+ courierimapuiddb=fopen(p, "r");
+ free(p);
+
+ if (!courierimapuiddb)
+ return 0; /* Can't open the uiddb file, no dice */
+
+ if ((p=readline(0, courierimapuiddb)) == NULL ||
+ sscanf(p, "%d %lu %lu", &version, &uidv, &nextuid) != 3 ||
+ version != IMAPDBVERSION /* Do not recognize the uiddb file */
+
+ || uidv != new_index->uidv /* Something major happened, abort */ )
+ {
+ fclose(courierimapuiddb);
+ return 0;
+ }
+
+ uid_line=readline(0, courierimapuiddb);
+
+ if (uid_line)
+ {
+ if (sscanf(uid_line, "%lu", &uid) != 1)
+ {
+ fclose(courierimapuiddb);
+ return 0;
+ }
+ }
+
+ /*
+ ** Both the snapshot file and courierimapuiddb should be in sorted
+ ** order, by UIDs, rely on that and do what amounts to a merge sort.
+ */
+
+ for (i=0; i<new_index->nmessages; i++)
+ {
+ unsigned long s_uid;
+ char flag_buf[128];
+
+ p=fgets(flag_buf, sizeof(flag_buf)-1, fp);
+
+ if (p == NULL || (p=strchr(p, '\n')) == NULL ||
+ (*p = 0, sscanf(flag_buf, "%lu", &s_uid)) != 1 ||
+ (p=strchr(flag_buf, ':')) == NULL) /* Corrupted file */
+ {
+ fclose(courierimapuiddb);
+ return 0;
+ }
+
+ new_index->msgs[i].uid=s_uid;
+
+ /* Try to fill in the filenames to as much of an extent as
+ ** possible. If IMAPDB no longer has a particular uid listed,
+ ** that's ok, because the message is now gone, so we just
+ ** insert an empty filename, which will be expunged by
+ ** noop() processing, after the snapshot is restored.
+ */
+
+ while (uid_line && uid <= s_uid)
+ {
+ if (uid == s_uid &&
+ (uid_line=strchr(uid_line, ' ')) != NULL)
+ /* Jackpot */
+ {
+ new_index->msgs[i].filename=
+ malloc(strlen(uid_line)+
+ strlen(flag_buf)+2);
+
+ if (!new_index->msgs[i].filename)
+ {
+ fclose(courierimapuiddb);
+ write_error_exit(0);
+ return 0;
+ }
+
+ strcpy(new_index->msgs[i].filename,
+ uid_line+1);
+
+ if (p)
+ {
+ strcat(strcat(new_index->msgs[i]
+ .filename, MDIRSEP),
+ p+1);
+ }
+ }
+
+ uid_line=readline(0, courierimapuiddb);
+
+ if (uid_line)
+ {
+ if (sscanf(uid_line, "%lu", &uid) != 1)
+ {
+ fclose(courierimapuiddb);
+ return 0;
+ }
+ }
+ }
+
+
+ if (new_index->msgs[i].filename == 0)
+ {
+ new_index->msgs[i].filename=strdup("");
+ /* A noop should get rid of this entry anyway */
+
+ if (!new_index->msgs[i].filename)
+ {
+ fclose(courierimapuiddb);
+ write_error_exit(0);
+ return 0;
+ }
+ }
+ }
+
+ fclose(courierimapuiddb);
+ if (keywords())
+ imapscan_restoreKeywordSnapshot(fp, new_index);
+ return 1;
+}
+
+void snapshot_select(int flag)
+{
+ snapshots_enabled=flag;
+}
+
+/*
+** Initialize snapshots for an opened folder.
+**
+** Parameters:
+**
+** folder - the path to a folder that's in the process of opening.
+**
+** snapshot - not NULL if the client requested a snapshot restore.
+**
+** Exit code:
+**
+** When a snapshot is requested, a non-zero exit code means that the
+** snapshot has been succesfully restored, and current_mailbox is now
+** initialized based on the snapshot. A zero exit code means that the
+** snapshot has not been restored, and snapshot_init() needs to be called
+** again with snapshot=NULL in order to initialize the snapshot structures.
+**
+** When a snapshot is not requested, the exit code is always 0
+*/
+
+int snapshot_init(const char *folder, const char *snapshot)
+{
+ struct snapshot_list *sl=NULL;
+ DIR *dirp;
+ struct dirent *de;
+ struct snapshot_list *snn, **ptr;
+ int cnt;
+ char *new_dir;
+ int rc=0;
+ char *new_snapshot_cur=NULL;
+ char *new_snapshot_last=NULL;
+
+ if ((new_dir=malloc(strlen(folder)+sizeof("/" SNAPSHOTDIR)))
+ == NULL)
+ {
+ write_error_exit(0);
+ return rc;
+ }
+
+ strcat(strcpy(new_dir, folder), "/" SNAPSHOTDIR);
+ mkdir(new_dir, 0755); /* Create, if doesn't exist */
+
+ if (snapshot)
+ {
+ FILE *fp;
+
+ if (*snapshot == 0 || strchr(snapshot, '/') ||
+ *snapshot == '.') /* Monkey business */
+ {
+ free(new_dir);
+ return 0;
+ }
+
+ new_snapshot_cur=malloc(strlen(new_dir) +
+ strlen(snapshot) + 2);
+
+ if (!new_snapshot_cur)
+ {
+ free(new_dir);
+ write_error_exit(0);
+ return rc;
+ }
+
+ strcat(strcat(strcpy(new_snapshot_cur, new_dir), "/"),
+ snapshot);
+
+ if ((fp=fopen(new_snapshot_cur, "r")) != NULL &&
+ restore_snapshot(new_dir, fp, &new_snapshot_last))
+ {
+ set_time(new_snapshot_cur, time(NULL));
+ rc=1; /* We're good to go. Finish everything else */
+ }
+
+ if (fp)
+ {
+ fclose(fp);
+ fp=NULL;
+ }
+
+ if (!rc) /* Couldn't get the snapshot, abort */
+ {
+ free(new_snapshot_cur);
+ free(new_dir);
+ return 0;
+ }
+ }
+
+ if (snapshot_dir) free(snapshot_dir);
+ if (snapshot_last) free(snapshot_last);
+ if (snapshot_cur) free(snapshot_cur);
+
+ snapshot_dir=NULL;
+ snapshot_last=new_snapshot_last;
+ snapshot_cur=new_snapshot_cur;
+
+ snapshot_dir=new_dir;
+
+ index_dirty=1;
+
+ /* Get rid of old snapshots as follows */
+
+ /* Step 1, compile a list of snapshots, sorted in mtime order */
+
+ dirp=opendir(snapshot_dir);
+
+ while (dirp && (de=readdir(dirp)) != NULL)
+ {
+ FILE *fp;
+ struct stat stat_buf;
+
+ char *n;
+
+ if (de->d_name[0] == '.') continue;
+
+ n=malloc(strlen(snapshot_dir)+strlen(de->d_name)+2);
+ if (!n) break; /* Furrfu */
+
+ strcat(strcat(strcpy(n, snapshot_dir), "/"), de->d_name);
+
+ fp=fopen(n, "r");
+
+ if (fp)
+ {
+ char buf[1024];
+
+ if (fgets(buf, sizeof(buf)-1, fp) != NULL &&
+ fstat(fileno(fp), &stat_buf) == 0)
+ {
+ char *p=strchr(buf, '\n');
+ int fmt;
+
+ if (p) *p=0;
+
+ p=strchr(buf, ':');
+
+ if (p)
+ *p++=0;
+
+
+ if (sscanf(buf, "%d", &fmt) == 1 &&
+ fmt == SNAPSHOTVERSION)
+ {
+ snn=malloc(sizeof(*sl));
+
+ if (snn) memset(snn, 0, sizeof(*snn));
+
+ if (snn == NULL ||
+ (snn->filename=strdup(de->d_name))
+ == NULL ||
+ (snn->prev=strdup(p ? p:""))
+ == NULL)
+ {
+ if (snn && snn->filename)
+ free(snn->filename);
+ if (snn)
+ free(snn);
+
+ snn=NULL;
+ }
+
+ if (snn)
+ {
+ snn->mtime=stat_buf.st_mtime;
+
+ for (ptr= &sl; *ptr;
+ ptr=&(*ptr)->next)
+ {
+ if ( (*ptr)->mtime >
+ snn->mtime)
+ break;
+ }
+
+ snn->next= *ptr;
+ *ptr=snn;
+ }
+ free(n);
+ n=NULL;
+ }
+
+ }
+ fclose(fp);
+ }
+ if (n)
+ {
+ unlink(n);
+ free(n);
+ }
+ }
+ if (dirp)
+ closedir(dirp);
+
+ /* Step 2: drop snapshots that are definitely obsolete */
+
+ for (ptr= &sl; *ptr; )
+ {
+ if ((snn=find_next_snapshot(sl, (*ptr)->filename)) &&
+ find_next_snapshot(sl, snn->filename))
+ {
+ snn= *ptr;
+
+ *ptr=snn->next;
+
+ delete_snapshot(snn);
+ }
+ else
+ ptr=&(*ptr)->next;
+
+ }
+
+ /* If there are more than 10 snapshots, drop older snapshots */
+
+ cnt=0;
+ for (snn=sl; snn; snn=snn->next)
+ ++cnt;
+
+ if (cnt > 10)
+ {
+ time_t now=time(NULL);
+
+ while (sl && sl->mtime < now &&
+ (now - sl->mtime) > 60 * 60 * 24 * (7 + (cnt-10)*2))
+ {
+ snn=sl;
+ sl=sl->next;
+ delete_snapshot(snn);
+ --cnt;
+ }
+ }
+
+ /* All right, put a lid on 50 snapshots */
+
+ while (cnt > 50)
+ {
+ snn=sl;
+ sl=sl->next;
+ delete_snapshot(snn);
+ --cnt;
+ }
+
+ return rc;
+}
+
+/*
+** Something changed in the folder, so next time snapshot_save() was called,
+** take a snapshot.
+*/
+
+void snapshot_needed()
+{
+ index_dirty=1;
+}
+
+/*
+** Save a snapshot, if the folder was changed.
+*/
+
+void snapshot_save()
+{
+ int rc;
+ struct maildir_tmpcreate_info createInfo;
+ FILE *fp;
+ unsigned long i;
+ const char *q;
+
+ if (!index_dirty || !snapshots_enabled)
+ return;
+
+ index_dirty=0;
+
+ maildir_tmpcreate_init(&createInfo);
+
+ createInfo.maildir=current_mailbox;
+ createInfo.uniq="snapshot";
+ createInfo.hostname=getenv("HOSTNAME");
+ createInfo.doordie=1;
+
+ if ((rc=maildir_tmpcreate_fd(&createInfo)) < 0)
+ {
+ perror("maildir_tmpcreate_fd");
+ return;
+ }
+ close(rc);
+
+ q=strrchr(createInfo.tmpname, '/'); /* Always there */
+
+ free(createInfo.newname);
+ createInfo.newname=malloc(strlen(snapshot_dir)+strlen(q)+2);
+
+ if (!createInfo.newname)
+ {
+ unlink(createInfo.tmpname);
+ maildir_tmpcreate_free(&createInfo);
+ perror("malloc");
+ return;
+ }
+
+ strcat(strcat(strcpy(createInfo.newname, snapshot_dir), "/"), q);
+
+ if ((fp=fopen(createInfo.tmpname, "w")) == NULL)
+ {
+ perror(createInfo.tmpname);
+ maildir_tmpcreate_free(&createInfo);
+ return;
+ }
+
+ fprintf(fp, "%d %lu %lu %lu", SNAPSHOTVERSION,
+ current_maildir_info.nmessages,
+ current_maildir_info.uidv,
+ current_maildir_info.nextuid);
+ if (snapshot_cur)
+ fprintf(fp, ":%s", strrchr(snapshot_cur, '/')+1);
+ fprintf(fp, "\n");
+
+ for (i=0; i<current_maildir_info.nmessages; i++)
+ {
+ struct imapscanmessageinfo *p=current_maildir_info.msgs + i;
+ q=strrchr(p->filename, MDIRSEP[0]);
+
+ fprintf(fp, "%lu:%s\n", p->uid, q ? q+1:"");
+ }
+
+ if (keywords())
+ imapscan_saveKeywordSnapshot(fp, &current_maildir_info);
+
+ if (fflush(fp) < 0 || ferror(fp) < 0)
+ {
+ fclose(fp);
+ perror(createInfo.tmpname);
+ unlink(createInfo.tmpname);
+ maildir_tmpcreate_free(&createInfo);
+ return;
+ }
+ fclose(fp);
+ if (rename(createInfo.tmpname, createInfo.newname) < 0)
+ {
+ perror(createInfo.tmpname);
+ unlink(createInfo.tmpname);
+ maildir_tmpcreate_free(&createInfo);
+ return;
+ }
+ if (snapshot_last)
+ {
+ unlink(snapshot_last); /* Obsolete snapshot */
+ free(snapshot_last);
+ }
+
+ snapshot_last=snapshot_cur;
+ snapshot_cur=createInfo.newname;
+ createInfo.newname=NULL;
+ maildir_tmpcreate_free(&createInfo);
+
+ writes("* SNAPSHOT ");
+ smapword(strrchr(snapshot_cur, '/')+1);
+ writes("\n");
+}