changeset 7629:bad3a811a148 HEAD

Added QRESYNC support.
author Timo Sirainen <tss@iki.fi>
date Sat, 15 Mar 2008 16:24:26 +0200
parents af2441dc6de6
children 7d208a7e0cbd
files src/imap/client.h src/imap/cmd-close.c src/imap/cmd-copy.c src/imap/cmd-enable.c src/imap/cmd-expunge.c src/imap/cmd-fetch.c src/imap/cmd-search.c src/imap/cmd-select.c src/imap/cmd-store.c src/imap/imap-fetch.c src/imap/imap-fetch.h src/imap/imap-search.c src/imap/imap-search.h src/imap/imap-sync.c src/lib-index/mail-index-modseq.c src/lib-index/mail-index-modseq.h src/lib-storage/index/cydir/cydir-storage.c src/lib-storage/index/dbox/dbox-storage.c src/lib-storage/index/index-fetch.c src/lib-storage/index/index-storage.h src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/raw/raw-storage.c src/lib-storage/mail-storage-private.h src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/util/idxview.c
diffstat 27 files changed, 825 insertions(+), 145 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/client.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/client.h	Sat Mar 15 16:24:26 2008 +0200
@@ -92,6 +92,7 @@
 	unsigned int destroyed:1;
 	unsigned int handling_input:1;
 	unsigned int syncing:1;
+	unsigned int selecting:1;
 	unsigned int input_skip_line:1; /* skip all the data until we've
 					   found a new line */
 };
--- a/src/imap/cmd-close.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-close.c	Sat Mar 15 16:24:26 2008 +0200
@@ -9,6 +9,8 @@
 	struct client *client = cmd->client;
 	struct mailbox *mailbox = client->mailbox;
 	struct mail_storage *storage;
+	struct mailbox_status status;
+	bool show_highestmodseq;
 
 	if (!client_verify_open_mailbox(cmd))
 		return TRUE;
@@ -16,15 +18,25 @@
 	storage = mailbox_get_storage(mailbox);
 	client->mailbox = NULL;
 
+	show_highestmodseq =
+		(cmd->client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0;
+
 	if (!imap_expunge(mailbox, NULL))
 		client_send_untagged_storage_error(client, storage);
-	else if (mailbox_sync(mailbox, 0, 0, NULL) < 0)
+	else if (mailbox_sync(mailbox, 0, !show_highestmodseq ? 0 :
+			      STATUS_HIGHESTMODSEQ, &status) < 0)
 		client_send_untagged_storage_error(client, storage);
 
 	if (mailbox_close(&mailbox) < 0)
                 client_send_untagged_storage_error(client, storage);
 	client_update_mailbox_flags(client, NULL);
 
-	client_send_tagline(cmd, "OK Close completed.");
+	if (!show_highestmodseq)
+		client_send_tagline(cmd, "OK Close completed.");
+	else {
+		client_send_tagline(cmd, t_strdup_printf(
+			"OK [HIGHESTMODSEQ %llu] Close completed.",
+			(unsigned long long)status.highest_modseq));
+	}
 	return TRUE;
 }
--- a/src/imap/cmd-copy.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-copy.c	Sat Mar 15 16:24:26 2008 +0200
@@ -109,7 +109,7 @@
 	if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE))
 		return TRUE;
 
-	search_arg = imap_search_get_arg(cmd, messageset, cmd->uid);
+	search_arg = imap_search_get_seqset(cmd, messageset, cmd->uid);
 	if (search_arg == NULL)
 		return TRUE;
 
--- a/src/imap/cmd-enable.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-enable.c	Sat Mar 15 16:24:26 2008 +0200
@@ -24,6 +24,11 @@
 			client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE);
 			str_append(reply, " CONDSTORE");
 		}
+		else if (strcmp(str, "QRESYNC") == 0) {
+			client_enable(cmd->client, MAILBOX_FEATURE_QRESYNC |
+				      MAILBOX_FEATURE_CONDSTORE);
+			str_append(reply, " QRESYNC");
+		}
 	}
 	if (str_len(reply) > 9)
 		client_send_line(cmd->client, str_c(reply));
--- a/src/imap/cmd-expunge.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-expunge.c	Sat Mar 15 16:24:26 2008 +0200
@@ -5,6 +5,29 @@
 #include "imap-search.h"
 #include "imap-expunge.h"
 
+static bool cmd_expunge_callback(struct client_command_context *cmd)
+{
+	struct mailbox_status status;
+
+	if (cmd->client->sync_seen_deletes && !cmd->uid) {
+		/* Outlook workaround: session 1 set \Deleted flag and
+		   session 2 tried to expunge without having seen it yet.
+		   expunge again. */
+		return cmd_expunge(cmd);
+	}
+
+	if ((cmd->client->enabled_features & MAILBOX_FEATURE_QRESYNC) == 0)
+		client_send_tagline(cmd, "OK Expunge completed.");
+	else {
+		mailbox_get_status(cmd->client->mailbox,
+				   STATUS_HIGHESTMODSEQ, &status);
+		client_send_tagline(cmd, t_strdup_printf(
+			"OK [HIGHESTMODSEQ %llu] Expunge completed.",
+			(unsigned long long)status.highest_modseq));
+	}
+	return TRUE;
+}
+
 bool cmd_uid_expunge(struct client_command_context *cmd)
 {
 	struct client *client = cmd->client;
@@ -24,13 +47,13 @@
 		return TRUE;
 	}
 
-	search_arg = imap_search_get_arg(cmd, uidset, TRUE);
+	search_arg = imap_search_get_seqset(cmd, uidset, TRUE);
 	if (search_arg == NULL)
 		return TRUE;
 
 	if (imap_expunge(client->mailbox, search_arg)) {
-		return cmd_sync(cmd, 0, IMAP_SYNC_FLAG_SAFE,
-				"OK Expunge completed.");
+		return cmd_sync_callback(cmd, 0, IMAP_SYNC_FLAG_SAFE,
+					 cmd_expunge_callback);
 	} else {
 		client_send_storage_error(cmd,
 					  mailbox_get_storage(client->mailbox));
@@ -38,19 +61,6 @@
 	}
 }
 
-static bool cmd_expunge_callback(struct client_command_context *cmd)
-{
-	if (cmd->client->sync_seen_deletes) {
-		/* Outlook workaround: session 1 set \Deleted flag and
-		   session 2 tried to expunge without having seen it yet.
-		   expunge again. */
-		return cmd_expunge(cmd);
-	}
-
-	client_send_tagline(cmd, "OK Expunge completed.");
-	return TRUE;
-}
-
 bool cmd_expunge(struct client_command_context *cmd)
 {
 	struct client *client = cmd->client;
--- a/src/imap/cmd-fetch.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-fetch.c	Sat Mar 15 16:24:26 2008 +0200
@@ -69,20 +69,34 @@
 }
 
 static bool
