Here's my modified patch against CVS. I suppose some code could be
added to statuscache_lookup() to read both v.2 and v.3. There isn't any
reason to invalidate all v.2 caches just because they don't have
highestmodseq
--
Kenneth Murchison
Systems Programmer
Project Cyrus Developer/Maintainer
Carnegie Mellon University
Index: doc/changes.html
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/doc/changes.html,v
retrieving revision 1.152
diff -u -r1.152 changes.html
--- doc/changes.html 17 Jan 2008 18:51:39 -0000 1.152
+++ doc/changes.html 18 Jan 2008 04:16:11 -0000
@@ -10,6 +10,10 @@
<h1>Changes to the Cyrus IMAP Server since 2.3.11</h1>
<ul>
+<li>Added <tt>statuscache.db</tt> to cache IMAP STATUS data which
+significantly reduces the amount of I/O necessary when neither the
+mailbox nor \Seen state has changed
+<i>-- courtesy of Fastmail.fm</i>.</li>
<li>Added option to <tt>unexpunge</tt> to restore messages by time
interval
<i>-- courtesy of David Carter</i>.</li>
Index: imap/Makefile.in
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/Makefile.in,v
retrieving revision 1.191
diff -u -r1.191 Makefile.in
--- imap/Makefile.in 18 Oct 2007 18:48:02 -0000 1.191
+++ imap/Makefile.in 18 Jan 2008 04:16:11 -0000
@@ -101,7 +101,8 @@
convert_code.o duplicate.o saslclient.o saslserver.o signals.o \
annotate.o search_engines.o squat.o squat_internal.o mbdump.o \
imapparse.o telemetry.o user.o notify.o protocol.o idle.o quota_db.o \
- sync_log.o $(SEEN) mboxkey.o backend.o tls.o message_guid.o
+ sync_log.o $(SEEN) mboxkey.o backend.o tls.o message_guid.o \
+ statuscache_db.o
IMAPDOBJS=pushstats.o imapd.o proxy.o imap_proxy.o index.o version.o
Index: imap/ctl_cyrusdb.c
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/ctl_cyrusdb.c,v
retrieving revision 1.30
diff -u -r1.30 ctl_cyrusdb.c
--- imap/ctl_cyrusdb.c 4 Jan 2008 12:24:05 -0000 1.30
+++ imap/ctl_cyrusdb.c 18 Jan 2008 04:16:11 -0000
@@ -80,6 +80,7 @@
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "seen.h"
+#include "statuscache.h"
#include "tls.h"
#include "util.h"
#include "xmalloc.h"
@@ -99,7 +100,8 @@
{ FNAME_ANNOTATIONS, &config_annotation_db, 1 },
{ FNAME_DELIVERDB, &config_duplicate_db, 0 },
{ FNAME_TLSSESSIONS, &config_tlscache_db, 0 },
- { FNAME_PTSDB, &config_ptscache_db, 0 },
+ { FNAME_PTSDB, &config_ptscache_db, 0 },
+ { FNAME_STATUSCACHEDB, &config_statuscache_db, 0 },
{ NULL, NULL, 0 }
};
Index: imap/global.c
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/global.c,v
retrieving revision 1.24
diff -u -r1.24 global.c
--- imap/global.c 28 Sep 2007 02:27:46 -0000 1.24
+++ imap/global.c 18 Jan 2008 04:16:11 -0000
@@ -95,6 +95,7 @@
struct cyrusdb_backend *config_duplicate_db;
struct cyrusdb_backend *config_tlscache_db;
struct cyrusdb_backend *config_ptscache_db;
+struct cyrusdb_backend *config_statuscache_db;
/* Called before a cyrus application starts (but after command line parameters
* are read) */
@@ -190,6 +191,8 @@
cyrusdb_fromname(config_getstring(IMAPOPT_TLSCACHE_DB));
config_ptscache_db =
cyrusdb_fromname(config_getstring(IMAPOPT_PTSCACHE_DB));
+ config_statuscache_db =
+ cyrusdb_fromname(config_getstring(IMAPOPT_STATUSCACHE_DB));
/* configure libcyrus as needed */
libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, config_dir);
Index: imap/global.h
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/global.h,v
retrieving revision 1.9
diff -u -r1.9 global.h
--- imap/global.h 27 Mar 2007 19:53:08 -0000 1.9
+++ imap/global.h 18 Jan 2008 04:16:11 -0000
@@ -154,5 +154,6 @@
extern struct cyrusdb_backend *config_duplicate_db;
extern struct cyrusdb_backend *config_tlscache_db;
extern struct cyrusdb_backend *config_ptscache_db;
+extern struct cyrusdb_backend *config_statuscache_db;
#endif /* INCLUDED_GLOBAL_H */
Index: imap/imapd.c
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/imapd.c,v
retrieving revision 1.539
diff -u -r1.539 imapd.c
--- imap/imapd.c 17 Jan 2008 13:07:40 -0000 1.539
+++ imap/imapd.c 18 Jan 2008 04:16:12 -0000
@@ -95,6 +95,7 @@
#include "mkgmtime.h"
#include "mupdate-client.h"
#include "quota.h"
+#include "statuscache.h"
#include "sync_log.h"
#include "telemetry.h"
#include "tls.h"
@@ -685,6 +686,10 @@
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
+ if (config_getswitch(IMAPOPT_STATUSCACHE)) {
+ statuscache_open(NULL);
+ }
+
/* Create a protgroup for input from the client and selected backend */
protin = protgroup_new(2);
@@ -866,6 +871,11 @@
annotatemore_close();
annotatemore_done();
+ if (config_getswitch(IMAPOPT_STATUSCACHE)) {
+ statuscache_close();
+ statuscache_done();
+ }
+
if (imapd_in) {
/* Flush the incoming buffer */
prot_NONBLOCK(imapd_in);
@@ -6747,21 +6757,19 @@
void cmd_status(char *tag, char *name)
{
int c;
- int statusitems = 0;
+ unsigned statusitems = 0;
static struct buf arg;
- struct mailbox mailbox;
char mailboxname[MAX_MAILBOX_NAME+1];
int mbtype;
- char *server;
+ char *server, *acl;
int r = 0;
- int doclose = 0;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL,
- &server, NULL, NULL);
+ &server, &acl, NULL);
}
if (r == IMAP_MAILBOX_MOVED) {
/* Eat the argument */
@@ -6864,24 +6872,18 @@
}
if (!r) {
- r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox);
- }
+ int myrights = cyrus_acl_myrights(imapd_authstate, acl);
- if (!r) {
- doclose = 1;
- r = mailbox_open_index(&mailbox);
- }
- if (!r && !(mailbox.myrights & ACL_READ)) {
- r = (imapd_userisadmin || (mailbox.myrights & ACL_LOOKUP)) ?
- IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
+ if (!(myrights & ACL_READ)) {
+ r = (imapd_userisadmin || (myrights & ACL_LOOKUP)) ?
+ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
+ }
}
if (!r) {
- r = index_status(&mailbox, name, statusitems);
+ r = index_status(mailboxname, name, statusitems);
}
- if (doclose) mailbox_close(&mailbox);
-
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
Index: imap/imapd.h
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/imapd.h,v
retrieving revision 1.69
diff -u -r1.69 imapd.h
--- imap/imapd.h 17 Jan 2008 13:07:40 -0000 1.69
+++ imap/imapd.h 18 Jan 2008 04:16:12 -0000
@@ -272,8 +272,7 @@
struct searchargs *searchargs, int usinguid);
extern int index_copy(struct mailbox *mailbox, char *sequence,
int usinguid, char *name, char **copyuidp, int nolink);
-extern int index_status(struct mailbox *mailbox, char *name,
- int statusitems);
+extern int index_status(char *mboxname, char *name, unsigned statusitems);
extern int index_getuids(struct mailbox *mailbox, unsigned lowuid);
extern int index_getstate(struct mailbox *mailbox);
Index: imap/index.c
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/index.c,v
retrieving revision 1.241
diff -u -r1.241 index.c
--- imap/index.c 17 Jan 2008 13:07:40 -0000 1.241
+++ imap/index.c 18 Jan 2008 04:16:12 -0000
@@ -69,10 +69,12 @@
#include "lsort.h"
#include "mailbox.h"
#include "map.h"
+#include "mboxlist.h"
#include "message.h"
#include "parseaddr.h"
#include "search_engines.h"
#include "seen.h"
+#include "statuscache.h"
#include "strhash.h"
#include "stristr.h"
#include "util.h"
@@ -1550,53 +1552,107 @@
/*
* Performs a STATUS command
*/
-int
-index_status(mailbox, name, statusitems)
-struct mailbox *mailbox;
-char *name;
-int statusitems;
+int index_status(char *mboxname, char *name, unsigned statusitems)
{
int r;
- struct seen *status_seendb;
- time_t last_read, last_change = 0;
- unsigned last_uid;
- char *last_seenuids;
+ struct statuscache_data scdata;
+ struct mailbox mailbox;
+ int doclose = 0;
int num_recent = 0;
int num_unseen = 0;
int sepchar;
static struct seq_set seq_set = { NULL, 0, 0, 0 , NULL};
- if (mailbox->exists != 0 &&
- (statusitems &
- (STATUS_RECENT | STATUS_UNSEEN))) {
- r = seen_open(mailbox,
- (mailbox->options & OPT_IMAP_SHAREDSEEN) ? "anyone" :
+ /* Check status cache if possible */
+ if (config_getswitch(IMAPOPT_STATUSCACHE)) {
+ /* Do actual lookup of cache item. */
+ r = statuscache_lookup(mboxname, imapd_userid, &scdata);
+
+ /* We need to find everything we're looking for in the cache,
+ otherwise we fallback to the regular method */
+ if (!r && (scdata.statusitems & statusitems) != statusitems) {
+ r = IMAP_NO_NOSUCHMSG;
+ }
+
+ if (!r) {
+ /* If index file has changed, fallback to regular method */
+ char *path, *mpath;
+ struct stat index;
+
+ /* Get path to mailbox */
+ r = mboxlist_detail(mboxname, NULL, &path, &mpath, NULL, NULL, NULL);
+ if (!r) r = mailbox_stat(path, mpath, NULL, &index, NULL);
+
+ if (!r &&
+ (index.st_mtime != scdata.message_mtime ||
+ index.st_ino != scdata.message_ino ||
+ index.st_size != scdata.message_size)) {
+ r = IMAP_NO_NOSUCHMSG;
+ }
+
+ /* Seen/recent status uses "push" invalidation events from
+ * seen_db.c. This avoids needing to open cyrus.header to get
+ * the mailbox uniqueid to open the seen db and get the
+ * unseen_mtime and recentuid.
+ */
+ }
+
+ if (!r) {
+ syslog(LOG_DEBUG, "statuscache, '%s', '%s', '0x%02x', 'yes'",
+ mboxname, imapd_userid, statusitems);
+ goto statusdone;
+ }
+
+ syslog(LOG_DEBUG, "statuscache, '%s', '%s', '0x%02x', 'no'",
+ mboxname, imapd_userid, statusitems);
+ }
+
+ /* Missing or invalid cache entry */
+ r = mailbox_open_header(mboxname, imapd_authstate, &mailbox);
+
+ if (!r) {
+ doclose = 1;
+ r = mailbox_open_index(&mailbox);
+ }
+
+ if (!r && mailbox.exists != 0 &&
+ (statusitems & (STATUS_RECENT | STATUS_UNSEEN))) {
+ /* Read \Seen state */
+ struct seen *status_seendb;
+ time_t last_read, last_change = 0;
+ unsigned last_uid;
+ char *last_seenuids;
+
+ r = seen_open(&mailbox,
+ (mailbox.options & OPT_IMAP_SHAREDSEEN) ? "anyone" :
imapd_userid,
SEEN_CREATE, &status_seendb);
- if (r) return r;
- r = seen_lockread(status_seendb, &last_read, &last_uid,
- &last_change, &last_seenuids);
- seen_close(status_seendb);
- if (r) return r;
+ if (!r) {
+ r = seen_lockread(status_seendb, &last_read, &last_uid,
+ &last_change, &last_seenuids);
+ seen_close(status_seendb);
+ }
- if (statusitems & (STATUS_RECENT | STATUS_UNSEEN)) {
+ if (!r) {
const char *base;
unsigned long len = 0;
unsigned msg, uid;
- map_refresh(mailbox->index_fd, 0, &base, &len,
- mailbox->start_offset +
- mailbox->exists * mailbox->record_size,
- "index", mailbox->name);
+ map_refresh(mailbox.index_fd, 0, &base, &len,
+ mailbox.start_offset +
+ mailbox.exists * mailbox.record_size,
+ "index", mailbox.name);
seq_set.len = seq_set.mark = 0;
index_parse_sequence(last_seenuids, 0, &seq_set);
- for (msg = 0; msg < mailbox->exists; msg++) {
- uid = ntohl(*((bit32 *)(base + mailbox->start_offset +
- msg * mailbox->record_size +
+ for (msg = 0; msg < mailbox.exists; msg++) {
+ uid = ntohl(*((bit32 *)(base + mailbox.start_offset +
+ msg * mailbox.record_size +
OFFSET_UID)));
+ /* Always calculate num_recent,
+ even if only need num_unseen... for caching below */
if (uid > last_uid) num_recent++;
if ((statusitems & STATUS_UNSEEN) &&
!index_insequence(uid, &seq_set, 1)) num_unseen++;
@@ -1609,36 +1665,57 @@
}
}
+ if (!r) {
+ /* We always have message count, uidnext,
+ uidvalidity, and highestmodseq for cache */
+ unsigned c_statusitems = statusitems | STATUS_MESSAGES |
+ STATUS_UIDNEXT | STATUS_UIDVALIDITY | STATUS_HIGHESTMODSEQ;
+
+ /* If we calculated num_unseen, we implicitly calculated num_recent */
+ if (c_statusitems & STATUS_UNSEEN) c_statusitems |= STATUS_RECENT;
+
+ statuscache_fill(&scdata, &mailbox,
+ c_statusitems, num_recent, num_unseen);
+ }
+
+ if (doclose) mailbox_close(&mailbox);
+ if (r) return r;
+
+ /* Upate the statuscache entry */
+ if (config_getswitch(IMAPOPT_STATUSCACHE)) {
+ statuscache_update(mboxname, imapd_userid, &scdata);
+ }
+
+ statusdone:
prot_printf(imapd_out, "* STATUS ");
printastring(name);
prot_printf(imapd_out, " ");
sepchar = '(';
if (statusitems & STATUS_MESSAGES) {
- prot_printf(imapd_out, "%cMESSAGES %lu", sepchar, mailbox->exists);
+ prot_printf(imapd_out, "%cMESSAGES %lu", sepchar, scdata.c_messages);
sepchar = ' ';
}
if (statusitems & STATUS_RECENT) {
- prot_printf(imapd_out, "%cRECENT %u", sepchar, num_recent);
+ prot_printf(imapd_out, "%cRECENT %u", sepchar, scdata.c_recent);
sepchar = ' ';
}
if (statusitems & STATUS_UIDNEXT) {
- prot_printf(imapd_out, "%cUIDNEXT %lu", sepchar, mailbox->last_uid+1);
+ prot_printf(imapd_out, "%cUIDNEXT %lu", sepchar, scdata.c_uidnext);
sepchar = ' ';
}
if (statusitems & STATUS_UIDVALIDITY) {
prot_printf(imapd_out, "%cUIDVALIDITY %lu", sepchar,
- mailbox->uidvalidity);
+ scdata.c_uidvalidity);
sepchar = ' ';
}
if (statusitems & STATUS_UNSEEN) {
- prot_printf(imapd_out, "%cUNSEEN %u", sepchar, num_unseen);
+ prot_printf(imapd_out, "%cUNSEEN %u", sepchar, scdata.c_unseen);
sepchar = ' ';
}
if (statusitems & STATUS_HIGHESTMODSEQ) {
prot_printf(imapd_out, "%cHIGHESTMODSEQ " MODSEQ_FMT, sepchar,
- (mailbox->options & OPT_IMAP_CONDSTORE) ?
- mailbox->highestmodseq : 0);
+ scdata.c_highestmodseq);
sepchar = ' ';
}
prot_printf(imapd_out, ")\r\n");
Index: imap/seen_db.c
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/seen_db.c,v
retrieving revision 1.57
diff -u -r1.57 seen_db.c
--- imap/seen_db.c 17 Jan 2008 13:25:31 -0000 1.57
+++ imap/seen_db.c 18 Jan 2008 04:16:12 -0000
@@ -68,6 +68,7 @@
#include "xstrlcat.h"
#include "mailbox.h"
#include "imap_err.h"
+#include "statuscache.h"
#include "seen.h"
#define FNAME_SEENSUFFIX ".seen" /* per user seen state extension */
@@ -80,6 +81,7 @@
struct seen {
char *user; /* what user is this for? */
+ const char *mboxname; /* what mailbox name? */
const char *uniqueid; /* what mailbox? */
const char *path; /* where is this mailbox? */
struct db *db;
@@ -147,6 +149,7 @@
/* if this is the db we've already opened, return it */
if (seendb && !strcmp(seendb->user, user)) {
abortcurrent(seendb);
+ seendb->mboxname = mailbox->name;
seendb->uniqueid = mailbox->uniqueid;
seendb->path = mailbox->path;
*seendbptr = seendb;
@@ -185,6 +188,7 @@
free(fname);
seendb->tid = NULL;
+ seendb->mboxname = mailbox->name;
seendb->uniqueid = mailbox->uniqueid;
seendb->path = mailbox->path;
seendb->user = xstrdup(user);
@@ -386,6 +390,9 @@
break;
}
+ /* Something changed, kill our status cache for this mailbox */
+ statuscache_invalidate(seendb->mboxname, seendb->user);
+
free(data);
return r;
}
@@ -408,6 +415,7 @@
seendb->tid = NULL;
}
+ seendb->mboxname = NULL;
seendb->uniqueid = NULL;
seendb->path = NULL;
Index: imap/statuscache.h
===================================================================
RCS file: imap/statuscache.h
diff -N imap/statuscache.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ imap/statuscache.h 18 Jan 2008 04:16:12 -0000
@@ -0,0 +1,93 @@
+/* statuscache.h -- Status caching routines
+ *
+ * Copyright (c) 1998-2008 Carnegie Mellon University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For permission or any other legal
+ * details, please contact
+ * Office of Technology Transfer
+ * Carnegie Mellon University
+ * 5000 Forbes Avenue
+ * Pittsburgh, PA 15213-3890
+ * (412) 268-4387, fax: (412) 268-7395
+ * [EMAIL PROTECTED]
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by Computing Services
+ * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $Id$
+ */
+
+#ifndef STATUSCACHE_H
+#define STATUSCACHE_H
+
+#include "mailbox.h"
+
+/* name of the statuscache database */
+#define FNAME_STATUSCACHEDB "/statuscache.db"
+#define STATUSCACHE_VERSION 3
+
+/* open the statuscache db */
+void statuscache_open(char *name);
+
+struct statuscache_data {
+ unsigned version;
+ unsigned statusitems;
+
+ time_t message_mtime;
+ ino_t message_ino;
+ off_t message_size;
+
+ unsigned long c_messages;
+ unsigned c_recent;
+ unsigned long c_uidnext;
+ unsigned long c_uidvalidity;
+ unsigned c_unseen;
+ modseq_t c_highestmodseq;
+};
+
+/* fill a statuscache entry */
+int statuscache_fill(struct statuscache_data *scdata, struct mailbox *mailbox,
+ int statusitems, int num_recent, int num_unseen);
+
+/* lookup a single statuscache entry and return result */
+int statuscache_lookup(const char *mboxname, const char *userid,
+ struct statuscache_data *scdata);
+
+/* update a statuscache entry */
+int statuscache_update(const char *mboxname, const char *userid,
+ struct statuscache_data *scdata);
+
+/* invalidate (delete) a statuscache entry */
+int statuscache_invalidate(const char *mboxname, const char *userid);
+
+/* close the database */
+void statuscache_close(void);
+
+/* done with database stuff */
+void statuscache_done(void);
+
+#endif /* STATUSCACHE_H */
Index: imap/statuscache_db.c
===================================================================
RCS file: imap/statuscache_db.c
diff -N imap/statuscache_db.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ imap/statuscache_db.c 18 Jan 2008 04:16:12 -0000
@@ -0,0 +1,216 @@
+/* statuscache_db.c -- Status caching routines
+ *
+ * Copyright (c) 1998-2008 Carnegie Mellon University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For permission or any other legal
+ * details, please contact
+ * Office of Technology Transfer
+ * Carnegie Mellon University
+ * 5000 Forbes Avenue
+ * Pittsburgh, PA 15213-3890
+ * (412) 268-4387, fax: (412) 268-7395
+ * [EMAIL PROTECTED]
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by Computing Services
+ * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $Id$
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <syslog.h>
+
+#include "assert.h"
+#include "cyrusdb.h"
+#include "exitcodes.h"
+#include "imapd.h"
+#include "global.h"
+#include "imap_err.h"
+#include "mboxlist.h"
+#include "seen.h"
+#include "util.h"
+#include "xmalloc.h"
+
+#include "statuscache.h"
+
+#define DB config_statuscache_db
+
+struct db *statuscachedb;
+static int statuscache_dbopen = 0;
+
+void statuscache_open(char *fname)
+{
+ int ret;
+ char *tofree = NULL;
+
+ /* create db file name */
+ if (!fname) {
+ fname = xmalloc(strlen(config_dir)+sizeof(FNAME_STATUSCACHEDB));
+ tofree = fname;
+ strcpy(fname, config_dir);
+ strcat(fname, FNAME_STATUSCACHEDB);
+ }
+
+ ret = DB->open(fname, CYRUSDB_CREATE, &statuscachedb);
+ if (ret != 0) {
+ syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
+ cyrusdb_strerror(ret));
+ fatal("can't read statuscache file", EC_TEMPFAIL);
+ }
+
+ if (tofree) free(tofree);
+
+ statuscache_dbopen = 1;
+}
+
+void statuscache_close(void)
+{
+ int r;
+
+ if (statuscache_dbopen) {
+ r = DB->close(statuscachedb);
+ if (r) {
+ syslog(LOG_ERR, "DBERROR: error closing statuscache: %s",
+ cyrusdb_strerror(r));
+ }
+ statuscache_dbopen = 0;
+ }
+}
+
+int statuscache_fill(struct statuscache_data *scdata, struct mailbox *mailbox,
+ int statusitems, int num_recent, int num_unseen)
+{
+ assert(scdata);
+ assert(mailbox);
+
+ scdata->version = STATUSCACHE_VERSION;
+ scdata->statusitems = statusitems;
+
+ scdata->message_mtime = mailbox->index_mtime;
+ scdata->message_ino = mailbox->index_ino;
+ scdata->message_size = mailbox->index_size;
+
+ scdata->c_messages = mailbox->exists;
+ scdata->c_recent = num_recent;
+ scdata->c_uidnext = mailbox->last_uid+1;
+ scdata->c_uidvalidity = mailbox->uidvalidity;
+ scdata->c_unseen = num_unseen;
+ scdata->c_highestmodseq =
+ (mailbox->options & OPT_IMAP_CONDSTORE) ? mailbox->highestmodseq : 0;
+
+ return 0;
+}
+
+void statuscache_done(void)
+{
+ /* DB->done() handled by cyrus_done() */
+}
+
+static char *statuscache_buildkey(const char *mailboxname, const char *userid)
+{
+ static char key[MAX_MAILBOX_NAME*2+7];
+
+ /* Build lookup key */
+ strcpy(key, "sc..");
+ strcat(key, mailboxname);
+ strcat(key, "..");
+ strcat(key, userid);
+
+ return key;
+}
+
+int statuscache_lookup(const char *mboxname, const char *userid,
+ struct statuscache_data *scdata)
+{
+ int datalen, r;
+ const char *data = NULL;
+ char *key = statuscache_buildkey(mboxname, userid);
+
+ /* Check if in database */
+ do {
+ r = DB->fetch(statuscachedb, key, strlen(key), &data, &datalen, NULL);
+ } while (r == CYRUSDB_AGAIN);
+
+ /* Copy data into struct if found and right length */
+ if (!r && data && datalen == sizeof(struct statuscache_data)) {
+ if (((struct statuscache_data *)data)->version != STATUSCACHE_VERSION)
+ return IMAP_NO_NOSUCHMSG;
+ memcpy((void *)scdata, (void *)data, datalen);
+ return 0;
+ } else if (r == CYRUSDB_NOTFOUND) {
+ return IMAP_NO_NOSUCHMSG;
+ }
+
+ return IMAP_NO_NOSUCHMSG;
+}
+
+int statuscache_update(const char *mboxname, const char *userid,
+ struct statuscache_data *scdata)
+{
+ int r;
+ char * key = statuscache_buildkey(mboxname, userid);
+
+ /* Store raw struct in database */
+ r = DB->store(statuscachedb, key, strlen(key),
+ (void *)scdata, sizeof(struct statuscache_data), NULL);
+ if (r != CYRUSDB_OK) {
+ syslog(LOG_ERR, "DBERROR: error updating database: %s",
+ cyrusdb_strerror(r));
+ }
+ return 0;
+}
+
+int statuscache_invalidate(const char *mboxname, const char *userid)
+{
+ int r;
+ char * key = statuscache_buildkey(mboxname, userid);
+
+ /* Don't access DB if it hasn't been opened */
+ if (!statuscachedb) return 0;
+
+ /* Delete db entry */
+ r = DB->delete(statuscachedb, key, strlen(key), NULL, 1);
+ if (r != CYRUSDB_OK) {
+ syslog(LOG_ERR, "DBERROR: error deleting from database: %s",
+ cyrusdb_strerror(r));
+ }
+ return 0;
+}
+
Index: imap/sync_server.c
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/imap/sync_server.c,v
retrieving revision 1.23
diff -u -r1.23 sync_server.c
--- imap/sync_server.c 17 Jan 2008 13:25:31 -0000 1.23
+++ imap/sync_server.c 18 Jan 2008 04:16:12 -0000
@@ -89,6 +89,7 @@
#include "retry.h"
#include "seen.h"
#include "spool.h"
+#include "statuscache.h"
#include "telemetry.h"
#include "tls.h"
#include "user.h"
@@ -333,6 +334,11 @@
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
+ /* Open the statuscache so we can invalidate seen states */
+ if (config_getswitch(IMAPOPT_STATUSCACHE)) {
+ statuscache_open(NULL);
+ }
+
return 0;
}
@@ -495,6 +501,11 @@
void shut_down(int code)
{
proc_cleanup();
+
+ if (config_getswitch(IMAPOPT_STATUSCACHE)) {
+ statuscache_close();
+ statuscache_done();
+ }
seen_done();
mboxlist_close();
Index: lib/imapoptions
===================================================================
RCS file: /afs/andrew/system/cvs/src/cyrus/lib/imapoptions,v
retrieving revision 1.47
diff -u -r1.47 imapoptions
--- lib/imapoptions 9 Jan 2008 19:10:56 -0000 1.47
+++ lib/imapoptions 18 Jan 2008 04:16:12 -0000
@@ -953,6 +953,12 @@
{ "subscription_db", "flat", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist")}
/* The cyrusdb backend to use for the subscriptions list. */
+{ "statuscache", 0, SWITCH }
+/* Enable/disable the imap status cache. */
+
+{ "statuscache_db", "berkeley-nosync", STRINGLIST("berkeley", "berkeley-nosync", "berkeley-hash", "berkeley-hash-nosync", "skiplist") }
+/* The cyrusdb backend to use for the imap status cache. */
+
{ "sync_authname", NULL, STRING }
/* The authentication name to use when authenticating to a sync server. */