Viewing file:
smapsnapshot.c (14.29 KB) -rw-rw-rw-Select action/file-type:

(
+) |

(
+) |

(
+) |
Code (
+) |
Session (
+) |

(
+) |
SDB (
+) |

(
+) |

(
+) |

(
+) |

(
+) |

(
+) |
/*
** 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");
}