-fetch_add_unchanged_since(struct imap_fetch_context *ctx, uint64_t modseq)
+fetch_parse_modifier(struct imap_fetch_context *ctx,
+		     const char *name, const struct imap_arg **args)
 {
-	struct mail_search_arg *search_arg;
+	unsigned long long num;
 
-	search_arg = p_new(ctx->cmd->pool, struct mail_search_arg, 1);
-	search_arg->type = SEARCH_MODSEQ;
-	search_arg->value.modseq =
-		p_new(ctx->cmd->pool, struct mail_search_modseq, 1);
-	search_arg->value.modseq->modseq = modseq;
+	if (strcmp(name, "CHANGEDSINCE") == 0) {
+		if ((*args)->type != IMAP_ARG_ATOM) {
+			client_send_command_error(ctx->cmd,
+				"Invalid CHANGEDSINCE modseq.");
+			return FALSE;
+		}
+		num = strtoull(imap_arg_string(*args), NULL, 10);
+		*args += 1;
+		return imap_fetch_add_unchanged_since(ctx, num);
+	}
+	if (strcmp(name, "VANISHED") == 0 && ctx->cmd->uid) {
+		if ((ctx->client->enabled_features &
+		     MAILBOX_FEATURE_QRESYNC) == 0) {
+			client_send_command_error(ctx->cmd,
+						  "QRESYNC not enabled");
+			return FALSE;
+		}
+		ctx->send_vanished = TRUE;
+		return TRUE;
+	}
 
-	search_arg->next = ctx->search_args->next;
-	ctx->search_args->next = search_arg;
-
-	return imap_fetch_init_handler(ctx, "MODSEQ", NULL);
+	client_send_command_error(ctx->cmd, "Unknown FETCH modifier");
+	return FALSE;
 }
 
 static bool
