diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 |
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 |
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /imap/smapsnapshot.c | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-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.c | 739 |
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(¤t_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, ¤t_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"); +} |