@@ -90,26 +104,24 @@
 		      const struct imap_arg *args)
 {
 	const char *name;
-	unsigned long long num;
 
-	for (; args->type != IMAP_ARG_EOL; args++) {
-		if (args->type != IMAP_ARG_ATOM ||
-		    args[1].type != IMAP_ARG_ATOM) {
+	while (args->type != IMAP_ARG_EOL) {
+		if (args->type != IMAP_ARG_ATOM) {
 			client_send_command_error(ctx->cmd,
 				"FETCH modifiers contain non-atoms.");
 			return FALSE;
 		}
-		name = IMAP_ARG_STR(args);
-		if (strcasecmp(name, "CHANGEDSINCE") == 0) {
-			args++;
-			num = strtoull(imap_arg_string(args), NULL, 10);
-			if (!fetch_add_unchanged_since(ctx, num))
-				return FALSE;
-		} else {
-			client_send_command_error(ctx->cmd,
-						  "Unknown FETCH modifier");
+		name = t_str_ucase(IMAP_ARG_STR(args));
+		args++;
+		if (!fetch_parse_modifier(ctx, name, &args))
 			return FALSE;
-		}
+	}
+	if (ctx->send_vanished &&
+	    (ctx->search_args->next == NULL ||
+	     ctx->search_args->next->type != SEARCH_MODSEQ)) {
+		client_send_command_error(ctx->cmd,
+			"VANISHED used without CHANGEDSINCE");
+		return FALSE;
 	}
 	return TRUE;
 }
@@ -150,15 +162,11 @@
 static bool cmd_fetch_continue(struct client_command_context *cmd)
 {
         struct imap_fetch_context *ctx = cmd->context;
-	int ret;
 
-	if ((ret = imap_fetch(ctx)) == 0) {
+	if (imap_fetch_more(ctx) == 0) {
 		/* unfinished */
 		return FALSE;
 	}
-	if (ret < 0)
-		ctx->failed = TRUE;
-
 	return cmd_fetch_finish(ctx);
 }
 
@@ -168,7 +176,6 @@
 	const struct imap_arg *args;
 	struct mail_search_arg *search_arg;
 	const char *messageset;
-	int ret;
 
 	if (!client_read_args(cmd, 0, 0, &args))
 		return FALSE;
@@ -185,11 +192,11 @@
 		return TRUE;
 	}
 
-	search_arg = imap_search_get_arg(cmd, messageset, cmd->uid);
+	search_arg = imap_search_get_anyset(cmd, messageset, cmd->uid);
 	if (search_arg == NULL)
 		return TRUE;
 
-	ctx = imap_fetch_init(cmd);
+	ctx = imap_fetch_init(cmd, cmd->client->mailbox);
 	if (ctx == NULL)
 		return TRUE;
 	ctx->search_args = search_arg;
@@ -201,17 +208,15 @@
 		return TRUE;
 	}
 
-	imap_fetch_begin(ctx);
-	if ((ret = imap_fetch(ctx)) == 0) {
-		/* unfinished */
-		cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+	if (imap_fetch_begin(ctx) == 0) {
+		if (imap_fetch_more(ctx) == 0) {
+			/* unfinished */
+			cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
 
-		cmd->func = cmd_fetch_continue;
-		cmd->context = ctx;
-		return FALSE;
+			cmd->func = cmd_fetch_continue;
+			cmd->context = ctx;
+			return FALSE;
+		}
 	}
-	if (ret < 0)
-		ctx->failed = TRUE;
-
 	return cmd_fetch_finish(ctx);
 }
--- a/src/imap/cmd-search.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-search.c	Sat Mar 15 16:24:26 2008 +0200
@@ -5,6 +5,7 @@
 #include "str.h"
 #include "commands.h"
 #include "mail-search.h"
+#include "mail-search-build.h"
 #include "imap-search.h"
 
 #define OUTBUF_SIZE 65536
--- a/src/imap/cmd-select.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-select.c	Sat Mar 15 16:24:26 2008 +0200
@@ -1,79 +1,283 @@
 /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
 
 #include "common.h"
+#include "seq-range-array.h"
 #include "commands.h"
+#include "mail-search.h"
+#include "imap-messageset.h"
+#include "imap-fetch.h"
 #include "imap-sync.h"
 
-bool cmd_select_full(struct client_command_context *cmd, bool readonly)
-{
-	struct client *client = cmd->client;
+#include <stdlib.h>
+
+struct imap_select_context {
+	struct client_command_context *cmd;
 	struct mail_storage *storage;
 	struct mailbox *box;
-	struct mailbox_status status;
-	enum mailbox_open_flags open_flags = 0;
-	const struct imap_arg *args, *list_args;
-	const char *mailbox, *str;
+
+	struct imap_fetch_context *fetch_ctx;
+
+	uint32_t qresync_uid_validity;
+	uint64_t qresync_modseq;
+	ARRAY_TYPE(seq_range) qresync_known_uids;
+	ARRAY_TYPE(uint32_t) qresync_sample_seqset;
+	ARRAY_TYPE(uint32_t) qresync_sample_uidset;
+};
+
+static int select_qresync_get_uids(struct imap_select_context *ctx,
+				   const ARRAY_TYPE(seq_range) *seqset,
+				   const ARRAY_TYPE(seq_range) *uidset)
+{
+	const struct seq_range *seq_range, *uid_range;
+	struct seq_range_iter seq_iter;
+	unsigned int i, seq_count, uid_count, diff, n = 0;
+	uint32_t seq;
+
+	/* change all n:m ranges to n,m and store the results */
+	seq_range = array_get(seqset, &seq_count);
+	uid_range = array_get(uidset, &uid_count);
+
+	seq_range_array_iter_init(&seq_iter, seqset);
+	i_array_init(&ctx->qresync_sample_uidset, uid_count);
+	i_array_init(&ctx->qresync_sample_seqset, uid_count);
+	for (i = 0; i < uid_count; i++) {
+		if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
+			return -1;
+		array_append(&ctx->qresync_sample_uidset,
+			     &uid_range[i].seq1, 1);
+		array_append(&ctx->qresync_sample_seqset, &seq, 1);
 
-	/* <mailbox> [(CONDSTORE)] */
-	if (!client_read_args(cmd, 0, 0, &args))
+		diff = uid_range[i].seq2 - uid_range[i].seq1;
+		if (diff > 0) {
+			n += diff - 1;
+			if (!seq_range_array_iter_nth(&seq_iter, n++, &seq))
+				return -1;
+
+			array_append(&ctx->qresync_sample_uidset,
+				     &uid_range[i].seq2, 1);
+			array_append(&ctx->qresync_sample_seqset, &seq, 1);
+		}
+	}
+	if (seq_range_array_iter_nth(&seq_iter, n, &seq))
+		return -1;
+	return 0;
+}
+
+static bool
+select_parse_qresync(struct imap_select_context *ctx,
+		     const struct imap_arg *args)
+{
+	ARRAY_TYPE(seq_range) seqset, uidset;
+	unsigned int count;
+
+	if ((ctx->cmd->client->enabled_features &
+	     MAILBOX_FEATURE_QRESYNC) == 0) {
+		client_send_command_error(ctx->cmd, "QRESYNC not enabled");
 		return FALSE;
+	}
+	if (args->type != IMAP_ARG_LIST) {
+		client_send_command_error(ctx->cmd,
+					  "QRESYNC parameters missing");
+		return FALSE;
+	}
+	args = IMAP_ARG_LIST_ARGS(args);
+	for (count = 0; args[count].type != IMAP_ARG_EOL; count++) ;
 
-	if (!IMAP_ARG_TYPE_IS_STRING(args[0].type)) {
-		client_send_command_error(cmd, "Invalid arguments.");
+	if (count < 2 || count > 4 ||
+	    args[0].type != IMAP_ARG_ATOM ||
+	    args[1].type != IMAP_ARG_ATOM ||
+	    (count > 2 && args[2].type != IMAP_ARG_ATOM) ||
+	    (count > 3 && args[3].type != IMAP_ARG_LIST)) {
+		client_send_command_error(ctx->cmd,
+					  "Invalid QRESYNC parameters");
 		return FALSE;
 	}
-	mailbox = IMAP_ARG_STR(&args[0]);
+	ctx->qresync_uid_validity =
+		strtoul(IMAP_ARG_STR_NONULL(&args[0]), NULL, 10);
+	ctx->qresync_modseq =
+		strtoull(IMAP_ARG_STR_NONULL(&args[1]), NULL, 10);
+	if (count > 2) {
+		i_array_init(&ctx->qresync_known_uids, 64);
+		if (imap_messageset_parse(&ctx->qresync_known_uids,
+					  IMAP_ARG_STR_NONULL(&args[2])) < 0) {
+			client_send_command_error(ctx->cmd,
+						  "Invalid QRESYNC known-uids");
+			return FALSE;
+		}
+	} else {
+		i_array_init(&ctx->qresync_known_uids, 64);
+		seq_range_array_add_range(&ctx->qresync_known_uids,
+					  1, (uint32_t)-1);
+	}
+	if (count > 3) {
+		args = IMAP_ARG_LIST_ARGS(&args[3]);
+		if (args[0].type != IMAP_ARG_ATOM ||
+		    args[1].type != IMAP_ARG_ATOM ||
+		    args[2].type != IMAP_ARG_EOL) {
+			client_send_command_error(ctx->cmd,
+				"Invalid QRESYNC known set parameters");
+			return FALSE;
+		}
+		t_array_init(&seqset, 32);
+		if (imap_messageset_parse(&seqset,
+					  IMAP_ARG_STR_NONULL(&args[0])) < 0) {
+			client_send_command_error(ctx->cmd,
+				"Invalid QRESYNC known-sequence-set");
+			return FALSE;
+		}
+		t_array_init(&uidset, 32);
+		if (imap_messageset_parse(&uidset,
+					  IMAP_ARG_STR_NONULL(&args[1])) < 0) {
+			client_send_command_error(ctx->cmd,
+				"Invalid QRESYNC known-uid-set");
+			return FALSE;
+		}
+		if (select_qresync_get_uids(ctx, &seqset, &uidset) < 0) {
+			client_send_command_error(ctx->cmd,
+				"Invalid QRESYNC sets");
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static bool
+select_parse_options(struct imap_select_context *ctx,
+		     const struct imap_arg *args)
+{
+	const char *name;
+
+	while (args->type != IMAP_ARG_EOL) {
+		if (args->type != IMAP_ARG_ATOM) {
+			client_send_command_error(ctx->cmd,
+				"SELECT options contain non-atoms.");
+			return FALSE;
+		}
+		name = t_str_ucase(IMAP_ARG_STR(args));
+		args++;
 
-	if (args[1].type == IMAP_ARG_LIST) {
-		list_args = IMAP_ARG_LIST_ARGS(&args[1]);
-		for (; list_args->type != IMAP_ARG_EOL; list_args++) {
-			str = imap_arg_string(list_args);
-			if (str != NULL && strcasecmp(str, "CONDSTORE") == 0) {
-				client_enable(client,
-					      MAILBOX_FEATURE_CONDSTORE);
-			}
+		if (strcmp(name, "CONDSTORE") == 0)
+			client_enable(ctx->cmd->client,
+				      MAILBOX_FEATURE_CONDSTORE);
+		else if (strcmp(name, "QRESYNC") == 0) {
+			if (!select_parse_qresync(ctx, args))
+				return FALSE;
+			args++;
+		} else {
+			client_send_command_error(ctx->cmd,
+						  "Unknown FETCH modifier");
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static void select_context_free(struct imap_select_context *ctx)
+{
+	if (array_is_created(&ctx->qresync_known_uids))
+		array_free(&ctx->qresync_known_uids);
+	if (array_is_created(&ctx->qresync_sample_seqset))
+		array_free(&ctx->qresync_sample_seqset);
+	if (array_is_created(&ctx->qresync_sample_uidset))
+		array_free(&ctx->qresync_sample_uidset);
+}
+
+static void cmd_select_finish(struct imap_select_context *ctx, int ret)
+{
+	if (ret < 0) {
+		if (ctx->box != NULL)
+			mailbox_close(&ctx->box);
+		client_send_storage_error(ctx->cmd, ctx->storage);
+		ctx->cmd->client->mailbox = NULL;
+	} else {
+		client_send_tagline(ctx->cmd, mailbox_is_readonly(ctx->box) ?
+				    "OK [READ-ONLY] Select completed." :
+				    "OK [READ-WRITE] Select completed.");
+	}
+	ctx->cmd->client->selecting = FALSE;
+	select_context_free(ctx);
+}
+
+static bool cmd_select_continue(struct client_command_context *cmd)
+{
+        struct imap_select_context *ctx = cmd->context;
+	int ret;
+
+	if ((ret = imap_fetch_more(ctx->fetch_ctx)) == 0) {
+		/* unfinished */
+		return FALSE;
+	}
+
+	ret = imap_fetch_deinit(ctx->fetch_ctx);
+	cmd_select_finish(ctx, ret);
+	return TRUE;
+}
+
+static int select_qresync(struct imap_select_context *ctx)
+{
+	struct imap_fetch_context *fetch_ctx;
+	struct mail_search_arg *search_arg;
+
+	search_arg = p_new(ctx->cmd->pool, struct mail_search_arg, 1);
+	search_arg->type = SEARCH_UIDSET;
+	search_arg->value.seqset = ctx->qresync_known_uids;
+
+	fetch_ctx = imap_fetch_init(ctx->cmd, ctx->box);
+	if (fetch_ctx == NULL)
+		return -1;
+
+	fetch_ctx->search_args = search_arg;
+	fetch_ctx->send_vanished = TRUE;
+	fetch_ctx->qresync_sample_seqset = &ctx->qresync_sample_seqset;
+	fetch_ctx->qresync_sample_uidset = &ctx->qresync_sample_uidset;
+
+	if (!imap_fetch_add_unchanged_since(fetch_ctx, ctx->qresync_modseq) ||
+	    !imap_fetch_init_handler(fetch_ctx, "UID", NULL) ||
+	    !imap_fetch_init_handler(fetch_ctx, "FLAGS", NULL) ||
+	    !imap_fetch_init_handler(fetch_ctx, "MODSEQ", NULL)) {
+		(void)imap_fetch_deinit(fetch_ctx);
+		return -1;
+	}
+
+	if (imap_fetch_begin(fetch_ctx) == 0) {
+		if (imap_fetch_more(fetch_ctx) == 0) {
+			/* unfinished */
+			ctx->fetch_ctx = fetch_ctx;
+			ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+
+			ctx->cmd->func = cmd_select_continue;
+			ctx->cmd->context = ctx;
+			return FALSE;
 		}
 	}
 
-	if (client->mailbox != NULL) {
-		box = client->mailbox;
-		client->mailbox = NULL;
+	return imap_fetch_deinit(fetch_ctx);
+}
 
-                storage = mailbox_get_storage(box);
-		if (mailbox_close(&box) < 0)
-			client_send_untagged_storage_error(client, storage);
-	}
-
-	storage = client_find_storage(cmd, &mailbox);
-	if (storage == NULL)
-		return TRUE;
+static int
+select_open(struct imap_select_context *ctx, const char *mailbox, bool readonly)
+{
+	struct client *client = ctx->cmd->client;
+	struct mailbox_status status;
+	enum mailbox_open_flags open_flags = 0;
 
 	if (readonly)
 		open_flags |= MAILBOX_OPEN_READONLY | MAILBOX_OPEN_KEEP_RECENT;
-
-	box = mailbox_open(storage, mailbox, NULL, open_flags);
-	if (box == NULL) {
-		client_send_storage_error(cmd, storage);
-		return TRUE;
-	}
+	ctx->box = mailbox_open(ctx->storage, mailbox, NULL, open_flags);
+	if (ctx->box == NULL)
+		return -1;
 
 	if (client->enabled_features != 0)
-		mailbox_enable(box, client->enabled_features);
-	if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ,
+		mailbox_enable(ctx->box, client->enabled_features);
+	if (mailbox_sync(ctx->box, MAILBOX_SYNC_FLAG_FULL_READ,
 			 STATUS_MESSAGES | STATUS_RECENT |
 			 STATUS_FIRST_UNSEEN_SEQ | STATUS_UIDVALIDITY |
 			 STATUS_UIDNEXT | STATUS_KEYWORDS |
-			 STATUS_HIGHESTMODSEQ, &status) < 0) {
-		client_send_storage_error(cmd, storage);
-		mailbox_close(&box);
-		return TRUE;
-	}
+			 STATUS_HIGHESTMODSEQ, &status) < 0)
+		return -1;
 
-	/* set client's mailbox only after getting status to make sure
-	   we're not sending any expunge/exists replies too early to client */
-	client->mailbox = box;
+	client->mailbox = ctx->box;
 	client->select_counter++;
-
 	client->messages_count = status.messages;
 	client->recent_count = status.recent;
 	client->uidvalidity = status.uidvalidity;
@@ -109,9 +313,65 @@
 				(unsigned long long)status.highest_modseq));
 	}
 
-	client_send_tagline(cmd, mailbox_is_readonly(box) ?
-			    "OK [READ-ONLY] Select completed." :
-			    "OK [READ-WRITE] Select completed.");
+	if (ctx->qresync_uid_validity == status.uidvalidity) {
+		if (select_qresync(ctx) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+bool cmd_select_full(struct client_command_context *cmd, bool readonly)
+{
+	struct client *client = cmd->client;
+	struct mailbox *box;
+	struct imap_select_context *ctx;
+	const struct imap_arg *args;
+	const char *mailbox;
+	int ret;
+
+	/* <mailbox> [(optional parameters)] */
+	if (!client_read_args(cmd, 0, 0, &args))
+		return FALSE;
+
+	if (!IMAP_ARG_TYPE_IS_STRING(args[0].type)) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return FALSE;
+	}
+	mailbox = IMAP_ARG_STR(&args[0]);
+
+	ctx = p_new(cmd->pool, struct imap_select_context, 1);
+	ctx->cmd = cmd;
+	ctx->storage = client_find_storage(cmd, &mailbox);
+	if (ctx->storage == NULL)
+		return TRUE;
+
+	if (args[1].type == IMAP_ARG_LIST) {
+		if (!select_parse_options(ctx, IMAP_ARG_LIST_ARGS(&args[1]))) {
+			select_context_free(ctx);
+			return TRUE;
+		}
+	}
+
+	if (client->selecting) {
+		client_send_tagline(cmd, "Mailbox is already being selected");
+		return TRUE;
+	}
+	client->selecting = TRUE;
+
+	if (client->mailbox != NULL) {
+		box = client->mailbox;
+		client->mailbox = NULL;
+
+		if (mailbox_close(&box) < 0) {
+			client_send_untagged_storage_error(client,
+				mailbox_get_storage(box));
+		}
+		/* CLOSED response is required by QRESYNC */
+		client_send_line(client, "* OK [CLOSED]");
+	}
+
+	ret = select_open(ctx, mailbox, readonly);
+	cmd_select_finish(ctx, ret);
 	return TRUE;
 }
 
--- a/src/imap/cmd-store.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/cmd-store.c	Sat Mar 15 16:24:26 2008 +0200
@@ -142,7 +142,7 @@
 	if (!store_parse_args(&ctx, args))
 		return TRUE;
 
-	search_arg = imap_search_get_arg(cmd, ctx.messageset, cmd->uid);
+	search_arg = imap_search_get_seqset(cmd, ctx.messageset, cmd->uid);
 	if (search_arg == NULL)
 		return TRUE;
 
--- a/src/imap/imap-fetch.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/imap-fetch.c	Sat Mar 15 16:24:26 2008 +0200
@@ -9,6 +9,8 @@
 #include "message-send.h"
 #include "message-size.h"
 #include "imap-date.h"
+#include "mail-search.h"
+#include "mail-search-build.h"
 #include "commands.h"
 #include "imap-fetch.h"
 #include "imap-util.h"
@@ -81,7 +83,8 @@
 	return handler->init(ctx, name, args);
 }
 
-struct imap_fetch_context *imap_fetch_init(struct client_command_context *cmd)
+struct imap_fetch_context *
+imap_fetch_init(struct client_command_context *cmd, struct mailbox *box)
 {
 	struct client *client = cmd->client;
 	struct imap_fetch_context *ctx;
@@ -94,7 +97,7 @@
 	ctx = p_new(cmd->pool, struct imap_fetch_context, 1);
 	ctx->client = client;
 	ctx->cmd = cmd;
-	ctx->box = client->mailbox;
+	ctx->box = box;
 
 	ctx->cur_str = str_new(default_pool, 8192);
 	ctx->all_headers_buf = buffer_create_dynamic(cmd->pool, 128);
@@ -105,6 +108,23 @@
 	return ctx;
 }
 
+bool imap_fetch_add_unchanged_since(struct imap_fetch_context *ctx,
+				    uint64_t modseq)
+{
+	struct mail_search_arg *search_arg;
+
+	search_arg = p_new(ctx->cmd->pool, struct mail_search_arg, 1);
+	search_arg->type = SEARCH_MODSEQ;
+	search_arg->value.modseq =
+		p_new(ctx->cmd->pool, struct mail_search_modseq, 1);
+	search_arg->value.modseq->modseq = modseq;
+
+	search_arg->next = ctx->search_args->next;
+	ctx->search_args->next = search_arg;
+
+	return imap_fetch_init_handler(ctx, "MODSEQ", NULL);
+}
+
 #undef imap_fetch_add_handler
 void imap_fetch_add_handler(struct imap_fetch_context *ctx,
 			    bool buffered, bool want_deinit,
@@ -153,11 +173,152 @@
 	}
 }
 
-void imap_fetch_begin(struct imap_fetch_context *ctx)
+static void
+expunges_drop_known(struct imap_fetch_context *ctx, struct mail *mail,
+		    ARRAY_TYPE(seq_range) *expunges)
+{
+	const uint32_t *seqs, *uids;
+	unsigned int i, count;
+
+	seqs = array_get(ctx->qresync_sample_seqset, &count);
+	uids = array_idx(ctx->qresync_sample_uidset, 0);
+	i_assert(array_count(ctx->qresync_sample_uidset) == count);
+	i_assert(count > 0);
+
+	/* FIXME: we could do removals from the middle as well */
+	for (i = 0; i < count; i++) {
+		mail_set_seq(mail, seqs[i]);
+		if (uids[i] != mail->uid)
+			break;
+	}
+	if (i > 0)
+		seq_range_array_remove_range(expunges, 1, uids[i-1]);
+}
+
+static int get_expunges_fallback(struct imap_fetch_context *ctx,
+				 const ARRAY_TYPE(seq_range) *uids,
+				 ARRAY_TYPE(seq_range) *expunges)
+{
+	struct mailbox_transaction_context *trans;
+	struct mail_search_arg search_arg;
+	struct mail_search_context *search_ctx;
+	struct mail *mail;
+	const struct seq_range *uid_range;
+	struct mailbox_status status;
+	unsigned int i, count;
+	uint32_t next_uid;
+	int ret = 0;
+
+	uid_range = array_get(uids, &count);
+	i_assert(count > 0);
+	i = 0;
+	next_uid = uid_range[0].seq1;
+
+	/* search UIDs in given range */
+	memset(&search_arg, 0, sizeof(search_arg));
+	search_arg.type = SEARCH_UIDSET;
+	i_array_init(&search_arg.value.seqset, array_count(uids));
+	array_append_array(&search_arg.value.seqset, uids);
+
+	trans = mailbox_transaction_begin(ctx->box, 0);
+	mail = mail_alloc(trans, 0, NULL);
+	search_ctx = mailbox_search_init(trans, NULL, &search_arg, NULL);
+	while (mailbox_search_next(search_ctx, mail) > 0) {
+		if (mail->uid == next_uid) {
+			if (next_uid < uid_range[i].seq2)
+				next_uid++;
+			else if (++i < count)
+				next_uid = uid_range[++i].seq1;
+			else
+				break;
+		} else {
+			/* next_uid .. mail->uid-1 are expunged */
+			i_assert(mail->uid > next_uid);
+			while (mail->uid > uid_range[i].seq2) {
+				seq_range_array_add_range(expunges, next_uid,
+							  uid_range[i].seq2);
+				i++;
+				i_assert(i < count);
+				next_uid = uid_range[i].seq1;
+			}
+			if (next_uid != mail->uid) {
+				seq_range_array_add_range(expunges, next_uid,
+							  mail->uid - 1);
+			}
+			if (uid_range[i].seq2 == mail->uid)
+				next_uid = uid_range[++i].seq1;
+			else
+				next_uid = mail->uid + 1;
+		}
+	}
+	if (i < count) {
+		i_assert(next_uid <= uid_range[i].seq2);
+		seq_range_array_add_range(expunges, next_uid,
+					  uid_range[i].seq2);
+		i++;
+	}
+	for (; i < count; i++) {
+		seq_range_array_add_range(expunges, uid_range[i].seq1,
+					  uid_range[i].seq2);
+	}
+
+	mailbox_get_status(ctx->box, STATUS_UIDNEXT, &status);
+	seq_range_array_remove_range(expunges, status.uidnext, (uint32_t)-1);
+
+	if (mailbox_search_deinit(&search_ctx) < 0)
+		ret = -1;
+
+	if (ret == 0 && ctx->qresync_sample_seqset != NULL)
+		expunges_drop_known(ctx, mail, expunges);
+
+	mail_free(&mail);
+	(void)mailbox_transaction_commit(&trans);
+	return ret;
+}
+
+static int
+imap_fetch_send_vanished(struct imap_fetch_context *ctx)
+{
+	const struct mail_search_arg *uidarg = ctx->search_args;
+	const struct mail_search_arg *modseqarg = uidarg->next;
+	const ARRAY_TYPE(seq_range) *uids = &uidarg->value.seqset;
+	uint64_t modseq = modseqarg->value.modseq->modseq;
+	ARRAY_TYPE(seq_range) expunges;
+	string_t *str;
+	int ret = 0;
+
+	i_array_init(&expunges, array_count(uids));
+	if (!mailbox_get_expunged_uids(ctx->box, modseq, uids, &expunges)) {
+		/* return all expunged UIDs */
+		if (get_expunges_fallback(ctx, uids, &expunges) < 0) {
+			array_clear(&expunges);
+			ret = -1;
+		}
+	}
+	if (array_count(&expunges) > 0) {
+		str = str_new(default_pool, 128);
+		str_append(str, "* VANISHED (EARLIER) ");
+		imap_write_seq_range(str, &expunges);
+		str_append(str, "\r\n");
+		o_stream_send(ctx->client->output, str_data(str), str_len(str));
+		str_free(&str);
+	}
+	array_free(&expunges);
+	return ret;
+}
+
+int imap_fetch_begin(struct imap_fetch_context *ctx)
 {
 	const void *null = NULL;
 	const void *data;
 
+	if (ctx->send_vanished) {
+		if (imap_fetch_send_vanished(ctx) < 0) {
+			ctx->failed = TRUE;
+			return -1;
+		}
+	}
+
 	if (ctx->flags_update_seen) {
 		if (mailbox_is_readonly(ctx->box))
 			ctx->flags_update_seen = FALSE;
@@ -186,8 +347,12 @@
 	ctx->select_counter = ctx->client->select_counter;
 	ctx->mail = mail_alloc(ctx->trans, ctx->fetch_data,
 			       ctx->all_headers_ctx);
+
+	/* Delayed uidset -> seqset conversion. VANISHED needs the uidset. */
+	mail_search_args_init(ctx->search_args, ctx->box, TRUE);
 	ctx->search_ctx =
 		mailbox_search_init(ctx->trans, NULL, ctx->search_args, NULL);
+	return 0;
 }
 
 static int imap_fetch_flush_buffer(struct imap_fetch_context *ctx)
@@ -233,7 +398,7 @@
 	return 0;
 }
 
-static int imap_fetch_more(struct imap_fetch_context *ctx)
+static int imap_fetch_more_int(struct imap_fetch_context *ctx)
 {
 	struct client *client = ctx->client;
 	const struct imap_fetch_context_handler *handlers;
@@ -350,14 +515,16 @@
 	return 1;
 }
 
-int imap_fetch(struct imap_fetch_context *ctx)
+int imap_fetch_more(struct imap_fetch_context *ctx)
 {
 	int ret;
 
 	i_assert(ctx->client->output_lock == NULL ||
 		 ctx->client->output_lock == ctx->cmd);
 
-	ret = imap_fetch_more(ctx);
+	ret = imap_fetch_more_int(ctx);
+	if (ret < 0)
+		ctx->failed = TRUE;
 	if (ctx->line_partial) {
 		/* nothing can be sent until FETCH is finished */
 		ctx->client->output_lock = ctx->cmd;
--- a/src/imap/imap-fetch.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/imap-fetch.h	Sat Mar 15 16:24:26 2008 +0200
@@ -53,6 +53,9 @@
 	bool skip_cr;
 	int (*cont_handler)(struct imap_fetch_context *ctx);
 
+	const ARRAY_TYPE(uint32_t) *qresync_sample_seqset;
+	const ARRAY_TYPE(uint32_t) *qresync_sample_uidset;
+
 	ARRAY_TYPE(keywords) tmp_keywords;
 	unsigned int select_counter;
 
@@ -67,6 +70,7 @@
 	unsigned int line_partial:1;
 	unsigned int line_finished:1;
 	unsigned int partial_fetch:1;
+	unsigned int send_vanished:1;
 	unsigned int failed:1;
 };
 
@@ -91,13 +95,17 @@
 		(imap_fetch_handler_t *)handler, context)
 #endif
 
-struct imap_fetch_context *imap_fetch_init(struct client_command_context *cmd);
+struct imap_fetch_context *
+imap_fetch_init(struct client_command_context *cmd, struct mailbox *box);
 int imap_fetch_deinit(struct imap_fetch_context *ctx);
 bool imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *name,
 			     const struct imap_arg **args);
 
-void imap_fetch_begin(struct imap_fetch_context *ctx);
-int imap_fetch(struct imap_fetch_context *ctx);
+bool imap_fetch_add_unchanged_since(struct imap_fetch_context *ctx,
+				    uint64_t modseq);
+
+int imap_fetch_begin(struct imap_fetch_context *ctx);
+int imap_fetch_more(struct imap_fetch_context *ctx);
 
 bool fetch_body_section_init(struct imap_fetch_context *ctx, const char *name,
 			     const struct imap_arg **args);
--- a/src/imap/imap-search.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/imap-search.c	Sat Mar 15 16:24:26 2008 +0200
@@ -77,39 +77,48 @@
 }
 
 static int
-imap_search_get_uidset_arg(pool_t pool, struct mailbox *box, const char *uidset,
+imap_search_get_uidset_arg(struct client_command_context *cmd,
+			   const char *uidset,
 			   struct mail_search_arg **arg_r, const char **error_r)
 {
 	struct mail_search_arg *arg;
 
-	arg = p_new(pool, struct mail_search_arg, 1);
+	arg = p_new(cmd->pool, struct mail_search_arg, 1);
 	arg->type = SEARCH_UIDSET;
-	p_array_init(&arg->value.seqset, pool, 16);
+	p_array_init(&arg->value.seqset, cmd->pool, 16);
 	if (imap_messageset_parse(&arg->value.seqset, uidset) < 0) {
 		*error_r = "Invalid uidset";
 		return -1;
 	}
 
-	mail_search_args_init(arg, box, TRUE);
 	*arg_r = arg;
 	return 0;
 }
 
 struct mail_search_arg *
-imap_search_get_arg(struct client_command_context *cmd,
-		    const char *set, bool uid)
+imap_search_get_seqset(struct client_command_context *cmd,
+		       const char *set, bool uid)
+{
+	struct mail_search_arg *search_arg;
+
+	search_arg = imap_search_get_anyset(cmd, set, uid);
+	if (uid && search_arg != NULL)
+		mail_search_args_init(search_arg, cmd->client->mailbox, TRUE);
+	return search_arg;
+}
+
+struct mail_search_arg *
+imap_search_get_anyset(struct client_command_context *cmd,
+		       const char *set, bool uid)
 {
 	struct mail_search_arg *search_arg = NULL;
 	const char *error = NULL;
 	int ret;
 
-	if (!uid) {
+	if (!uid)
 		ret = imap_search_get_msgset_arg(cmd, set, &search_arg, &error);
-	} else {
-		ret = imap_search_get_uidset_arg(cmd->pool,
-						 cmd->client->mailbox, set,
-						 &search_arg, &error);
-	}
+	else
+		ret = imap_search_get_uidset_arg(cmd, set, &search_arg, &error);
 	if (ret < 0) {
 		client_send_command_error(cmd, error);
 		return NULL;
--- a/src/imap/imap-search.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/imap-search.h	Sat Mar 15 16:24:26 2008 +0200
@@ -11,7 +11,10 @@
 		       const struct imap_arg *args, const char **error_r);
 
 struct mail_search_arg *
-imap_search_get_arg(struct client_command_context *cmd,
-		    const char *set, bool uid);
+imap_search_get_seqset(struct client_command_context *cmd,
+		       const char *set, bool uid);
+struct mail_search_arg *
+imap_search_get_anyset(struct client_command_context *cmd,
+		       const char *set, bool uid);
 
 #endif
--- a/src/imap/imap-sync.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/imap/imap-sync.c	Sat Mar 15 16:24:26 2008 +0200
@@ -29,6 +29,7 @@
 
 	struct mailbox_sync_rec sync_rec;
 	ARRAY_TYPE(keywords) tmp_keywords;
+	ARRAY_TYPE(seq_range) expunges;
 	uint32_t seq;
 
 	unsigned int messages_count;
@@ -56,6 +57,9 @@
 	ctx->messages_count = client->messages_count;
 	i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
 
+	if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0)
+		i_array_init(&ctx->expunges, 128);
+
 	client_send_mailbox_flags(client, FALSE);
 	return ctx;
 }
@@ -66,6 +70,8 @@
 	int ret;
 
 	mail_free(&ctx->mail);
+	if (array_is_created(&ctx->expunges))
+		array_free(&ctx->expunges);
 
 	if (mailbox_sync_deinit(&ctx->sync_ctx, STATUS_UIDVALIDITY |
 				STATUS_MESSAGES | STATUS_RECENT, &status) < 0 ||
@@ -144,6 +150,53 @@
 	return client_send_line(ctx->client, str_c(str));
 }
 
+static void imap_sync_vanished(struct imap_sync_context *ctx)
+{
+	const struct seq_range *seqs;
+	unsigned int i, count;
+	string_t *line;
+	uint32_t seq, prev_uid, start_uid;
+	bool comma = FALSE;
+
+	/* Convert expunge sequences to UIDs and send them in VANISHED line. */
+	seqs = array_get(&ctx->expunges, &count);
+	if (count == 0)
+		return;
+
+	line = t_str_new(256);
+	str_append(line, "* VANISHED ");
+	for (i = 0; i < count; i++) {
+		prev_uid = start_uid = 0;
+		for (seq = seqs[i].seq1; seq <= seqs[i].seq2; seq++) {
+			mail_set_seq(ctx->mail, seq);
+			if (prev_uid + 1 != ctx->mail->uid) {
+				if (start_uid != 0) {
+					if (!comma)
+						comma = TRUE;
+					else
+						str_append_c(line, ',');
+					str_printfa(line, "%u", start_uid);
+					if (start_uid != prev_uid) {
+						str_printfa(line, ":%u",
+							    prev_uid);
+					}
+				}
+				start_uid = ctx->mail->uid;
+			}
+			prev_uid = ctx->mail->uid;
+		}
+		if (!comma)
+			comma = TRUE;
+		else
+			str_append_c(line, ',');
+		str_printfa(line, "%u", start_uid);
+		if (start_uid != prev_uid)
+			str_printfa(line, ":%u", prev_uid);
+	}
+	str_append(line, "\r\n");
+	o_stream_send(ctx->client->output, str_data(line), str_len(line));
+}
+
 int imap_sync_more(struct imap_sync_context *ctx)
 {
 	string_t *str;
@@ -170,6 +223,10 @@
 			ctx->sync_rec.seq2 = ctx->messages_count;
 		}
 
+		/* EXPUNGEs must come last */
+		i_assert(!array_is_created(&ctx->expunges) ||
+			 array_count(&ctx->expunges) == 0 ||
+			 ctx->sync_rec.type == MAILBOX_SYNC_TYPE_EXPUNGE);
 		switch (ctx->sync_rec.type) {
 		case MAILBOX_SYNC_TYPE_FLAGS:
 			if (ctx->seq == 0)
@@ -184,6 +241,16 @@
 			}
 			break;
 		case MAILBOX_SYNC_TYPE_EXPUNGE:
+			if (array_is_created(&ctx->expunges)) {
+				/* Use a single VANISHED line */
+				seq_range_array_add_range(&ctx->expunges,
+							  ctx->sync_rec.seq1,
+							  ctx->sync_rec.seq2);
+				ctx->messages_count -=
+					ctx->sync_rec.seq2 -
+					ctx->sync_rec.seq1 + 1;
+				break;
+			}
 			if (ctx->seq == 0)
 				ctx->seq = ctx->sync_rec.seq2;
 			ret = 1;
@@ -228,6 +295,8 @@
 
 		ctx->seq = 0;
 	}
+	if (array_is_created(&ctx->expunges))
+		imap_sync_vanished(ctx);
 	return ret;
 }
 
--- a/src/lib-index/mail-index-modseq.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-index/mail-index-modseq.c	Sat Mar 15 16:24:26 2008 +0200
@@ -21,14 +21,6 @@
 	METADATA_MODSEQ_IDX_KEYWORD_START
 };
 
-struct mail_index_modseq_header {
-	/* highest used modseq */
-	uint64_t highest_modseq;
-	/* last tracked log file position */
-	uint32_t log_seq;
-	uint32_t log_offset;
-};
-
 struct metadata_modseqs {
 	ARRAY_TYPE(modseqs) modseqs;
 };
@@ -608,3 +600,16 @@
 	array_free(&mmap->metadata_modseqs);
 	i_free(mmap);
 }
+
+bool mail_index_modseq_get_log_offset(struct mail_index_view *view,
+				      uint64_t modseq, uint32_t *log_seq_r,
+				      uoff_t *log_offset_r)
+{
+	if (view->map->hdr.indexid >= (modseq >> 32)) {
+		/* invalid modseq or created for an earlier index */
+		return FALSE;
+	}
+	*log_seq_r = (modseq >> 32) - view->map->hdr.indexid;
+	*log_offset_r = modseq & 0xffffffff;
+	return TRUE;
+}
--- a/src/lib-index/mail-index-modseq.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-index/mail-index-modseq.h	Sat Mar 15 16:24:26 2008 +0200
@@ -9,6 +9,14 @@
 struct mail_index_map_modseq;
 struct mail_index_sync_map_ctx;
 
+struct mail_index_modseq_header {
+	/* highest used modseq */
+	uint64_t highest_modseq;
+	/* last tracked log file position */
+	uint32_t log_seq;
+	uint32_t log_offset;
+};
+
 void mail_index_modseq_init(struct mail_index *index);
 
 void mail_index_modseq_enable(struct mail_index *index);
@@ -41,4 +49,8 @@
 
 void mail_index_map_modseq_free(struct mail_index_map_modseq *mmap);
 
+bool mail_index_modseq_get_log_offset(struct mail_index_view *view,
+				      uint64_t modseq, uint32_t *log_seq_r,
+				      uoff_t *log_offset_r);
+
 #endif
--- a/src/lib-storage/index/cydir/cydir-storage.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/cydir/cydir-storage.c	Sat Mar 15 16:24:26 2008 +0200
@@ -431,6 +431,7 @@
 		index_keywords_create,
 		index_keywords_free,
 		index_storage_get_uids,
+		index_storage_get_expunged_uids,
 		index_mail_alloc,
 		index_header_lookup_init,
 		index_header_lookup_deinit,
--- a/src/lib-storage/index/dbox/dbox-storage.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/dbox/dbox-storage.c	Sat Mar 15 16:24:26 2008 +0200
@@ -680,6 +680,7 @@
 		index_keywords_create,
 		index_keywords_free,
 		index_storage_get_uids,
+		index_storage_get_expunged_uids,
 		dbox_mail_alloc,
 		index_header_lookup_init,
 		index_header_lookup_deinit,
--- a/src/lib-storage/index/index-fetch.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/index-fetch.c	Sat Mar 15 16:24:26 2008 +0200
@@ -1,6 +1,8 @@
 /* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "array.h"
+#include "mail-index-modseq.h"
 #include "index-storage.h"
 #include "index-mail.h"
 
@@ -12,3 +14,77 @@
 
 	mail_index_lookup_seq_range(ibox->view, uid1, uid2, seq1_r, seq2_r);
 }
+
+bool index_storage_get_expunged_uids(struct mailbox *box, uint64_t modseq,
+				     const ARRAY_TYPE(seq_range) *uids,
+				     ARRAY_TYPE(seq_range) *expunged_uids)
+{
+#define EXPUNGE_MASK (MAIL_TRANSACTION_EXPUNGE | MAIL_TRANSACTION_EXTERNAL)
+	struct index_mailbox *ibox = (struct index_mailbox *)box;
+	struct mail_transaction_log_view *log_view;
+	const struct mail_index_header *hdr;
+	const struct mail_transaction_header *thdr;
+	const struct mail_transaction_expunge *rec, *end;
+	const struct seq_range *uid_range;
+	unsigned int count;
+	const void *tdata;
+	uint32_t prev_seq, log_seq, min_uid, max_uid;
+	uoff_t prev_offset, log_offset;
+	bool reset;
+
+	if (!mail_index_modseq_get_log_offset(ibox->view, modseq,
+					      &log_seq, &log_offset))
+		return FALSE;
+	if (log_seq > ibox->view->log_file_head_seq ||
+	    (log_seq == ibox->view->log_file_head_seq &&
+	     log_offset >= ibox->view->log_file_head_offset)) {
+		/* we haven't seen this high expunges at all */
+		return TRUE;
+	}
+
+	hdr = mail_index_get_header(ibox->view);
+	log_view = mail_transaction_log_view_open(ibox->index->log);
+	/* we can't trust user-given log offsets, so we have to start reading
+	   from the beginning of the log. */
+	if (mail_transaction_log_view_set(log_view, log_seq, 0,
+					  ibox->view->log_file_head_seq,
+					  ibox->view->log_file_head_offset,
+					  &reset) <= 0) {
+		mail_transaction_log_view_close(&log_view);
+		return FALSE;
+	}
+
+	/* do only minimal range checks while adding the UIDs. */
+	uid_range = array_get(uids, &count);
+	i_assert(count > 0);
+	min_uid = uid_range[0].seq1;
+	max_uid = uid_range[count-1].seq2;
+
+	while (mail_transaction_log_view_next(log_view, &thdr, &tdata) > 0) {
+		if ((thdr->type & EXPUNGE_MASK) != EXPUNGE_MASK)
+			continue;
+
+		mail_transaction_log_view_get_prev_pos(log_view,
+						       &prev_seq, &prev_offset);
+		if (prev_seq < log_seq ||
+		    (prev_offset <= log_offset && prev_seq == log_seq)) {
+			/* still too old expunge. note that
+			   prev_offset==log_offset is also skipped. */
+			continue;
+		}
+
+		rec = tdata;
+		end = rec + thdr->size / sizeof(*rec);
+		for (; rec != end; rec++) {
+			if (!(rec->uid1 > max_uid || rec->uid2 < min_uid)) {
+				seq_range_array_add_range(expunged_uids,
+							  rec->uid1, rec->uid2);
+			}
+		}
+	}
+
+	/* remove UIDs not in the wanted UIDs range */
+	seq_range_array_remove_invert_range(expunged_uids, uids);
+	mail_transaction_log_view_close(&log_view);
+	return TRUE;
+}
--- a/src/lib-storage/index/index-storage.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/index-storage.h	Sat Mar 15 16:24:26 2008 +0200
@@ -146,6 +146,9 @@
 			      struct mailbox_status *status_r);
 void index_storage_get_uids(struct mailbox *box, uint32_t uid1, uint32_t uid2,
 			    uint32_t *seq1_r, uint32_t *seq2_r);
+bool index_storage_get_expunged_uids(struct mailbox *box, uint64_t modseq,
+				     const ARRAY_TYPE(seq_range) *uids,
+				     ARRAY_TYPE(seq_range) *expunged_uids);
 
 struct mailbox_header_lookup_ctx *
 index_header_lookup_init(struct mailbox *box, const char *const headers[]);
--- a/src/lib-storage/index/maildir/maildir-storage.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Sat Mar 15 16:24:26 2008 +0200
@@ -1038,6 +1038,7 @@
 		index_keywords_create,
 		index_keywords_free,
 		index_storage_get_uids,
+		index_storage_get_expunged_uids,
 		index_mail_alloc,
 		index_header_lookup_init,
 		index_header_lookup_deinit,
--- a/src/lib-storage/index/mbox/mbox-storage.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Sat Mar 15 16:24:26 2008 +0200
@@ -988,6 +988,7 @@
 		index_keywords_create,
 		index_keywords_free,
 		index_storage_get_uids,
+		index_storage_get_expunged_uids,
 		index_mail_alloc,
 		index_header_lookup_init,
 		index_header_lookup_deinit,
--- a/src/lib-storage/index/raw/raw-storage.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/index/raw/raw-storage.c	Sat Mar 15 16:24:26 2008 +0200
@@ -288,6 +288,7 @@
 		index_keywords_create,
 		index_keywords_free,
 		index_storage_get_uids,
+		index_storage_get_expunged_uids,
 		index_mail_alloc,
 		index_header_lookup_init,
 		index_header_lookup_deinit,
--- a/src/lib-storage/mail-storage-private.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/mail-storage-private.h	Sat Mar 15 16:24:26 2008 +0200
@@ -125,6 +125,9 @@
 
 	void (*get_uids)(struct mailbox *box, uint32_t uid1, uint32_t uid2,
 			 uint32_t *seq1_r, uint32_t *seq2_r);
+	bool (*get_expunged_uids)(struct mailbox *box, uint64_t modseq,
+				  const ARRAY_TYPE(seq_range) *uids,
+				  ARRAY_TYPE(seq_range) *expunged_uids);
 
 	struct mail *
 		(*mail_alloc)(struct mailbox_transaction_context *t,
--- a/src/lib-storage/mail-storage.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/mail-storage.c	Sat Mar 15 16:24:26 2008 +0200
@@ -608,6 +608,13 @@
 	box->v.get_uids(box, uid1, uid2, seq1_r, seq2_r);
 }
 
+bool mailbox_get_expunged_uids(struct mailbox *box, uint64_t modseq,
+			       const ARRAY_TYPE(seq_range) *uids,
+			       ARRAY_TYPE(seq_range) *expunged_uids)
+{
+	return box->v.get_expunged_uids(box, modseq, uids, expunged_uids);
+}
+
 struct mailbox_header_lookup_ctx *
 mailbox_header_lookup_init(struct mailbox *box, const char *const headers[])
 {
--- a/src/lib-storage/mail-storage.h	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/lib-storage/mail-storage.h	Sat Mar 15 16:24:26 2008 +0200
@@ -3,6 +3,7 @@
 
 struct message_size;
 
+#include "seq-range-array.h"
 #include "file-lock.h"
 #include "mail-types.h"
 #include "mail-error.h"
@@ -60,6 +61,8 @@
 enum mailbox_feature {
 	/* Enable tracking modsequences */
 	MAILBOX_FEATURE_CONDSTORE	= 0x01,
+	/* Enable tracking expunge modsequences */
+	MAILBOX_FEATURE_QRESYNC		= 0x02
 };
 
 enum mailbox_status_items {
@@ -354,6 +357,12 @@
 /* Convert uid range to sequence range. */
 void mailbox_get_uids(struct mailbox *box, uint32_t uid1, uint32_t uid2,
 		      uint32_t *seq1_r, uint32_t *seq2_r);
+/* Get list of UIDs expunged after modseq and within the given range.
+   UIDs that have been expunged after the last mailbox sync aren't returned.
+   Returns TRUE if ok, FALSE if modseq is lower than we can check for. */
+bool mailbox_get_expunged_uids(struct mailbox *box, uint64_t modseq,
+			       const ARRAY_TYPE(seq_range) *uids,
+			       ARRAY_TYPE(seq_range) *expunged_uids);
 
 /* Initialize header lookup for given headers. */
 struct mailbox_header_lookup_ctx *
--- a/src/util/idxview.c	Sat Mar 15 15:24:45 2008 +0200
+++ b/src/util/idxview.c	Sat Mar 15 16:24:26 2008 +0200
@@ -7,6 +7,8 @@
 #include "file-lock.h"
 #include "mail-index-private.h"
 #include "mail-cache-private.h"
+#include "mail-cache-private.h"
+#include "mail-index-modseq.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -89,6 +91,14 @@
 
 		printf("header\n");
 		printf(" - last_dirty_flush_stamp = %s\n", unixdate2str(hdr->last_dirty_flush_stamp));
+	} else if (strcmp(ext->name, "modseq") == 0) {
+		const struct mail_index_modseq_header *hdr = data;
+
+		printf("header\n");
+		printf(" - highest_modseq = %llu\n",
+		       (unsigned long long)hdr->highest_modseq);
+		printf(" - log_seq ...... = %u\n", hdr->log_seq);
+		printf(" - log_offset ... = %u\n", hdr->log_offset);
 	} else {
 		printf("header ........ = %s\n",
 		       binary_to_hex(data, ext->hdr_size));