changeset 6898:e739cffd05ef HEAD

FTS API changes and Squat rewrite. Squat is still missing expunge handling.
author Timo Sirainen <tss@iki.fi>
date Sun, 02 Dec 2007 23:51:46 +0200
parents 0a3186f44dff
children 69babcc2fb80
files src/plugins/fts-lucene/fts-backend-lucene.c src/plugins/fts-lucene/lucene-wrapper.cc src/plugins/fts-squat/fts-backend-squat.c src/plugins/fts-squat/squat-test.c src/plugins/fts-squat/squat-trie-private.h src/plugins/fts-squat/squat-trie.c src/plugins/fts-squat/squat-trie.h src/plugins/fts-squat/squat-uidlist.c src/plugins/fts-squat/squat-uidlist.h src/plugins/fts/Makefile.am src/plugins/fts/fts-api-private.h src/plugins/fts/fts-api.c src/plugins/fts/fts-api.h src/plugins/fts/fts-storage.c
diffstat 14 files changed, 2977 insertions(+), 3310 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/fts-lucene/fts-backend-lucene.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-lucene/fts-backend-lucene.c	Sun Dec 02 23:51:46 2007 +0200
@@ -101,23 +101,26 @@
 	return lucene_index_get_last_uid(backend->lstorage->index, last_uid_r);
 }
 
-static struct fts_backend_build_context *
-fts_backend_lucene_build_init(struct fts_backend *_backend, uint32_t *last_uid_r)
+static int
+fts_backend_lucene_build_init(struct fts_backend *_backend,
+			      uint32_t *last_uid_r,
+			      struct fts_backend_build_context **ctx_r)
 {
 	struct lucene_fts_backend *backend =
 		(struct lucene_fts_backend *)_backend;
 	struct fts_backend_build_context *ctx;
 
 	fts_backend_select(backend);
+	if (lucene_index_build_init(backend->lstorage->index,
+				    &backend->last_uid) < 0)
+		return -1;
 
 	ctx = i_new(struct fts_backend_build_context, 1);
 	ctx->backend = _backend;
-	if (lucene_index_build_init(backend->lstorage->index,
-				    &backend->last_uid) < 0)
-		ctx->failed = TRUE;
 
 	*last_uid_r = backend->last_uid;
-	return ctx;
+	*ctx_r = ctx;
+	return 0;
 }
 
 static int
@@ -182,22 +185,24 @@
 
 static int
 fts_backend_lucene_lookup(struct fts_backend *_backend,
-			  enum fts_lookup_flags flags,
-			  const char *key, ARRAY_TYPE(seq_range) *result)
+			  const char *key, enum fts_lookup_flags flags,
+			  ARRAY_TYPE(seq_range) *definite_uids,
+			  ARRAY_TYPE(seq_range) *maybe_uids)
 {
 	struct lucene_fts_backend *backend =
 		(struct lucene_fts_backend *)_backend;
 
 	i_assert((flags & FTS_LOOKUP_FLAG_INVERT) == 0);
 
+	array_clear(maybe_uids);
 	fts_backend_select(backend);
 	return lucene_index_lookup(backend->lstorage->index,
-				   flags, key, result);
+				   flags, key, definite_uids);
 }
 
 struct fts_backend fts_backend_lucene = {
 	MEMBER(name) "lucene",
-	MEMBER(flags) FTS_BACKEND_FLAG_DEFINITE_LOOKUPS,
+	MEMBER(flags) 0,
 
 	{
 		fts_backend_lucene_init,
--- a/src/plugins/fts-lucene/lucene-wrapper.cc	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-lucene/lucene-wrapper.cc	Sun Dec 02 23:51:46 2007 +0200
@@ -549,7 +549,7 @@
 	const char *quoted_key;
 	int ret = 0;
 
-	i_assert((flags & (FTS_LOOKUP_FLAG_HEADERS|FTS_LOOKUP_FLAG_BODY)) != 0);
+	i_assert((flags & (FTS_LOOKUP_FLAG_HEADER|FTS_LOOKUP_FLAG_BODY)) != 0);
 
 	if (lucene_index_open_search(index) <= 0)
 		return -1;
@@ -567,7 +567,7 @@
 	BooleanQuery lookup_query;
 	Query *content_query1 = NULL, *content_query2 = NULL;
 	try {
-		if ((flags & FTS_LOOKUP_FLAG_HEADERS) != 0) {
+		if ((flags & FTS_LOOKUP_FLAG_HEADER) != 0) {
 			content_query1 = QueryParser::parse(tkey, _T("headers"),
 							    index->analyzer);
 			lookup_query.add(content_query1, false, false);
--- a/src/plugins/fts-squat/fts-backend-squat.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/fts-backend-squat.c	Sun Dec 02 23:51:46 2007 +0200
@@ -16,7 +16,7 @@
 
 struct squat_fts_backend_build_context {
 	struct fts_backend_build_context ctx;
-	struct squat_trie_build_context *trie_ctx;
+	struct squat_trie_build_context *build_ctx;
 };
 
 static struct fts_backend *fts_backend_squat_init(struct mailbox *box)
@@ -43,7 +43,7 @@
 	backend = i_new(struct squat_fts_backend, 1);
 	backend->backend = fts_backend_squat;
 	backend->trie =
-		squat_trie_open(t_strconcat(path, "/"SQUAT_FILE_PREFIX, NULL),
+		squat_trie_init(t_strconcat(path, "/"SQUAT_FILE_PREFIX, NULL),
 				status.uidvalidity, storage->lock_method,
 				mmap_disable);
 	return &backend->backend;
@@ -54,7 +54,7 @@
 	struct squat_fts_backend *backend =
 		(struct squat_fts_backend *)_backend;
 
-	squat_trie_close(backend->trie);
+	squat_trie_deinit(&backend->trie);
 	i_free(backend);
 }
 
@@ -67,28 +67,39 @@
 	return squat_trie_get_last_uid(backend->trie, last_uid_r);
 }
 
-static struct fts_backend_build_context *
-fts_backend_squat_build_init(struct fts_backend *_backend, uint32_t *last_uid_r)
+static int
+fts_backend_squat_build_init(struct fts_backend *_backend, uint32_t *last_uid_r,
+			     struct fts_backend_build_context **ctx_r)
 {
 	struct squat_fts_backend *backend =
 		(struct squat_fts_backend *)_backend;
 	struct squat_fts_backend_build_context *ctx;
+	struct squat_trie_build_context *build_ctx;
+
+	if (squat_trie_build_init(backend->trie, last_uid_r, &build_ctx) < 0)
+		return -1;
 
 	ctx = i_new(struct squat_fts_backend_build_context, 1);
 	ctx->ctx.backend = _backend;
-	ctx->trie_ctx = squat_trie_build_init(backend->trie, last_uid_r);
-	return &ctx->ctx;
+	ctx->build_ctx = build_ctx;
+
+	*ctx_r = &ctx->ctx;
+	return 0;
 }
 
 static int
 fts_backend_squat_build_more(struct fts_backend_build_context *_ctx,
 			     uint32_t uid, const unsigned char *data,
-			     size_t size, bool headers ATTR_UNUSED)
+			     size_t size, bool headers)
 {
 	struct squat_fts_backend_build_context *ctx =
 		(struct squat_fts_backend_build_context *)_ctx;
+	enum squat_index_type squat_type;
 
-	return squat_trie_build_more(ctx->trie_ctx, uid, data, size);
+	squat_type = headers ? SQUAT_INDEX_TYPE_HEADER :
+		SQUAT_INDEX_TYPE_BODY;
+	return squat_trie_build_more(ctx->build_ctx, uid, squat_type,
+				     data, size);
 }
 
 static int
@@ -98,7 +109,7 @@
 		(struct squat_fts_backend_build_context *)_ctx;
 	int ret;
 
-	ret = squat_trie_build_deinit(ctx->trie_ctx);
+	ret = squat_trie_build_deinit(&ctx->build_ctx);
 	i_free(ctx);
 	return ret;
 }
@@ -109,55 +120,11 @@
 {
 }
 
-static int get_uids(struct mailbox *box, ARRAY_TYPE(seq_range) *uids,
-		    unsigned int *message_count_r)
-{
-	struct mail_search_arg search_arg;
-        struct mailbox_transaction_context *t;
-	struct mail_search_context *ctx;
-	struct mail *mail;
-	unsigned int count = 0;
-	int ret;
-
-	memset(&search_arg, 0, sizeof(search_arg));
-	search_arg.type = SEARCH_ALL;
-
-	t = mailbox_transaction_begin(box, 0);
-	ctx = mailbox_search_init(t, NULL, &search_arg, NULL);
-
-	mail = mail_alloc(t, 0, NULL);
-	while (mailbox_search_next(ctx, mail) > 0) {
-		seq_range_array_add(uids, 0, mail->uid);
-		count++;
-	}
-	mail_free(&mail);
-
-	ret = mailbox_search_deinit(&ctx);
-	mailbox_transaction_rollback(&t);
-
-	*message_count_r = count;
-	return ret;
-}
-
 static void
 fts_backend_squat_expunge_finish(struct fts_backend *_backend,
 				 struct mailbox *box, bool committed)
 {
-	struct squat_fts_backend *backend =
-		(struct squat_fts_backend *)_backend;
-	ARRAY_TYPE(seq_range) uids = ARRAY_INIT;
-	unsigned int count;
-
-	if (!committed)
-		return;
-
-	t_push();
-	t_array_init(&uids, 128);
-	if (get_uids(box, &uids, &count) == 0) {
-		(void)squat_trie_mark_having_expunges(backend->trie, &uids,
-						      count);
-	}
-	t_pop();
+	/* FIXME */
 }
 
 static int fts_backend_squat_lock(struct fts_backend *_backend)
@@ -165,45 +132,39 @@
 	struct squat_fts_backend *backend =
 		(struct squat_fts_backend *)_backend;
 
-	return squat_trie_lock(backend->trie, F_RDLCK);
+	squat_trie_refresh(backend->trie);
+	return 1;
 }
 
-static void fts_backend_squat_unlock(struct fts_backend *_backend)
+static void fts_backend_squat_unlock(struct fts_backend *_backend ATTR_UNUSED)
 {
-	struct squat_fts_backend *backend =
-		(struct squat_fts_backend *)_backend;
-
-	squat_trie_unlock(backend->trie);
 }
 
 static int
-fts_backend_squat_lookup(struct fts_backend *_backend,
+fts_backend_squat_lookup(struct fts_backend *_backend, const char *key,
 			 enum fts_lookup_flags flags,
-			 const char *key, ARRAY_TYPE(seq_range) *result)
+			 ARRAY_TYPE(seq_range) *definite_uids,
+			 ARRAY_TYPE(seq_range) *maybe_uids)
 {
 	struct squat_fts_backend *backend =
 		(struct squat_fts_backend *)_backend;
-
-	i_assert((flags & FTS_LOOKUP_FLAG_INVERT) == 0);
-	return squat_trie_lookup(backend->trie, result, key);
-}
-
-static int
-fts_backend_squat_filter(struct fts_backend *_backend,
-			 enum fts_lookup_flags flags,
-			 const char *key, ARRAY_TYPE(seq_range) *result)
-{
-	struct squat_fts_backend *backend =
-		(struct squat_fts_backend *)_backend;
+	enum squat_index_type squat_type = 0;
 
 	i_assert((flags & FTS_LOOKUP_FLAG_INVERT) == 0);
 
-	return squat_trie_filter(backend->trie, result, key);
+	if ((flags & FTS_LOOKUP_FLAG_HEADER) != 0)
+		squat_type |= SQUAT_INDEX_TYPE_HEADER;
+	if ((flags & FTS_LOOKUP_FLAG_BODY) != 0)
+		squat_type |= SQUAT_INDEX_TYPE_BODY;
+	i_assert(squat_type != 0);
+
+	return squat_trie_lookup(backend->trie, key, flags,
+				 definite_uids, maybe_uids);
 }
 
 struct fts_backend fts_backend_squat = {
 	MEMBER(name) "squat",
-	MEMBER(flags) FTS_BACKEND_FLAG_EXACT_LOOKUPS,
+	MEMBER(flags) FTS_BACKEND_FLAG_SUBSTRING_LOOKUPS,
 
 	{
 		fts_backend_squat_init,
@@ -217,6 +178,6 @@
 		fts_backend_squat_lock,
 		fts_backend_squat_unlock,
 		fts_backend_squat_lookup,
-		fts_backend_squat_filter
+		NULL
 	}
 };
--- a/src/plugins/fts-squat/squat-test.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/squat-test.c	Sun Dec 02 23:51:46 2007 +0200
@@ -31,24 +31,29 @@
 
 int main(int argc ATTR_UNUSED, char *argv[])
 {
+	const char *trie_path = "/tmp/squat-test-index.search";
+	const char *uidlist_path = "/tmp/squat-test-index.search.uids";
 	struct squat_trie *trie;
 	struct squat_trie_build_context *build_ctx;
 	struct istream *input;
-	ARRAY_TYPE(seq_range) result;
+	struct stat trie_st, uidlist_st;
+	ARRAY_TYPE(seq_range) definite_uids, maybe_uids;
 	char *line, *str, buf[4096];
-	int fd;
-	ssize_t ret;
-	unsigned int last = 0, seq = 0, leaves, uid_lists_mem, uid_lists_count;
+	int ret, fd;
+	unsigned int last = 0, seq = 1, node_count, uidlist_count;
+	enum squat_index_type index_type;
+	bool data_header = TRUE, first = TRUE, skip_body = FALSE;
+	bool mime_header = TRUE;
 	uint32_t last_uid;
-	size_t mem;
+	size_t trie_mem, uidlist_mem;
 	clock_t clock_start, clock_end;
 	struct timeval tv_start, tv_end;
 	double cputime;
 
 	lib_init();
-	(void)unlink("/tmp/squat-test-index.search");
-	(void)unlink("/tmp/squat-test-index.search.uids");
-	trie = squat_trie_open("/tmp/squat-test-index.search", time(NULL),
+	(void)unlink(trie_path);
+	(void)unlink(uidlist_path);
+	trie = squat_trie_init(trie_path, time(NULL),
 			       FILE_LOCK_METHOD_FCNTL, FALSE);
 
 	clock_start = clock();
@@ -58,24 +63,63 @@
 	if (fd == -1)
 		return 1;
 
-	build_ctx = squat_trie_build_init(trie, &last_uid);
+	if (squat_trie_build_init(trie, &last_uid, &build_ctx) < 0)
+		return 1;
+
 	input = i_stream_create_fd(fd, 0, FALSE);
-	while ((line = i_stream_read_next_line(input)) != NULL) {
+	ret = 0;
+	while (ret == 0 && (line = i_stream_read_next_line(input)) != NULL) {
 		if (last != input->v_offset/(1024*100)) {
 			fprintf(stderr, "\r%ukB", (unsigned)(input->v_offset/1024));
 			fflush(stderr);
 			last = input->v_offset/(1024*100);
 		}
 		if (strncmp(line, "From ", 5) == 0) {
-			seq++;
+			if (!first)
+				seq++;
+			data_header = TRUE;
+			skip_body = FALSE;
+			mime_header = TRUE;
 			continue;
 		}
+		first = FALSE;
+
+		if (strncmp(line, "--", 2) == 0) {
+			skip_body = FALSE;
+			mime_header = TRUE;
+		}
 
-		if (squat_trie_build_more(build_ctx, seq,
-					  (const void *)line, strlen(line)) < 0)
-			break;
+		if (mime_header) {
+			if (*line == '\0') {
+				if (data_header)
+					seq++;
+				data_header = FALSE;
+				mime_header = FALSE;
+				continue;
+			}
+
+			if (strncasecmp(line, "Content-Type:", 13) == 0 &&
+			    strncasecmp(line, "Content-Type: text/", 19) != 0 &&
+			    strncasecmp(line, "Content-Type: message/", 22) != 0)
+				skip_body = TRUE;
+			else if (strncasecmp(line, "Content-Transfer-Encoding: base64", 33) == 0)
+				skip_body = TRUE;
+		} else if (skip_body)
+			continue;
+		if (*line == '\0')
+			continue;
+
+		index_type = data_header ? SQUAT_INDEX_TYPE_HEADER :
+			SQUAT_INDEX_TYPE_BODY;
+		ret = squat_trie_build_more(build_ctx, seq, index_type,
+					    (const void *)line, strlen(line));
 	}
-	squat_trie_build_deinit(build_ctx);
+	if (squat_trie_build_deinit(&build_ctx) < 0)
+		ret = -1;
+	if (ret < 0) {
+		printf("build broken\n");
+		return 1;
+	}
 
 	clock_end = clock();
 	gettimeofday(&tv_end, NULL);
@@ -87,37 +131,54 @@
 		(tv_end.tv_usec - tv_start.tv_usec)/1000000.0,
 		input->v_offset / cputime / (1024*1024));
 
-	mem = squat_trie_mem_used(trie, &leaves);
-	uid_lists_mem = squat_uidlist_mem_used(squat_trie_get_uidlist(trie),
-					       &uid_lists_count);
-	fprintf(stderr, " - %u bytes in %u nodes (%.02f%%)\n"
-		" - %u bytes in %u uid_lists (%.02f%%)\n"
-		" - %u bytes total of %"PRIuUOFF_T" (%.02f%%)\n",
-		(unsigned)mem, leaves, mem / (float)input->v_offset * 100.0,
-		uid_lists_mem, uid_lists_count,
-		uid_lists_mem / (float)input->v_offset * 100.0,
-		(unsigned)(uid_lists_mem + mem), input->v_offset,
-		(uid_lists_mem + mem) / (float)input->v_offset * 100.0);
+	if (stat(trie_path, &trie_st) < 0)
+		i_error("stat(%s) failed: %m", trie_path);
+	if (stat(uidlist_path, &uidlist_st) < 0)
+		i_error("stat(%s) failed: %m", uidlist_path);
+
+	trie_mem = squat_trie_mem_used(trie, &node_count);
+	uidlist_mem = squat_uidlist_mem_used(squat_trie_get_uidlist(trie),
+					     &uidlist_count);
+	fprintf(stderr, " - memory: %uk for trie, %uk for uidlist\n",
+		(unsigned)(trie_mem/1024), (unsigned)(uidlist_mem/1024));
+	fprintf(stderr, " - %"PRIuUOFF_T" bytes in %u nodes (%.02f%%)\n",
+		trie_st.st_size, node_count,
+		trie_st.st_size / (float)input->v_offset * 100.0);
+	fprintf(stderr, " - %"PRIuUOFF_T" bytes in %u UID lists (%.02f%%)\n",
+		uidlist_st.st_size, uidlist_count,
+		uidlist_st.st_size / (float)input->v_offset * 100.0);
+	fprintf(stderr, " - %"PRIuUOFF_T" bytes total of %"
+		PRIuUOFF_T" (%.02f%%)\n",
+		(trie_st.st_size + uidlist_st.st_size), input->v_offset,
+		(trie_st.st_size + uidlist_st.st_size) /
+		(float)input->v_offset * 100.0);
 
 	i_stream_unref(&input);
 	close(fd);
 
-	i_array_init(&result, 128);
+	i_array_init(&definite_uids, 128);
+	i_array_init(&maybe_uids, 128);
 	while ((str = fgets(buf, sizeof(buf), stdin)) != NULL) {
 		ret = strlen(str)-1;
 		str[ret] = 0;
 
-		array_clear(&result);
 		gettimeofday(&tv_start, NULL);
-		if (!squat_trie_lookup(trie, &result, str))
-			printf("No matches\n");
-		else {
+		ret = squat_trie_lookup(trie, str, SQUAT_INDEX_TYPE_HEADER |
+					SQUAT_INDEX_TYPE_BODY,
+					&definite_uids, &maybe_uids);
+		if (ret > 0) {
 			gettimeofday(&tv_end, NULL);
 			printf(" - Search took %.05f CPU seconds\n",
 			       (tv_end.tv_sec - tv_start.tv_sec) +
 			       (tv_end.tv_usec - tv_start.tv_usec)/1000000.0);
-			result_print(&result);
-		}
+			printf(" - definite uids: ");
+			result_print(&definite_uids);
+			printf(" - maybe uids: ");
+			result_print(&maybe_uids);
+		} else if (ret == 0)
+			printf("not found\n");
+		else
+			printf("error\n");
 	}
 	return 0;
 }
--- a/src/plugins/fts-squat/squat-trie-private.h	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/squat-trie-private.h	Sun Dec 02 23:51:46 2007 +0200
@@ -1,30 +1,174 @@
 #ifndef SQUAT_TRIE_PRIVATE_H
 #define SQUAT_TRIE_PRIVATE_H
 
-struct squat_trie_header {
+#include "squat-trie.h"
+
+#define SQUAT_TRIE_VERSION 2
+#define SQUAT_TRIE_LOCK_TIMEOUT 60
+
+struct squat_file_header {
 	uint8_t version;
 	uint8_t unused[3];
 
+	uint32_t indexid;
 	uint32_t uidvalidity;
 	uint32_t used_file_size;
 	uint32_t deleted_space;
 	uint32_t node_count;
-	uint32_t modify_counter;
 
 	uint32_t root_offset;
+	uint32_t root_unused_uids;
+	uint32_t root_next_uid;
+	uint32_t root_uidlist_idx;
 };
 
 /*
-packed_node {
-	packed ((8bit_chars_count << 1) | have_16bit_chars);
-	uint8_t 8bit_chars[8bit_chars_count];
-	uint32_t idx[8bit_chars_count];
-	if (have_16bit_chars) {
-		packed 16bit_chars_count;
-		uint16_t 16bit_chars[16bit_chars_count];
-		uint32_t idx[16bit_chars_count];
-	}
-}
+   node file: FIXME: no up-to-date
+
+   struct squat_file_header;
+
+   // children are written before their parents
+   node[] {
+     uint8_t child_count;
+     unsigned char chars[child_count];
+     packed neg_diff_to_first_child_offset; // relative to node
+     packed diff_to_prev_offset[child_count-1];
+     packed[child_count] {
+       // unused_uids_count == uid if have_uid_offset bit is zero
+       (unused_uids_count << 1) | (have_uid_offset);
+       [diff_to_prev_uid_offset;] // first one is relative to zero
+     }
+   }
 */
 
+struct squat_node {
+	unsigned int child_count:8;
+
+	/* children.leaf_string contains this many bytes */
+	unsigned int leaf_string_length:16;
+
+	/* TRUE = children.data contains our children.
+	   FALSE = children.offset contains offset to our children in the
+	   index file. */
+	unsigned int children_not_mapped:1;
+	/* When allocating our children, use a sequential array. */
+	unsigned int want_sequential:1;
+	/* This node's children are in a sequential array, meaning that the
+	   first SEQUENTIAL_COUNT children have chars[n] = n. */
+	unsigned int have_sequential:1;
+
+	/* Number of UIDs that exists in parent node but not in this one.
+	   This is mainly used when adding new UIDs to our children to set
+	   the UID to be relative to this node's UID list. */
+	uint32_t unused_uids;
+
+	/* next_uid=0 means there are no UIDs in this node, otherwise
+	   next_uid-1 is the last UID added to this node. */
+	uint32_t next_uid;
+	uint32_t uid_list_idx;
+
+	/*
+	   struct {
+	     unsigned char chars[child_count];
+	     struct squat_node[child_count];
+	   } *children;
+	*/
+	union {
+		/* children_not_mapped determines if data or offset should
+		   be used. */
+		void *data;
+		unsigned char *leaf_string;
+		unsigned char static_leaf_string[sizeof(void *)];
+		uint32_t offset;
+	} children;
+};
+/* Return pointer to node.children.chars[] */
+#define NODE_CHILDREN_CHARS(node) \
+	((unsigned char *)(node)->children.data)
+/* Return pointer to node.children.node[] */
+#define NODE_CHILDREN_NODES(_node) \
+	((struct squat_node *)(NODE_CHILDREN_CHARS(_node) + \
+			       MEM_ALIGN((_node)->child_count)))
+/* Return number of bytes allocated in node.children.data */
+#define NODE_CHILDREN_ALLOC_SIZE(child_count) \
+	(MEM_ALIGN(child_count) + \
+	 ((child_count) / 8 + 1) * 8 * sizeof(struct squat_node))
+/* Return TRUE if children.leaf_string is set. */
+#define NODE_IS_DYNAMIC_LEAF(node) \
+	((node)->leaf_string_length > \
+		sizeof((node)->children.static_leaf_string))
+/* Return node's leaf string. Assumes that it is set. */
+#define NODE_LEAF_STRING(node) \
+	(NODE_IS_DYNAMIC_LEAF(node) ? \
+	 (node)->children.leaf_string : (node)->children.static_leaf_string)
+struct squat_trie {
+	struct squat_node root;
+	struct squat_uidlist *uidlist;
+
+	struct squat_file_header hdr;
+	size_t node_alloc_size;
+	unsigned int unmapped_child_count;
+
+	enum file_lock_method lock_method;
+	uint32_t uidvalidity;
+
+	char *path;
+	int fd;
+	uoff_t locked_file_size;
+
+	void *mmap_base;
+	size_t mmap_size;
+
+	unsigned char normalize_map[256];
+
+	unsigned int corrupted:1;
+};
+
+#define SQUAT_PACK_MAX_SIZE ((sizeof(uint32_t) * 8 + 7) / 7)
+
+static inline void squat_pack_num(uint8_t **p, uint32_t num)
+{
+	/* number continues as long as the highest bit is set */
+	while (num >= 0x80) {
+		**p = (num & 0x7f) | 0x80;
+		*p += 1;
+		num >>= 7;
+	}
+
+	**p = num;
+	*p += 1;
+}
+
+static inline uint32_t squat_unpack_num(const uint8_t **p, const uint8_t *end)
+{
+	const uint8_t *c = *p;
+	uint32_t value = 0;
+	unsigned int bits = 0;
+
+	for (;;) {
+		if (unlikely(c == end)) {
+			/* we should never see EOF */
+			return 0;
+		}
+
+		value |= (*c & 0x7f) << bits;
+		if (*c < 0x80)
+			break;
+
+		bits += 7;
+		c++;
+	}
+
+	if (unlikely(bits > 32-7)) {
+		/* broken input */
+		*p = end;
+		return 0;
+	}
+
+	*p = c + 1;
+	return value;
+}
+
+void squat_trie_delete(struct squat_trie *trie);
+
 #endif
--- a/src/plugins/fts-squat/squat-trie.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/squat-trie.c	Sun Dec 02 23:51:46 2007 +0200
@@ -1,2031 +1,1387 @@
-/* Copyright (c) 2006-2007 Dovecot authors, see the included COPYING file */
+/* Copyright (c) 2007 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
 #include "array.h"
-#include "bsearch-insert-pos.h"
-#include "file-cache.h"
-#include "file-lock.h"
+#include "str.h"
 #include "istream.h"
 #include "ostream.h"
-#include "read-full.h"
-#include "write-full.h"
-#include "mmap-util.h"
-#include "unichar.h"
+#include "seq-range-array.h"
 #include "squat-uidlist.h"
-#include "squat-trie.h"
 #include "squat-trie-private.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <ctype.h>
 #include <fcntl.h>
-#include <ctype.h>
-
-/* 8bit character counter holds only 255, so we can't use 256. */
-#define MAX_8BIT_CHAR_COUNT 255
-
-#define FAST_8BIT_LEVEL 2
-
-#define TRIE_COMPRESS_PERCENTAGE 30
-#define TRIE_COMPRESS_MIN_SIZE (1024*50)
-
-#define SQUAT_TRIE_VERSION 1
-#define SQUAT_TRIE_LOCK_TIMEOUT 60
-
-/* for non-x86 use memcpy() when accessing unaligned int* addresses */
-#if defined(__i386__) || defined(__x86_64__)
-#  define ALLOW_UNALIGNED_ACCESS
-#endif
-
-#define BLOCK_SIZE 4
-
-#define ALIGN(size) \
-	(((size) + sizeof(void *)-1) & ~((unsigned int) sizeof(void *)-1))
+#include <time.h>
+#include <sys/mman.h>
 
-struct squat_trie {
-	char *filepath;
-	int fd;
-	dev_t dev;
-	ino_t ino;
-
-	enum file_lock_method lock_method;
-	struct file_lock *file_lock;
-	int lock_count;
-	int lock_type; /* F_RDLCK / F_WRLCK */
-
-	struct file_cache *file_cache;
-	uint32_t file_cache_modify_counter;
+#define MAX_FAST_LEVEL 3
+#define MAX_PARTIAL_LEN 4
+#define MAX_FULL_LEN 4
 
-	void *mmap_base; /* NULL with mmap_disable=yes */
-	const uint8_t *const_mmap_base;
-	size_t mmap_size;
-
-	const struct squat_trie_header *hdr;
-	uint32_t uidvalidity;
-
-	char *uidlist_filepath;
-	struct squat_uidlist *uidlist;
-	struct trie_node *root;
-	buffer_t *buf;
-
-	unsigned int corrupted:1;
-	unsigned int mmap_disable:1;
-};
+#define SEQUENTIAL_COUNT 46
 
 struct squat_trie_build_context {
 	struct squat_trie *trie;
-
 	struct ostream *output;
 
-	uint32_t prev_uid;
-	unsigned int prev_added_size;
-	uint16_t prev_added[BLOCK_SIZE-1];
-
-	unsigned int node_count;
-	unsigned int deleted_space;
-
-	unsigned int modified:1;
-	unsigned int failed:1;
-	unsigned int locked:1;
+	uint32_t first_uid;
+	unsigned int compress_nodes:1;
 };
 
-struct squat_trie_compress_context {
-	struct squat_trie *trie;
-
-	const char *tmp_path;
-	struct ostream *output;
-	int fd;
-
-	struct squat_uidlist_compress_ctx *uidlist_ctx;
-
-	unsigned int node_count;
+struct squat_trie_iterate_node {
+	struct squat_node *node;
+	unsigned int idx;
 };
 
-struct trie_node {
-	/* new characters have been added to this node */
-	uint8_t resized:1;
-	/* idx pointers have been updated */
-	uint8_t modified:1;
-	uint8_t chars_8bit_count;
-	uint16_t chars_16bit_count;
-
-	uint32_t file_offset;
-	uint32_t orig_size;
-
-	/* the node pointers are valid as long as their lowest bit is 0,
-	   otherwise they're offsets to the trie file (>> 1).
-
-	   in leaf nodes the children pointers are uint32_t uid_list_idx[]; */
-	/* uint8_t 8bit_chars[chars_8bit_count]; */
-	/* struct trie_node *children[chars_8bit_count]; */
-	/* uint16_t 16bit_chars[chars_16bit_count]; */
-	/* struct trie_node *children[chars_16bit_count]; */
+struct squat_trie_iterate_context {
+	struct squat_trie *trie;
+	struct squat_trie_iterate_node cur;
+	ARRAY_DEFINE(parents, struct squat_trie_iterate_node);
+	bool failed;
 };
-#define NODE_CHARS8(node) \
-	(uint8_t *)(node + 1)
-#define NODE_CHILDREN8(node) \
-	(struct trie_node **) \
-		((char *)((node) + 1) + \
-		 ALIGN(sizeof(uint8_t) * ((node)->chars_8bit_count)))
-#define NODE_CHARS16(node, level) \
-	(uint16_t *)((char *)NODE_CHILDREN8(node) + \
-		((node)->chars_8bit_count) * \
-		((level) == BLOCK_SIZE ? \
-		 sizeof(uint32_t) : sizeof(struct trie_node *)))
-#define NODE_CHILDREN16(node, level) \
-	(struct trie_node **) \
-		((char *)NODE_CHARS16(node, level) + \
-		 ALIGN(sizeof(uint16_t) * ((node)->chars_16bit_count)))
+
+static int squat_trie_map(struct squat_trie *trie);
 
-static void free_node(struct trie_node *node, unsigned int level);
-static void squat_trie_compress_chars8(struct trie_node *node);
-static int
-squat_trie_compress_node(struct squat_trie_compress_context *ctx,
-			 struct trie_node *node, unsigned int level);
-static int trie_write_node(struct squat_trie_build_context *ctx,
-			   unsigned int level, struct trie_node *node);
-static int
-squat_trie_build_flush(struct squat_trie_build_context *ctx, bool finish);
-
-static int chr_8bit_cmp(const void *_key, const void *_chr)
+void squat_trie_delete(struct squat_trie *trie)
 {
-	const uint16_t *key = _key;
-	const uint8_t *chr = _chr;
-
-	return *key - *chr;
-}
-
-static int chr_16bit_cmp(const void *_key, const void *_chr)
-{
-	const uint16_t *key = _key, *chr = _chr;
-
-	return *key - *chr;
+	if (unlink(trie->path) < 0 && errno != ENOENT)
+		i_error("unlink(%s) failed: %m", trie->path);
+	squat_uidlist_delete(trie->uidlist);
 }
 
-void squat_trie_pack_num(buffer_t *buffer, uint32_t num)
-{
-	uint8_t c;
-
-	/* number continues as long as the highest bit is set */
-	while (num >= 0x80) {
-		c = (num & 0x7f) | 0x80;
-		num >>= 7;
-
-		buffer_append(buffer, &c, 1);
-	}
-
-	c = num;
-	buffer_append(buffer, &c, 1);
-}
-
-uint32_t squat_trie_unpack_num(const uint8_t **p, const uint8_t *end)
+static void squat_trie_set_corrupted(struct squat_trie *trie)
 {
-	const uint8_t *c = *p;
-	uint32_t value = 0;
-	unsigned int bits = 0;
-
-	while (c != end && *c >= 0x80) {
-		value |= (*c & 0x7f) << bits;
-		bits += 7;
-		c++;
-	}
-
-	if (c == end) {
-		/* last number shouldn't end with high bit */
-		return 0;
-	}
-	if (bits > 32-7) {
-		/* we have only 32bit numbers */
-		return 0;
-	}
-
-	value |= (*c & 0x7f) << bits;
-	*p = c + 1;
-	return value;
+	trie->corrupted = TRUE;
+	i_error("Corrupted file %s", trie->path);
+	squat_trie_delete(trie);
 }
 
-static const uint16_t *
-data_normalize(const void *data, size_t size, buffer_t *dest)
+static void squat_trie_normalize_map_build(struct squat_trie *trie)
 {
-	const unsigned char *src = data;
-	size_t i;
-
-	buffer_set_used_size(dest, 0);
-	for (i = 0; i < size; i++) {
-		uint16_t chr;
+	static unsigned char valid_chars[] =
+		"EOTIRSACDNLMVUGPHBFWYXKJQZ0123456789@.-+#$%_&";
+	unsigned int i, j;
 
-		if (src[i] <= 32)
-			chr = 0;
-		else if (src[i] <= 'z')
-			chr = i_toupper(src[i]) - 32;
-		else if (src[i] < 128)
-			chr = src[i] - 32 - 26;
-		else {
-			/* UTF-8 input */
-			unichar_t uchr;
+	memset(trie->normalize_map, 0, sizeof(trie->normalize_map));
 
-			/* FIXME: can we do anything better than just
-			   truncate with >16bit values? */
-			if (uni_utf8_get_char_n(src+i, size-i, &uchr) <= 0)
-				chr = 0;
-			else {
-				uchr -= 32 - 26;
-				chr = uchr < (uint16_t)-1 ? uchr : 0;
-			}
-			i += uni_utf8_char_bytes(src[i]) - 1;
-		}
-		buffer_append(dest, &chr, sizeof(chr));
-	}
-
-	return dest->data;
-}
+#if 1
+	for (i = 0, j = 1; i < sizeof(valid_chars)-1; i++) {
+		unsigned char chr = valid_chars[i];
 
-static void
-squat_trie_set_syscall_error(struct squat_trie *trie, const char *function)
-{
-	i_error("%s failed with index search file %s: %m",
-		function, trie->filepath);
-}
-
-void squat_trie_set_corrupted(struct squat_trie *trie, const char *reason)
-{
-	i_error("Corrupted index search file %s: %s", trie->filepath, reason);
-
-	(void)unlink(trie->filepath);
-	(void)unlink(trie->uidlist_filepath);
-	trie->corrupted = TRUE;
-}
+		if (chr >= 'A' && chr <= 'Z')
+			trie->normalize_map[chr-'A'+'a'] = j;
+		trie->normalize_map[chr] = j++;
+	}
+	i_assert(j <= SEQUENTIAL_COUNT);
 
-static void
-trie_map_node_save_leaf(const uint32_t *src_idx, unsigned int count,
-			uint32_t *children)
-{
-	unsigned int i;
+	for (i = 128; i < 256; i++)
+		trie->normalize_map[i] = j++;
+#else
+	for (i = 0; i < sizeof(valid_chars)-1; i++) {
+		unsigned char chr = valid_chars[i];
 
-#ifndef ALLOW_UNALIGNED_ACCESS
-	if ((POINTER_CAST_TO(src_idx, size_t) & (sizeof(uint32_t)-1)) == 0) {
-#endif
-		for (i = 0; i < count; i++)
-			children[i] = src_idx[i];
-#ifndef ALLOW_UNALIGNED_ACCESS
-	} else {
-		/* unaligned access */
-		const uint8_t *src_idx8 = (const uint8_t *)src_idx;
-
-		for (i = 0; i < count; i++) {
-			memcpy(&children[i], src_idx8 + i * sizeof(uint32_t),
-			       sizeof(children[i]));
-		}
+		if (chr >= 'A' && chr <= 'Z')
+			trie->normalize_map[chr-'A'+'a'] = chr;
+		trie->normalize_map[chr] = chr;
 	}
+	for (i = 128; i < 256; i++)
+		trie->normalize_map[i] = i_toupper(i);
 #endif
 }
 
-static void
-trie_map_node_save_children(unsigned int level, const uint32_t *src_idx,
-			    unsigned int count, struct trie_node **children)
+static void node_free(struct squat_trie *trie, struct squat_node *node)
 {
+	struct squat_node *children;
 	unsigned int i;
 
-	if (level == BLOCK_SIZE) {
-		trie_map_node_save_leaf(src_idx, count, (uint32_t *)children);
-		return;
-	}
-
-#ifndef ALLOW_UNALIGNED_ACCESS
-	if ((POINTER_CAST_TO(src_idx, size_t) & (sizeof(uint32_t)-1)) == 0) {
-#endif
-		for (i = 0; i < count; i++) {
-			children[i] = src_idx[i] == 0 ? NULL :
-				POINTER_CAST(src_idx[i] | 1);
-		}
-#ifndef ALLOW_UNALIGNED_ACCESS
-	} else {
-		/* unaligned access */
-		const uint8_t *src_idx8 = (const uint8_t *)src_idx;
-		uint32_t idx;
-
-		for (i = 0; i < count; i++) {
-			memcpy(&idx, src_idx8 + i * sizeof(uint32_t),
-			       sizeof(idx));
-			children[i] = idx == 0 ? NULL : POINTER_CAST(idx | 1);
-		}
-	}
-#endif
-}
-
-static int trie_map_area(struct squat_trie *trie, uoff_t offset, size_t len)
-{
-	ssize_t ret;
+	if (NODE_IS_DYNAMIC_LEAF(node))
+		i_free(node->children.leaf_string);
+	else if (!node->children_not_mapped && node->child_count > 0) {
+		children = NODE_CHILDREN_NODES(node);
 
-	if (trie->file_cache == NULL)
-		return 0;
-
-	ret = file_cache_read(trie->file_cache, offset, len);
-	if (ret < 0) {
-		squat_trie_set_syscall_error(trie, "file_cache_read()");
-		return -1;
-	}
-	trie->const_mmap_base =
-		file_cache_get_map(trie->file_cache, &trie->mmap_size);
-	trie->hdr = (const void *)trie->const_mmap_base;
-	return 0;
-}
+		trie->node_alloc_size -=
+			NODE_CHILDREN_ALLOC_SIZE(node->child_count);
+		for (i = 0; i < node->child_count; i++)
+			node_free(trie, &children[i]);
 
-static void
-trie_map_fix_fast_node(struct trie_node *node, unsigned int chars8_count)
-{
-	uint8_t *chars = NODE_CHARS8(node);
-	struct trie_node **children = NODE_CHILDREN8(node);
-	int i, j;
-
-	i_assert(node->chars_8bit_count == MAX_8BIT_CHAR_COUNT);
-
-	j = chars8_count - 1;
-	for (i = node->chars_8bit_count - 1; i >= 0; i--) {
-		if (j >= 0 && i == chars[j])
-			children[i] = children[j--];
-		else
-			children[i] = NULL;
-		chars[i] = i;
+		i_free(node->children.data);
 	}
 }
 
-static int
-trie_map_node(struct squat_trie *trie, uint32_t offset, unsigned int level,
-	      struct trie_node **node_r)
-{
-	struct trie_node *node;
-	const uint8_t *p, *end, *chars8_src, *chars16_src;
-	uint32_t num, chars8_count, chars16_count;
-	unsigned int chars8_offset, chars8_size, chars8_memsize;
-	unsigned int chars16_offset, chars16_size, chars16_memsize;
-	unsigned int idx_size, alloced_chars8_count;
-
-	i_assert(trie->fd != -1);
-
-	if (trie_map_area(trie, offset, 2+256) < 0)
-		return -1;
-
-	if (offset >= trie->mmap_size) {
-		squat_trie_set_corrupted(trie, "trie offset too large");
-		return -1;
-	}
-
-	p = trie->const_mmap_base + offset;
-	end = trie->const_mmap_base + trie->mmap_size;
-
-	/* get 8bit char count and check that it's valid */
-	num = squat_trie_unpack_num(&p, end);
-	chars8_count = num >> 1;
-
-	chars8_offset = p - trie->const_mmap_base;
-	chars8_size = chars8_count * (sizeof(uint8_t) + sizeof(uint32_t));
-
-	if (trie_map_area(trie, chars8_offset, chars8_size + 8) < 0)
-		return -1;
-
-	if (chars8_count > MAX_8BIT_CHAR_COUNT ||
-	    chars8_offset + chars8_size > trie->mmap_size) {
-		squat_trie_set_corrupted(trie, "trie offset broken");
-		return -1;
-	}
-
-	idx_size = level == BLOCK_SIZE ?
-		sizeof(uint32_t) : sizeof(struct trie_node *);
-
-	alloced_chars8_count = level <= FAST_8BIT_LEVEL ?
-		MAX_8BIT_CHAR_COUNT : chars8_count;
-	chars8_memsize = ALIGN(alloced_chars8_count * sizeof(uint8_t)) +
-		alloced_chars8_count * idx_size;
-
-	if ((num & 1) == 0) {
-		/* no 16bit chars */
-		chars16_count = 0;
-		chars16_memsize = 0;
-		chars16_offset = 0;
-	} else {
-		/* get the 16bit char count */
-		p = trie->const_mmap_base + chars8_offset + chars8_size;
-		end = trie->const_mmap_base + trie->mmap_size;
-
-		chars16_count = squat_trie_unpack_num(&p, end);
-		if (chars16_count > 65536) {
-			squat_trie_set_corrupted(trie, "trie offset broken");
-			return -1;
-		}
-		chars16_offset = p - trie->const_mmap_base;
-
-		/* map the required area size and make sure it exists */
-		chars16_size = chars16_count *
-			(sizeof(uint16_t) + sizeof(uint32_t));
-		if (trie_map_area(trie, chars16_offset, chars16_size) < 0)
-			return -1;
-
-		if (chars16_offset + chars16_size > trie->mmap_size) {
-			squat_trie_set_corrupted(trie, "trie offset broken");
-			return -1;
-		}
-
-		chars16_memsize = ALIGN(chars16_count * sizeof(uint16_t)) +
-			chars16_count * idx_size;
-	}
-
-	node = i_malloc(sizeof(*node) + chars8_memsize + chars16_memsize);
-	node->chars_8bit_count = alloced_chars8_count;
-	node->chars_16bit_count = chars16_count;
-	node->file_offset = offset;
-
-	{
-		uint8_t *chars8 = NODE_CHARS8(node);
-		uint16_t *chars16 = NODE_CHARS16(node, level);
-		struct trie_node **children8 = NODE_CHILDREN8(node);
-		struct trie_node **children16 = NODE_CHILDREN16(node, level);
-		const uint32_t *src_idx;
-		const void *end_offset;
-
-		chars8_src = trie->const_mmap_base + chars8_offset;
-		chars16_src = trie->const_mmap_base + chars16_offset;
-
-		memcpy(chars8, chars8_src, sizeof(uint8_t) * chars8_count);
-		memcpy(chars16, chars16_src, sizeof(uint16_t) * chars16_count);
-
-		src_idx = CONST_PTR_OFFSET(chars8_src, chars8_count);
-		trie_map_node_save_children(level, src_idx, chars8_count,
-					    children8);
-
-		if (alloced_chars8_count != chars8_count)
-			trie_map_fix_fast_node(node, chars8_count);
-		if (chars16_count == 0)
-			end_offset = &src_idx[chars8_count];
-		else {
-			src_idx = CONST_PTR_OFFSET(chars16_src,
-						   chars16_count *
-						   sizeof(uint16_t));
-			trie_map_node_save_children(level, src_idx,
-						    chars16_count, children16);
-			end_offset = &src_idx[chars16_count];
-		}
-
-		node->orig_size = ((const uint8_t *)end_offset -
-				   trie->const_mmap_base) - offset;
-	}
-
-	*node_r = node;
-	return 0;
-}
-
-static void free_children(unsigned int level, struct trie_node **children,
-			  unsigned int count)
-{
-	unsigned int i;
-	uint32_t child_idx;
-
-	for (i = 0; i < count; i++) {
-		child_idx = POINTER_CAST_TO(children[i], size_t);
-		if ((child_idx & 1) == 0 && children[i] != NULL)
-			free_node(children[i], level);
-	}
-}
-
-static void free_node(struct trie_node *node, unsigned int level)
-{
-	if (level < BLOCK_SIZE) {
-		struct trie_node **children8 = NODE_CHILDREN8(node);
-		struct trie_node **children16 = NODE_CHILDREN16(node, level);
-
-		free_children(level + 1, children8, node->chars_8bit_count);
-		free_children(level + 1, children16, node->chars_16bit_count);
-	}
-	i_free(node);
-}
-
-static void squat_trie_unmap(struct squat_trie *trie)
-{
-	if (trie->file_cache != NULL)
-		file_cache_invalidate(trie->file_cache, 0, (uoff_t)-1);
-
-	if (trie->mmap_base != NULL) {
-		if (munmap(trie->mmap_base, trie->mmap_size) < 0)
-			squat_trie_set_syscall_error(trie, "munmap()");
-		trie->mmap_base = NULL;
-	}
-
-	trie->mmap_size = 0;
-	trie->hdr = NULL;
-	trie->const_mmap_base = NULL;
-
-	if (trie->root != NULL) {
-		free_node(trie->root, 1);
-		trie->root = NULL;
-	}
-}
-
-static void trie_file_close(struct squat_trie *trie)
+static void squat_trie_clear(struct squat_trie *trie)
 {
-	if (trie->file_cache != NULL)
-		file_cache_free(&trie->file_cache);
-	if (trie->file_lock != NULL)
-		file_lock_free(&trie->file_lock);
-
-	squat_trie_unmap(trie);
-	if (trie->fd != -1) {
-		if (close(trie->fd) < 0)
-			squat_trie_set_syscall_error(trie, "close()");
-		trie->fd = -1;
-	}
-
-	trie->hdr = NULL;
-	trie->corrupted = FALSE;
-}
-
-static int
-trie_map_check_header(struct squat_trie *trie,
-		      const struct squat_trie_header *hdr, uoff_t file_size)
-{
-	if (hdr->version != SQUAT_TRIE_VERSION)
-		return -1;
-
-	if (hdr->used_file_size > file_size) {
-		squat_trie_set_corrupted(trie, "used_file_size too large");
-		return -1;
-	}
-	if (hdr->root_offset != 0 &&
-	    (hdr->root_offset > file_size ||
-	     hdr->root_offset < sizeof(*hdr))) {
-		squat_trie_set_corrupted(trie, "invalid root_offset");
-		return -1;
-	}
-	if (hdr->uidvalidity != trie->uidvalidity) {
-		squat_trie_set_corrupted(trie, "uidvalidity changed");
-		return -1;
-	}
-
-	return 0;
-}
-
-static int squat_trie_file_was_modified(struct squat_trie *trie)
-{
-	struct squat_trie_header hdr;
-	int ret;
-
-	ret = pread_full(trie->fd, &hdr.modify_counter,
-			 sizeof(hdr.modify_counter),
-			 offsetof(struct squat_trie_header, modify_counter));
-	if (ret < 0) {
-		squat_trie_set_syscall_error(trie, "pread_full()");
-		return -1;
-	}
-	if (ret == 0) {
-		/* broken file, treat as modified */
-		return 1;
-	}
-	return hdr.modify_counter == trie->file_cache_modify_counter ? 0 : 1;
-}
-
-static int squat_trie_map(struct squat_trie *trie)
-{
-	const struct squat_trie_header *hdr;
-	struct stat st;
-	ssize_t ret;
-
-	if (trie->hdr != NULL) {
-		if (!trie->mmap_disable) {
-			if (trie->hdr->used_file_size <= trie->mmap_size) {
-				/* everything is already mapped */
-				return 1;
-			}
-		} else {
-			ret = squat_trie_file_was_modified(trie);
-			if (ret <= 0)
-				return ret < 0 ? -1 : 1;
-		}
-	}
-
-	if (fstat(trie->fd, &st) < 0) {
-		squat_trie_set_syscall_error(trie, "fstat()");
-		return -1;
-	}
-	trie->dev = st.st_dev;
-	trie->ino = st.st_ino;
-
-	squat_trie_unmap(trie);
-
-	if (!trie->mmap_disable) {
-		trie->mmap_size = st.st_size;
-		trie->mmap_base = mmap(NULL, trie->mmap_size,
-				       PROT_READ | PROT_WRITE,
-				       MAP_SHARED, trie->fd, 0);
-		if (trie->mmap_base == MAP_FAILED) {
-			trie->mmap_size = 0;
-			trie->mmap_base = NULL;
-			squat_trie_set_syscall_error(trie, "mmap()");
-			return -1;
-		}
-		trie->const_mmap_base = trie->mmap_base;
-	} else {
-		ret = file_cache_read(trie->file_cache, 0, sizeof(*trie->hdr));
-		if (ret < 0) {
-			squat_trie_set_syscall_error(trie, "file_cache_read()");
-			return -1;
-		}
-		if ((size_t)ret < sizeof(*trie->hdr)) {
-			squat_trie_set_corrupted(trie, "file too small");
-			return -1;
-		}
-		trie->const_mmap_base =
-			file_cache_get_map(trie->file_cache, &trie->mmap_size);
-	}
-
-	hdr = (const void *)trie->const_mmap_base;
-	if (trie_map_check_header(trie, hdr, st.st_size) < 0)
-		return -1;
-	trie->hdr = hdr;
-	trie->file_cache_modify_counter = trie->hdr->modify_counter;
-
-	if (trie->hdr->root_offset != 0) {
-		if (trie_map_node(trie, trie->hdr->root_offset,
-				  1, &trie->root) < 0)
-			return 0;
-	}
-	return 1;
-}
-
-static void trie_file_open_fd(struct squat_trie *trie, int fd)
-{
-	struct stat st;
-
-	if (fstat(fd, &st) < 0) {
-		/* don't bother adding complexity by trying to handle this
-		   error here. we'll break later anyway in easier error
-		   handling paths. */
-		squat_trie_set_syscall_error(trie, "fstat()");
-		trie->ino = 0;
-	} else {
-		trie->dev = st.st_dev;
-		trie->ino = st.st_ino;
-	}
-	trie->fd = fd;
-
-	if (trie->mmap_disable)
-		trie->file_cache = file_cache_new(trie->fd);
-}
-
-static int trie_file_open(struct squat_trie *trie, bool create)
-{
-	int fd;
-
-	i_assert(trie->fd == -1);
-
-	fd = open(trie->filepath, O_RDWR | (create ? O_CREAT : 0), 0660);
-	if (fd == -1) {
-		if (errno == ENOENT)
-			return 0;
-
-		squat_trie_set_syscall_error(trie, "open()");
-		return -1;
-	}
-	trie_file_open_fd(trie, fd);
-	return 1;
-}
-
-static int trie_file_create_finish(struct squat_trie *trie)
-{
-	struct squat_trie_header hdr;
-	struct stat st;
-
-	if (fstat(trie->fd, &st) < 0) {
-		squat_trie_set_syscall_error(trie, "fstat()");
-		return -1;
-	}
-
-	if (st.st_size <= (off_t)sizeof(hdr)) {
-		memset(&hdr, 0, sizeof(hdr));
-		hdr.version = SQUAT_TRIE_VERSION;
-		hdr.uidvalidity = trie->uidvalidity;
-		hdr.used_file_size = sizeof(hdr);
-
-		if (pwrite_full(trie->fd, &hdr, sizeof(hdr), 0) < 0) {
-			squat_trie_set_syscall_error(trie, "pwrite_full()");
-			return -1;
-		}
-	}
-
-	return 0;
+	node_free(trie, &trie->root);
+	memset(&trie->root, 0, sizeof(trie->root));
+	memset(&trie->hdr, 0, sizeof(trie->hdr));
 }
 
 struct squat_trie *
-squat_trie_open(const char *path, uint32_t uidvalidity,
+squat_trie_init(const char *path, uint32_t uidvalidity,
 		enum file_lock_method lock_method, bool mmap_disable)
 {
 	struct squat_trie *trie;
 
 	trie = i_new(struct squat_trie, 1);
+	trie->path = i_strdup(path);
+	trie->uidlist = squat_uidlist_init(trie);
 	trie->fd = -1;
-	trie->filepath = i_strdup(path);
-	trie->uidvalidity = uidvalidity;
 	trie->lock_method = lock_method;
-	trie->mmap_disable = mmap_disable;
-	trie->buf = buffer_create_dynamic(default_pool, 1024);
-
-	trie->uidlist_filepath = i_strconcat(path, ".uids", NULL);
-	trie->uidlist =
-		squat_uidlist_init(trie, trie->uidlist_filepath,
-				   uidvalidity, mmap_disable);
+	trie->uidvalidity = uidvalidity;
+	squat_trie_normalize_map_build(trie);
 	return trie;
 }
 
-void squat_trie_close(struct squat_trie *trie)
+void squat_trie_deinit(struct squat_trie **_trie)
 {
-	squat_trie_unmap(trie);
-	buffer_free(&trie->buf);
+	struct squat_trie *trie = *_trie;
+
+	*_trie = NULL;
+	squat_trie_clear(trie);
 	squat_uidlist_deinit(trie->uidlist);
-	i_free(trie->uidlist_filepath);
-	i_free(trie->filepath);
+	i_free(trie->path);
 	i_free(trie);
 }
 
-int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *uid_r)
+static void squat_trie_close(struct squat_trie *trie)
 {
-	int ret;
+	if (trie->mmap_size != 0) {
+		if (munmap(trie->mmap_base, trie->mmap_size) < 0)
+			i_error("munmap(%s) failed: %m", trie->path);
+	}
+	if (trie->fd != -1) {
+		if (close(trie->fd) < 0)
+			i_error("close(%s) failed: %m", trie->path);
+		trie->fd = -1;
+	}
+	trie->locked_file_size = 0;
+	squat_uidlist_close(trie->uidlist);
+}
 
+static void squat_trie_header_init(struct squat_trie *trie)
+{
+	memset(&trie->hdr, 0, sizeof(trie->hdr));
+	trie->hdr.version = SQUAT_TRIE_VERSION;
+	trie->hdr.indexid = ioloop_time;
+	trie->hdr.uidvalidity = trie->uidvalidity;
+}
+
+static int squat_trie_open_fd(struct squat_trie *trie)
+{
+	trie->fd = open(trie->path, O_RDWR);
 	if (trie->fd == -1) {
-		if ((ret = trie_file_open(trie, FALSE)) < 0)
-			return ret;
-		if (ret == 0) {
-			*uid_r = 0;
+		if (errno == ENOENT) {
+			squat_trie_header_init(trie);
 			return 0;
 		}
+		i_error("open(%s) failed: %m", trie->path);
+		return -1;
 	}
+	return 0;
+}
 
-	if (squat_trie_lock(trie, F_RDLCK) <= 0)
+static int squat_trie_open(struct squat_trie *trie)
+{
+	squat_trie_close(trie);
+	squat_trie_clear(trie);
+
+	if (squat_trie_open_fd(trie) < 0)
+		return -1;
+	if (squat_trie_map(trie) < 0)
 		return -1;
 
-	ret = squat_uidlist_get_last_uid(trie->uidlist, uid_r);
-	squat_trie_unlock(trie);
-	return ret;
+	return squat_uidlist_open(trie->uidlist);
 }
 
 static int squat_trie_is_file_stale(struct squat_trie *trie)
 {
-	struct stat st;
+	struct stat st, st2;
 
-	if (stat(trie->filepath, &st) < 0) {
+	if (stat(trie->path, &st) < 0) {
 		if (errno == ENOENT)
 			return 1;
 
-		squat_trie_set_syscall_error(trie, "stat()");
+		i_error("stat(%s) failed: %m", trie->path);
+		return -1;
+	}
+	if (fstat(trie->fd, &st2) < 0) {
+		i_error("fstat(%s) failed: %m", trie->path);
 		return -1;
 	}
+	trie->locked_file_size = st2.st_size;
 
-	return st.st_ino == trie->ino &&
-		CMP_DEV_T(st.st_dev, trie->dev) ? 0 : 1;
+	return st.st_ino == st2.st_ino &&
+		CMP_DEV_T(st.st_dev, st2.st_dev) ? 0 : 1;
 }
 
-static int
-squat_trie_file_lock(struct squat_trie *trie, int fd, const char *path,
-		     int lock_type, struct file_lock **lock_r)
+void squat_trie_refresh(struct squat_trie *trie)
+{
+	if (squat_trie_is_file_stale(trie) > 0)
+		(void)squat_trie_open(trie);
+}
+
+static int squat_trie_lock(struct squat_trie *trie, int lock_type,
+			   struct file_lock **file_lock_r)
 {
 	int ret;
 
-	ret = file_wait_lock(fd, path, lock_type, trie->lock_method,
-			     SQUAT_TRIE_LOCK_TIMEOUT, lock_r);
-	if (ret == 0)
-		squat_trie_set_syscall_error(trie, "file_wait_lock()");
-	return ret;
-}
-
-int squat_trie_lock(struct squat_trie *trie, int lock_type)
-{
-	bool created = FALSE;
-	int ret;
-
-	i_assert(lock_type == F_RDLCK || lock_type == F_WRLCK);
-
-	if (trie->lock_count > 0) {
-		/* read lock -> write lock would deadlock */
-		i_assert(trie->lock_type == lock_type || lock_type == F_RDLCK);
-
-		trie->lock_count++;
-		return 1;
-	}
-
-	if (trie->fd == -1 || trie->corrupted) {
-		trie_file_close(trie);
-		if (lock_type == F_WRLCK) {
-			if ((ret = trie_file_open(trie, FALSE)) < 0)
-				return -1;
-			if (ret == 0) {
-				if (trie_file_open(trie, TRUE) < 0)
-					return -1;
-				created = TRUE;
-			}
-		} else {
-			if (trie_file_open(trie, FALSE) <= 0)
-				return -1;
+	while (trie->fd != -1) {
+		ret = file_wait_lock(trie->fd, trie->path, lock_type,
+				     trie->lock_method, SQUAT_TRIE_LOCK_TIMEOUT,
+				     file_lock_r);
+		if (ret == 0) {
+			i_error("file_wait_lock(%s) failed: %m", trie->path);
+			return 0;
 		}
-	}
-
-	for (;;) {
-		i_assert(trie->file_lock == NULL);
-		ret = squat_trie_file_lock(trie, trie->fd, trie->filepath,
-					   lock_type, &trie->file_lock);
-		if (ret <= 0)
-			return ret;
+		if (ret < 0)
+			return -1;
 
 		/* if the trie has been compressed, we need to reopen the
 		   file and try to lock again */
 		ret = squat_trie_is_file_stale(trie);
 		if (ret == 0)
-			break;
+			return 1;
 
-		file_unlock(&trie->file_lock);
+		file_unlock(file_lock_r);
 		if (ret < 0)
 			return -1;
 
-		trie_file_close(trie);
-		if (trie_file_open(trie, FALSE) <= 0)
-			return -1;
-	}
-
-	if (created) {
-		/* we possibly created this file. now that we've locked the
-		   file, we can safely check if someone else already wrote the
-		   header or if we should do it now */
-		if (trie_file_create_finish(trie) < 0) {
-			file_unlock(&trie->file_lock);
-			return -1;
-		}
-	}
-
-	if (squat_trie_map(trie) <= 0) {
-		file_unlock(&trie->file_lock);
-		return -1;
-	}
-	if (squat_uidlist_refresh(trie->uidlist) < 0) {
-		file_unlock(&trie->file_lock);
-		return -1;
-	}
-
-	trie->lock_count++;
-	trie->lock_type = lock_type;
-	return 1;
-}
-
-void squat_trie_unlock(struct squat_trie *trie)
-{
-	i_assert(trie->lock_count > 0);
-
-	if (--trie->lock_count > 0)
-		return;
-
-	file_unlock(&trie->file_lock);
-}
-
-static struct trie_node *
-node_alloc(uint16_t chr, unsigned int level)
-{
-	struct trie_node *node;
-	unsigned int i, idx_size, idx_offset = sizeof(*node);
-
-	idx_size = level < BLOCK_SIZE ?
-		sizeof(struct trie_node *) : sizeof(uint32_t);
-
-	if (level <= FAST_8BIT_LEVEL) {
-		uint8_t *chars;
-		unsigned int chars16_count = chr >= MAX_8BIT_CHAR_COUNT ? 1 : 0;
-
-		node = i_malloc(sizeof(*node) +
-				ALIGN(MAX_8BIT_CHAR_COUNT) +
-				ALIGN(sizeof(uint16_t) * chars16_count) +
-				(MAX_8BIT_CHAR_COUNT + chars16_count) *
-				idx_size);
-		node->chars_8bit_count = MAX_8BIT_CHAR_COUNT;
-
-		chars = NODE_CHARS8(node);
-		for (i = 0; i < MAX_8BIT_CHAR_COUNT; i++)
-			chars[i] = i;
-
-		if (chars16_count > 0) {
-			uint16_t *chars16 = NODE_CHARS16(node, 0);
-
-			node->chars_16bit_count = chars16_count;
-			chars16[0] = chr;
-		}
-	} else if (chr < MAX_8BIT_CHAR_COUNT) {
-		uint8_t *chrp;
-
-		idx_offset += ALIGN(sizeof(*chrp));
-		node = i_malloc(idx_offset + idx_size);
-		node->chars_8bit_count = 1;
-
-		chrp = PTR_OFFSET(node, sizeof(*node));
-		*chrp = chr;
-	} else {
-		uint16_t *chrp;
-
-		idx_offset += ALIGN(sizeof(*chrp));
-		node = i_malloc(idx_offset + idx_size);
-		node->chars_16bit_count = 1;
-
-		chrp = PTR_OFFSET(node, sizeof(*node));
-		*chrp = chr;
-	}
-
-	node->modified = TRUE;
-	node->resized = TRUE;
-	return node;
-}
-
-static struct trie_node *
-node_realloc(struct trie_node *node, uint32_t char_idx, uint16_t chr,
-	     unsigned int level)
-{
-	struct trie_node *new_node;
-	unsigned int old_size_8bit, old_size_16bit, old_idx_offset;
-	unsigned int idx_size, old_size, new_size, new_idx_offset;
-	unsigned int hole1_pos, hole2_pos, skip;
-
-	idx_size = level < BLOCK_SIZE ?
-		sizeof(struct trie_node *) : sizeof(uint32_t);
-
-	old_size_8bit = ALIGN(node->chars_8bit_count) +
-		node->chars_8bit_count * idx_size;
-	old_size_16bit = ALIGN(sizeof(uint16_t) * node->chars_16bit_count) +
-		node->chars_16bit_count * idx_size;
-	old_size = sizeof(*node) + old_size_8bit + old_size_16bit;
-
-	if (chr < MAX_8BIT_CHAR_COUNT) {
-		new_idx_offset = sizeof(*node) +
-			ALIGN(node->chars_8bit_count + 1);
-		new_size = new_idx_offset + old_size_16bit +
-			(node->chars_8bit_count + 1) * idx_size;
-	} else {
-		new_idx_offset = sizeof(*node) + old_size_8bit +
-			ALIGN((node->chars_16bit_count + 1) * sizeof(uint16_t));
-		new_size = new_idx_offset +
-			(node->chars_16bit_count + 1) * idx_size;
-	}
-
-	new_node = t_buffer_get(new_size);
-	if (chr < MAX_8BIT_CHAR_COUNT) {
-		hole1_pos = sizeof(*node) + char_idx;
-		old_idx_offset = sizeof(*node) + ALIGN(node->chars_8bit_count);
-	} else {
-		hole1_pos = sizeof(*node) + old_size_8bit +
-			char_idx * sizeof(uint16_t);
-		old_idx_offset = sizeof(*node) + old_size_8bit +
-			ALIGN(node->chars_16bit_count * sizeof(uint16_t));
-	}
-	hole2_pos = old_idx_offset + idx_size * char_idx;
-
-	/* 0..character position */
-	memcpy(new_node, node, hole1_pos);
-	if (chr < MAX_8BIT_CHAR_COUNT) {
-		uint8_t *chrp = PTR_OFFSET(new_node, hole1_pos);
-		*chrp = chr;
-		new_node->chars_8bit_count++;
-
-		/* rest of the characters */
-		memcpy(PTR_OFFSET(new_node, hole1_pos + sizeof(uint8_t)),
-		       PTR_OFFSET(node, hole1_pos), old_idx_offset - hole1_pos);
-	} else {
-		uint16_t *chrp = PTR_OFFSET(new_node, hole1_pos);
-		*chrp = chr;
-		new_node->chars_16bit_count++;
-
-		/* rest of the characters */
-		memcpy(PTR_OFFSET(new_node, hole1_pos + sizeof(uint16_t)),
-		       PTR_OFFSET(node, hole1_pos), old_idx_offset - hole1_pos);
-	}
-
-	/* indexes from 0 to character position */
-	memcpy(PTR_OFFSET(new_node, new_idx_offset),
-	       PTR_OFFSET(node, old_idx_offset),
-	       hole2_pos - old_idx_offset);
-
-	/* zero the inserted character index */
-	skip = char_idx * idx_size;
-	memset(PTR_OFFSET(new_node, new_idx_offset + skip), 0, idx_size);
-
-	/* rest of the indexes */
-	skip += idx_size;
-	memcpy(PTR_OFFSET(new_node, new_idx_offset + skip),
-	       PTR_OFFSET(node, hole2_pos),
-	       old_size - hole2_pos);
-
-	new_node->resized = TRUE;
-
-	node = i_realloc(node, 0, new_size);
-	memcpy(node, new_node, new_size);
-	return node;
-}
-
-static int
-trie_insert_node(struct squat_trie_build_context *ctx,
-		 struct trie_node **parent,
-		 const uint16_t *data, uint32_t uid, unsigned int level)
-{
-	struct squat_trie *trie = ctx->trie;
-	struct trie_node *node = *parent;
-	struct trie_node **children;
-	uint32_t char_idx;
-	bool match, modified = FALSE;
-	int ret;
-
-	if (*data < MAX_8BIT_CHAR_COUNT) {
-		unsigned int count;
-
-		if (node == NULL) {
-			ctx->node_count++;
-			node = *parent = node_alloc(*data, level);
-			char_idx = level <= FAST_8BIT_LEVEL ? *data : 0;
-			modified = TRUE;
-		} else if (level <= FAST_8BIT_LEVEL) {
-			char_idx = *data;
-		} else {
-			uint8_t *chars = NODE_CHARS8(node);
-
-			count = node->chars_8bit_count;
-			match = bsearch_insert_pos(data, chars, count,
-						   sizeof(chars[0]),
-						   chr_8bit_cmp,
-						   &char_idx);
-			if (!match) {
-				node = node_realloc(node, char_idx,
-						    *data, level);
-				*parent = node;
-				modified = TRUE;
-			}
-		}
-		children = NODE_CHILDREN8(node);
-	} else {
-		unsigned int offset = sizeof(*node);
-		unsigned int count;
-
-		if (node == NULL) {
-			ctx->node_count++;
-			node = *parent = node_alloc(*data, level);
-			char_idx = 0;
-			modified = TRUE;
-		} else {
-			unsigned int idx_size;
-			uint16_t *chars;
-
-			idx_size = level < BLOCK_SIZE ?
-				sizeof(struct trie_node *) : sizeof(uint32_t);
-			offset += ALIGN(node->chars_8bit_count) +
-				idx_size * node->chars_8bit_count;
-			chars = PTR_OFFSET(node, offset);
-
-			count = node->chars_16bit_count;
-			match = bsearch_insert_pos(data, chars, count,
-						   sizeof(chars[0]),
-						   chr_16bit_cmp,
-						   &char_idx);
-			if (!match) {
-				node = node_realloc(node, char_idx,
-						    *data, level);
-				*parent = node;
-				modified = TRUE;
-			}
-		}
-
-		children = NODE_CHILDREN16(node, level);
-	}
-
-	if (level < BLOCK_SIZE) {
-		size_t child_idx = POINTER_CAST_TO(children[char_idx], size_t);
-
-		if ((child_idx & 1) != 0) {
-			if (trie_map_node(trie, child_idx & ~1, level + 1,
-					  &children[char_idx]) < 0)
-				return -1;
-		}
-
-		if (children[char_idx] == NULL)
-			node->resized = TRUE;
-
-		ret = trie_insert_node(ctx, &children[char_idx],
-				       data + 1, uid, level + 1);
-		if (ret < 0)
-			return -1;
-		if (ret > 0)
-			node->modified = TRUE;
-	} else {
-		uint32_t *uid_lists = (uint32_t *)children;
-
-		if (uid_lists[char_idx] == 0)
-			node->resized = TRUE;
-
-		if (squat_uidlist_add(trie->uidlist, &uid_lists[char_idx],
-				      uid) < 0)
-			return -1;
-
-		node->modified = TRUE;
-	}
-	return modified ? 1 : 0;
-}
-
-static uint32_t
-trie_lookup_node(struct squat_trie *trie, struct trie_node *node,
-		 const uint16_t *data, unsigned int level)
-{
-	struct trie_node **children;
-	uint32_t char_idx;
-
-	if (node == NULL)
-		return 0;
-
-	if (*data < MAX_8BIT_CHAR_COUNT) {
-		if (level <= FAST_8BIT_LEVEL)
-			char_idx = *data;
-		else {
-			const uint8_t *chars, *pos;
-			chars = NODE_CHARS8(node);
-			pos = bsearch(data, chars, node->chars_8bit_count,
-				      sizeof(chars[0]), chr_8bit_cmp);
-			if (pos == NULL || *pos != *data)
-				return 0;
-
-			char_idx = pos - chars;
-		}
-		children = NODE_CHILDREN8(node);
-	} else {
-		const uint16_t *chars, *pos;
-
-		chars = NODE_CHARS16(node, level);
-		pos = bsearch(data, chars, node->chars_16bit_count,
-			      sizeof(chars[0]), chr_16bit_cmp);
-		if (pos == NULL || *pos != *data)
-			return 0;
-
-		char_idx = pos - chars;
-		children = NODE_CHILDREN16(node, level);
-	}
-
-	if (level < BLOCK_SIZE) {
-		size_t child_idx = POINTER_CAST_TO(children[char_idx], size_t);
-
-		if ((child_idx & 1) != 0) {
-			/* not mapped to memory yet. do it. */
-			if (trie_map_node(trie, child_idx & ~1, level + 1,
-					  &children[char_idx]) < 0)
-				return -1;
-		}
-
-		return trie_lookup_node(trie, children[char_idx],
-					data + 1, level + 1);
-	} else {
-		const uint32_t *uid_lists = (const uint32_t *)children;
-
-		return uid_lists[char_idx];
-	}
-}
-
-static bool block_want_add(const uint16_t *data)
-{
-	unsigned int i;
-
-	/* skip all blocks that contain spaces or control characters.
-	   no-one searches them anyway */
-	for (i = 0; i < BLOCK_SIZE; i++) {
-		if (data[i] == 0)
-			return FALSE;
-	}
-	return TRUE;
-}
-
-struct squat_trie_build_context *
-squat_trie_build_init(struct squat_trie *trie, uint32_t *last_uid_r)
-{
-	struct squat_trie_build_context *ctx;
-
-	ctx = i_new(struct squat_trie_build_context, 1);
-	ctx->trie = trie;
-
-	if (squat_trie_lock(trie, F_WRLCK) <= 0)
-		ctx->failed = TRUE;
-	else {
-		ctx->locked = TRUE;
-		ctx->node_count = trie->hdr->node_count;
-
-		if (squat_uidlist_get_last_uid(trie->uidlist, last_uid_r) < 0)
-			ctx->failed = TRUE;
-	}
-
-	if (ctx->failed)
-		*last_uid_r = 0;
-	return ctx;
-}
-
-int squat_trie_build_deinit(struct squat_trie_build_context *ctx)
-{
-	int ret = ctx->failed ? -1 : 0;
-
-	if (ret == 0)
-		ret = squat_trie_build_flush(ctx, TRUE);
-
-	if (ctx->locked)
-		squat_trie_unlock(ctx->trie);
-
-	i_free(ctx);
-	return ret;
-}
-
-int squat_trie_build_more(struct squat_trie_build_context *ctx, uint32_t uid,
-			  const unsigned char *data, size_t size)
-{
-	const uint16_t *str;
-	uint16_t buf[(BLOCK_SIZE-1)*2];
-	unsigned int i, tmp_size, str_len;
-
-	if (ctx->failed)
-		return -1;
-
-	t_push();
-	str = data_normalize(data, size, ctx->trie->buf);
-	str_len = ctx->trie->buf->used / sizeof(*str);
-
-	if (uid == ctx->prev_uid) {
-		/* @UNSAFE: continue from last block */
-		memcpy(buf, ctx->prev_added,
-		       sizeof(buf[0]) * ctx->prev_added_size);
-		tmp_size = I_MIN(str_len, BLOCK_SIZE-1);
-		memcpy(buf + ctx->prev_added_size, str,
-		       sizeof(buf[0]) * tmp_size);
-
-		tmp_size += ctx->prev_added_size;
-		for (i = 0; i + BLOCK_SIZE <= tmp_size; i++) {
-			if (block_want_add(buf+i)) {
-				if (trie_insert_node(ctx,
-						     &ctx->trie->root,
-						     buf + i, uid, 1) < 0) {
-					t_pop();
-					return -1;
-				}
-			}
-		}
-
-		if (str_len < BLOCK_SIZE) {
-			ctx->prev_added_size = I_MIN(tmp_size, BLOCK_SIZE-1);
-			memcpy(ctx->prev_added, buf + i,
-			       sizeof(buf[0]) * ctx->prev_added_size);
-			t_pop();
-			return 0;
-		}
-	} else if (squat_uidlist_want_flush(ctx->trie->uidlist)) {
-		if (squat_trie_build_flush(ctx, FALSE) < 0) {
-			ctx->failed = TRUE;
-			t_pop();
-			return -1;
-		}
-		str = data_normalize(data, size, ctx->trie->buf);
-		str_len = ctx->trie->buf->used / sizeof(*str);
-	}
-
-	ctx->prev_uid = uid;
-	for (i = 0; i + BLOCK_SIZE <= str_len; i++) {
-		if (block_want_add(str+i)) {
-			if (trie_insert_node(ctx, &ctx->trie->root,
-					     str + i, uid, 1) < 0) {
-				t_pop();
-				return -1;
-			}
-		}
-	}
-	ctx->prev_added_size = I_MIN(str_len - i, BLOCK_SIZE-1);
-	memcpy(ctx->prev_added, str + i,
-	       sizeof(ctx->prev_added[0]) * ctx->prev_added_size);
-
-	t_pop();
-	return 0;
-}
-
-static void node_pack_children(buffer_t *buf, struct trie_node **children,
-			       unsigned int count)
-{
-	unsigned int i;
-	size_t child_idx;
-	uint32_t idx;
-
-	for (i = 0; i < count; i++) {
-		if (children[i] == NULL)
-			continue;
-
-		child_idx = POINTER_CAST_TO(children[i], size_t);
-		if ((child_idx & 1) != 0)
-			idx = child_idx & ~1;
-		else
-			idx = children[i]->file_offset;
-		buffer_append(buf, &idx, sizeof(idx));
-	}
-}
-
-static void node_pack(buffer_t *buf, struct trie_node *node)
-{
-	uint8_t *chars8 = NODE_CHARS8(node);
-	uint16_t *chars16 = NODE_CHARS16(node, 0);
-	struct trie_node **children8 = NODE_CHILDREN8(node);
-	struct trie_node **children16 = NODE_CHILDREN16(node, 0);
-
-	buffer_set_used_size(buf, 0);
-	squat_trie_pack_num(buf, (node->chars_8bit_count << 1) |
-			    (node->chars_16bit_count > 0 ? 1 : 0));
-	buffer_append(buf, chars8, node->chars_8bit_count);
-	node_pack_children(buf, children8, node->chars_8bit_count);
-
-	if (node->chars_16bit_count > 0) {
-		squat_trie_pack_num(buf, node->chars_16bit_count);
-		buffer_append(buf, chars16,
-			      sizeof(*chars16) * node->chars_16bit_count);
-		node_pack_children(buf, children16, node->chars_16bit_count);
-	}
-}
-
-static int node_leaf_finish(struct squat_trie *trie, struct trie_node *node)
-{
-	uint32_t *idx8 = (uint32_t *)NODE_CHILDREN8(node);
-	uint32_t *idx16 = (uint32_t *)NODE_CHILDREN16(node, BLOCK_SIZE);
-	unsigned int i;
-
-	for (i = 0; i < node->chars_8bit_count; i++) {
-		if (squat_uidlist_finish_list(trie->uidlist, &idx8[i]) < 0)
-			return -1;
-	}
-	for (i = 0; i < node->chars_16bit_count; i++) {
-		if (squat_uidlist_finish_list(trie->uidlist, &idx16[i]) < 0)
+		squat_trie_close(trie);
+		if (squat_trie_open_fd(trie) < 0)
 			return -1;
 	}
 	return 0;
 }
 
-static void node_pack_leaf(buffer_t *buf, struct trie_node *node)
+static void
+node_make_squential(struct squat_trie *trie, struct squat_node *node, int level)
 {
-	uint8_t *chars8 = NODE_CHARS8(node);
-	uint16_t *chars16 = NODE_CHARS16(node, BLOCK_SIZE);
-	uint32_t *idx8 = (uint32_t *)NODE_CHILDREN8(node);
-	uint32_t *idx16 = (uint32_t *)NODE_CHILDREN16(node, BLOCK_SIZE);
+	const unsigned int alloc_size =
+		NODE_CHILDREN_ALLOC_SIZE(SEQUENTIAL_COUNT);
+	struct squat_node *children;
+	unsigned char *chars;
+	unsigned int i;
+
+	i_assert(node->child_count == 0);
+
+	trie->node_alloc_size += alloc_size;
 
-	buffer_set_used_size(buf, 0);
-	squat_trie_pack_num(buf, (node->chars_8bit_count << 1) |
-			    (node->chars_16bit_count > 0 ? 1 : 0));
-	buffer_append(buf, chars8, node->chars_8bit_count);
-	buffer_append(buf, idx8, sizeof(*idx8) * node->chars_8bit_count);
+	node->want_sequential = FALSE;
+	node->have_sequential = TRUE;
+
+	node->child_count = SEQUENTIAL_COUNT;
+	node->children.data = i_malloc(alloc_size);
 
-	if (node->chars_16bit_count > 0) {
-		squat_trie_pack_num(buf, node->chars_16bit_count);
-		buffer_append(buf, chars16,
-			      sizeof(*chars16) * node->chars_16bit_count);
-		buffer_append(buf, idx16,
-			      sizeof(*idx16) * node->chars_16bit_count);
+	chars = NODE_CHILDREN_CHARS(node);
+	for (i = 0; i < SEQUENTIAL_COUNT; i++)
+		chars[i] = i;
+
+	if (level < MAX_FAST_LEVEL) {
+		children = NODE_CHILDREN_NODES(node);
+		for (i = 0; i < SEQUENTIAL_COUNT; i++)
+			children[i].want_sequential = TRUE;
 	}
 }
 
+static unsigned int
+node_add_child(struct squat_trie *trie, struct squat_node *node,
+	       unsigned char chr, int level)
+{
+	unsigned int old_child_count = node->child_count;
+	struct squat_node *children, *old_children;
+	unsigned char *chars;
+	size_t old_size, new_size;
+
+	i_assert(node->leaf_string_length == 0);
+
+	if (node->want_sequential) {
+		node_make_squential(trie, node, level);
+
+		if (chr < SEQUENTIAL_COUNT)
+			return chr;
+		old_child_count = SEQUENTIAL_COUNT;
+	}
+
+	node->child_count++;
+	new_size = NODE_CHILDREN_ALLOC_SIZE(node->child_count);
+
+	if (old_child_count == 0) {
+		/* first child */
+		node->children.data = i_malloc(new_size);
+		trie->node_alloc_size += new_size;
+		children = NODE_CHILDREN_NODES(node);
+	} else {
+		old_size = NODE_CHILDREN_ALLOC_SIZE(old_child_count);
+		if (old_size != new_size) {
+			trie->node_alloc_size += new_size - old_size;
+			node->children.data = i_realloc(node->children.data,
+							old_size, new_size);
+		}
+
+		children = NODE_CHILDREN_NODES(node);
+		old_children = (void *)(NODE_CHILDREN_CHARS(node) +
+					MEM_ALIGN(old_child_count));
+		if (children != old_children) {
+			memmove(children, old_children,
+				old_child_count * sizeof(struct squat_node));
+		}
+	}
+
+	chars = NODE_CHILDREN_CHARS(node);
+	chars[node->child_count - 1] = chr;
+	return node->child_count - 1;
+}
+
 static int
-trie_write_node_children(struct squat_trie_build_context *ctx,
-			 unsigned int level, struct trie_node **children,
-			 unsigned int count)
+node_read_children(struct squat_trie *trie, struct squat_node *node, int level)
 {
+	const uint8_t *data, *end;
+	const unsigned char *child_chars;
+	struct squat_node *child, *children = NULL;
+	uoff_t node_offset;
+	unsigned int i, child_idx, child_count;
+	uoff_t base_offset;
+	uint32_t num;
+
+	i_assert(node->children_not_mapped);
+	i_assert(!node->have_sequential);
+	i_assert(trie->unmapped_child_count > 0);
+
+	trie->unmapped_child_count--;
+	node_offset = node->children.offset;
+	node->children_not_mapped = FALSE;
+	node->children.data = NULL;
+
+	if (unlikely(node_offset >= trie->mmap_size)) {
+		squat_trie_set_corrupted(trie);
+		return -1;
+	}
+
+	data = CONST_PTR_OFFSET(trie->mmap_base, node_offset);
+	end = CONST_PTR_OFFSET(trie->mmap_base, trie->mmap_size);
+	child_count = *data++;
+	if (unlikely(node_offset + child_count >= trie->mmap_size)) {
+		squat_trie_set_corrupted(trie);
+		return -1;
+	}
+
+	if (child_count == 0)
+		return 0;
+
+	child_chars = data;
+	data += child_count;
+
+	/* get child offsets */
+	base_offset = node_offset;
+	for (i = 0; i < child_count; i++) {
+		/* we always start with !have_sequential, so at i=0 this
+		   check always goes to add the first child */
+		if (node->have_sequential && child_chars[i] < SEQUENTIAL_COUNT)
+			child_idx = child_chars[i];
+		else {
+			child_idx = node_add_child(trie, node, child_chars[i],
+						   level);
+			children = NODE_CHILDREN_NODES(node);
+		}
+		child = &children[child_idx];
+
+		/* 1) child offset */
+		num = squat_unpack_num(&data, end);
+		if (num == 0) {
+			/* no children */
+		} else {
+			if ((num & 1) != 0) {
+				base_offset += num >> 1;
+			} else {
+				base_offset -= num >> 1;
+			}
+			if (base_offset >= trie->mmap_size) {
+				squat_trie_set_corrupted(trie);
+				return -1;
+			}
+			trie->unmapped_child_count++;
+			child->children_not_mapped = TRUE;
+			child->children.offset = base_offset;
+		}
+
+		/* 2) uidlist */
+		child->uid_list_idx = squat_unpack_num(&data, end);
+		if (child->uid_list_idx == 0) {
+			/* we don't write nodes with empty uidlists */
+			squat_trie_set_corrupted(trie);
+			return -1;
+		}
+		if (!UIDLIST_IS_SINGLETON(child->uid_list_idx)) {
+			/* 3) next uid */
+			child->next_uid = squat_unpack_num(&data, end) + 1;
+		} else {
+			uint32_t idx = child->uid_list_idx;
+
+			child->next_uid = 1 +
+				squat_uidlist_singleton_last_uid(idx);
+		}
+
+		/* 4) unused uids + leaf string flag */
+		num = squat_unpack_num(&data, end);
+		child->unused_uids = num >> 1;
+		if ((num & 1) != 0) {
+			/* leaf string */
+			unsigned int len;
+			unsigned char *dest;
+
+			/* 5) leaf string length */
+			len = child->leaf_string_length =
+				squat_unpack_num(&data, end) + 1;
+			if (!NODE_IS_DYNAMIC_LEAF(child))
+				dest = child->children.static_leaf_string;
+			else {
+				dest = child->children.leaf_string =
+					i_malloc(len);
+			}
+			if (end - data < len) {
+				squat_trie_set_corrupted(trie);
+				return -1;
+			}
+			memcpy(dest, data, len);
+			data += len;
+		}
+	}
+	return 0;
+}
+
+static void
+node_write_children(struct squat_trie_build_context *ctx,
+		    struct squat_node *node, const uoff_t *node_offsets)
+{
+	struct squat_node *children;
+	const unsigned char *chars;
+	uint8_t child_count, buf[SQUAT_PACK_MAX_SIZE * 5], *bufp;
+	uoff_t base_offset;
 	unsigned int i;
-	size_t child_idx;
+
+	chars = NODE_CHILDREN_CHARS(node);
+	children = NODE_CHILDREN_NODES(node);
+
+	base_offset = ctx->output->offset;
+	child_count = node->child_count;
+	o_stream_send(ctx->output, &child_count, 1);
+	o_stream_send(ctx->output, chars, child_count);
+
+	for (i = 0; i < child_count; i++) {
+		bufp = buf;
+		/* 1) child offset */
+		if (node_offsets[i] == 0)
+			*bufp++ = 0;
+		else if (node_offsets[i] >= base_offset) {
+			squat_pack_num(&bufp,
+				((node_offsets[i] - base_offset) << 1) | 1);
+			base_offset = node_offsets[i];
+		} else {
+			squat_pack_num(&bufp,
+				       (base_offset - node_offsets[i]) << 1);
+			base_offset = node_offsets[i];
+		}
+
+		/* 2) uidlist */
+		squat_pack_num(&bufp, children[i].uid_list_idx);
+		if (!UIDLIST_IS_SINGLETON(children[i].uid_list_idx)) {
+			/* 3) next uid */
+			squat_pack_num(&bufp, children[i].next_uid - 1);
+		}
+
+		if (children[i].leaf_string_length == 0) {
+			/* 4a) unused uids */
+			squat_pack_num(&bufp, children[i].unused_uids << 1);
+			o_stream_send(ctx->output, buf, bufp - buf);
+		} else {
+			i_assert(node_offsets[i] == 0);
+			/* 4b) unused uids + flag */
+			squat_pack_num(&bufp, (children[i].unused_uids << 1) | 1);
+			/* 5) leaf string length */
+			squat_pack_num(&bufp, children[i].leaf_string_length - 1);
+			o_stream_send(ctx->output, buf, bufp - buf);
+			o_stream_send(ctx->output,
+				      NODE_LEAF_STRING(&children[i]),
+				      children[i].leaf_string_length);
+		}
+	}
+}
+
+static inline void
+node_add_uid(struct squat_trie *trie, uint32_t uid, struct squat_node *node)
+{
+	if (uid < node->next_uid) {
+		/* duplicate */
+		return;
+	}
+	node->unused_uids += uid - node->next_uid;
+	node->next_uid = uid + 1;
+
+	node->uid_list_idx =
+		squat_uidlist_build_add_uid(trie->uidlist,
+					    node->uid_list_idx, uid);
+}
+
+static void node_split_string(struct squat_trie *trie, struct squat_node *node)
+{
+	struct squat_node *child;
+	unsigned char *str;
+	unsigned int uid, idx, str_len = node->leaf_string_length;
+
+	i_assert(str_len > 0);
+
+	t_push();
+	/* make a copy of the leaf string and convert to normal node by
+	   removing it. */
+	str = t_malloc(str_len);
+	if (!NODE_IS_DYNAMIC_LEAF(node))
+		memcpy(str, node->children.static_leaf_string, str_len);
+	else {
+		memcpy(str, node->children.leaf_string, str_len);
+		i_free(node->children.leaf_string);
+	}
+	node->leaf_string_length = 0;
+
+	/* create a new child node for the rest of the string */
+	idx = node_add_child(trie, node, str[0], MAX_FAST_LEVEL);
+	child = NODE_CHILDREN_NODES(node) + idx;
+
+	/* update uidlist to contain all of parent's UIDs */
+	child->next_uid =  node->next_uid - node->unused_uids;
+	for (uid = 0; uid < child->next_uid; uid++) {
+		child->uid_list_idx =
+			squat_uidlist_build_add_uid(trie->uidlist,
+						    child->uid_list_idx, uid);
+	}
+
+	i_assert(!child->have_sequential && child->children.data == NULL);
+	if (str_len > 1) {
+		/* make the child a leaf string */
+		str_len--;
+		child->leaf_string_length = str_len;
+		if (!NODE_IS_DYNAMIC_LEAF(child)) {
+			memcpy(child->children.static_leaf_string,
+			       str + 1, str_len);
+		} else {
+			child->children.leaf_string = i_malloc(str_len);
+			memcpy(child->children.leaf_string, str + 1, str_len);
+		}
+	}
+	t_pop();
+}
 
-	for (i = 0; i < count; i++) {
-		child_idx = POINTER_CAST_TO(children[i], size_t);
-		if ((child_idx & 1) == 0 && children[i] != NULL) {
-			if (trie_write_node(ctx, level, children[i]) < 0)
+static bool
+node_leaf_string_add_or_split(struct squat_trie *trie, struct squat_node *node,
+			      const unsigned char *data, unsigned int data_len)
+{
+	const unsigned char *str = NODE_LEAF_STRING(node);
+	const unsigned int str_len = node->leaf_string_length;
+	unsigned int i;
+
+	if (data_len != str_len) {
+		/* different lengths, can't match */
+		node_split_string(trie, node);
+		return FALSE;
+	}
+
+	for (i = 0; i < data_len; i++) {
+		if (data[i] != str[i]) {
+			/* non-match */
+			node_split_string(trie, node);
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static int squat_build_add(struct squat_trie *trie, uint32_t uid,
+			   const unsigned char *data, unsigned int size)
+{
+	struct squat_node *node = &trie->root;
+	const unsigned char *end = data + size;
+	unsigned char *chars;
+	unsigned int idx;
+	int level = 0;
+
+	for (;;) {
+		if (node->children_not_mapped) {
+			if (unlikely(node_read_children(trie, node, level) < 0))
 				return -1;
 		}
+
+		if (node->leaf_string_length != 0) {
+			/* the whole string must match or we need to split
+			   the node */
+			if (node_leaf_string_add_or_split(trie, node, data,
+							  end - data)) {
+				node_add_uid(trie, uid, node);
+				return 0;
+			}
+		}
+
+		node_add_uid(trie, uid, node);
+
+		if (unlikely(uid < node->unused_uids)) {
+			squat_trie_set_corrupted(trie);
+			return -1;
+		}
+		/* child node's UIDs are relative to ours. so for example if
+		   we're adding UID 4 and this node now has [2,4] UIDs,
+		   unused_uids=3 and so the child node will be adding
+		   UID 4-3 = 1. */
+		uid -= node->unused_uids;
+
+		if (data == end)
+			return 0;
+		level++;
+
+		if (node->have_sequential) {
+			if (*data < SEQUENTIAL_COUNT) {
+				idx = *data;
+				goto found;
+			}
+			idx = SEQUENTIAL_COUNT;
+		} else {
+			idx = 0;
+		}
+		chars = NODE_CHILDREN_CHARS(node);
+		for (; idx < node->child_count; idx++) {
+			if (chars[idx] == *data)
+				goto found;
+		}
+		break;
+	found:
+		data++;
+		node = NODE_CHILDREN_NODES(node) + idx;
+	}
+
+	/* create new children */
+	i_assert(node->leaf_string_length == 0);
+
+	for (;;) {
+		idx = node_add_child(trie, node, *data,
+				     size - (end - data) + 1);
+		node = NODE_CHILDREN_NODES(node) + idx;
+
+		node_add_uid(trie, uid, node);
+		uid = 0;
+
+		if (++data == end)
+			break;
+
+		if (!node->have_sequential) {
+			/* convert the node into a leaf string */
+			unsigned int len = end - data;
+
+			i_assert(node->children.data == NULL);
+			node->leaf_string_length = len;
+			if (!NODE_IS_DYNAMIC_LEAF(node)) {
+				memcpy(node->children.static_leaf_string,
+				       data, len);
+			} else {
+				node->children.leaf_string = i_malloc(len);
+				memcpy(node->children.leaf_string, data, len);
+			}
+			break;
+		}
 	}
 	return 0;
 }
 
-static int trie_write_node(struct squat_trie_build_context *ctx,
-			   unsigned int level, struct trie_node *node)
+static int
+squat_build_word(struct squat_trie *trie, uint32_t uid,
+		 const unsigned char *data, unsigned int size)
 {
-	struct squat_trie *trie = ctx->trie;
-	uoff_t offset;
+#if MAX_PARTIAL_LEN > 0
+	unsigned int i;
 
-	if (level < BLOCK_SIZE) {
-		struct trie_node **children8 = NODE_CHILDREN8(node);
-		struct trie_node **children16 = NODE_CHILDREN16(node, level);
-
-		if (trie_write_node_children(ctx, level + 1,
-					     children8,
-					     node->chars_8bit_count) < 0)
-			return -1;
-		if (trie_write_node_children(ctx, level + 1,
-					     children16,
-					     node->chars_16bit_count) < 0)
+	for (i = size - 1; i > 0; i--) {
+		if (squat_build_add(trie, uid, data + i,
+				    I_MIN(MAX_PARTIAL_LEN, size - i)) < 0)
 			return -1;
 	}
-
-	if (!node->modified)
-		return 0;
-
-	if (level < BLOCK_SIZE) {
-		if (level <= FAST_8BIT_LEVEL)
-			squat_trie_compress_chars8(node);
-		node_pack(trie->buf, node);
-	} else {
-		if (node_leaf_finish(trie, node) < 0)
-			return -1;
-
-		node_pack_leaf(trie->buf, node);
-	}
-
-	offset = ctx->output->offset;
-	if ((offset & 1) != 0) {
-		o_stream_send(ctx->output, "", 1);
-		offset++;
-	}
-
-	if (node->resized && node->orig_size < trie->buf->used) {
-		/* append to end of file. the parent node is written later. */
-		node->file_offset = offset;
-		o_stream_send(ctx->output, trie->buf->data, trie->buf->used);
-
-		ctx->deleted_space += node->orig_size;
-	} else {
-		/* overwrite node's contents */
-		i_assert(node->file_offset != 0);
-		i_assert(trie->buf->used <= node->orig_size);
-
-		/* FIXME: write only the indexes if !node->resized */
-		o_stream_seek(ctx->output, node->file_offset);
-		o_stream_send(ctx->output, trie->buf->data, trie->buf->used);
-		o_stream_seek(ctx->output, offset);
-
-		ctx->deleted_space += trie->buf->used - node->orig_size;
-	}
-
-	ctx->modified = TRUE;
-	return 0;
+#endif
+	return squat_build_add(trie, uid, data, I_MIN(size, MAX_FULL_LEN));
 }
 
-static int
-trie_nodes_write(struct squat_trie_build_context *ctx, uint32_t *uidvalidity_r)
+static unsigned char *
+squat_data_normalize(struct squat_trie *trie, const unsigned char *data,
+		     unsigned int size)
 {
-	struct squat_trie *trie = ctx->trie;
-	struct squat_trie_header hdr;
-
-	hdr = *trie->hdr;
-	ctx->output = o_stream_create_fd_file(trie->fd, (uoff_t)-1, FALSE);
-	o_stream_seek(ctx->output, hdr.used_file_size);
-	o_stream_cork(ctx->output);
-	if (hdr.used_file_size == 0) {
-		o_stream_send(ctx->output, &hdr, sizeof(hdr));
-		ctx->modified = TRUE;
-	}
+	unsigned char *dest;
+	unsigned int i;
 
-	ctx->deleted_space = 0;
-	if (trie_write_node(ctx, 1, trie->root) < 0)
-		return -1;
-
-	if (ctx->modified) {
-		/* update the header */
-		hdr.root_offset = trie->root->file_offset;
-		hdr.used_file_size = ctx->output->offset;
-		hdr.deleted_space += ctx->deleted_space;
-		hdr.node_count = ctx->node_count;
-		hdr.modify_counter++;
-		o_stream_seek(ctx->output, 0);
-		o_stream_send(ctx->output, &hdr, sizeof(hdr));
-	}
-
-	o_stream_destroy(&ctx->output);
-	*uidvalidity_r = hdr.uidvalidity;
-	return 0;
+	dest = t_malloc(size);
+	for (i = 0; i < size; i++)
+		dest[i] = trie->normalize_map[data[i]];
+	return dest;
 }
 
-static bool squat_trie_need_compress(struct squat_trie *trie,
-				     unsigned int current_message_count)
-{
-	uint32_t max_del_space;
-
-	if (trie->hdr->used_file_size >= TRIE_COMPRESS_MIN_SIZE) {
-		/* see if we've reached the max. deleted space in file */
-		max_del_space = trie->hdr->used_file_size / 100 *
-			TRIE_COMPRESS_PERCENTAGE;
-		if (trie->hdr->deleted_space > max_del_space)
-			return TRUE;
-	}
-
-	return squat_uidlist_need_compress(trie->uidlist,
-					   current_message_count);
-}
-
-static int
-squat_trie_build_flush(struct squat_trie_build_context *ctx, bool finish)
+int squat_trie_build_more(struct squat_trie_build_context *ctx,
+			  uint32_t uid, enum squat_index_type type,
+			  const unsigned char *data, unsigned int size)
 {
 	struct squat_trie *trie = ctx->trie;
-	uint32_t uidvalidity;
+	unsigned int i, start = 0;
+	int ret = 0;
+
+	uid = uid * 2 + (type == SQUAT_INDEX_TYPE_HEADER ? 0 : 1);
 
-	if (trie->root == NULL) {
-		/* nothing changed */
-		return 0;
-	}
-
-	if (trie->corrupted)
-		return -1;
+	t_push();
+	data = squat_data_normalize(trie, data, size);
+	for (i = 0; i < size; i++) {
+		if (data[i] != '\0')
+			continue;
 
-	if (trie_nodes_write(ctx, &uidvalidity) < 0)
-		return -1;
-	if (squat_uidlist_flush(trie->uidlist, uidvalidity) < 0)
-		return -1;
-
-	squat_trie_unmap(trie);
-	if (squat_trie_map(trie) <= 0)
-		return -1;
-
-	if (squat_trie_need_compress(trie, (unsigned int)-1)) {
-		if (ctx->locked && finish) {
-			squat_trie_unlock(ctx->trie);
-			ctx->locked = FALSE;
+		while (start < i && data[start] == '\0')
+			start++;
+		if (i != start) {
+			if (squat_build_word(trie, uid, data + start,
+					     i - start) < 0) {
+				ret = -1;
+				start = i;
+				break;
+			}
 		}
-
-		if (squat_trie_compress(trie, NULL) < 0)
-			return -1;
+		start = i + 1;
 	}
-	return 0;
-}
-
-static void squat_trie_compress_chars8(struct trie_node *node)
-{
-	uint8_t *chars = NODE_CHARS8(node);
-	uint16_t *chars16, *old_chars16 = NODE_CHARS16(node, 0);
-	struct trie_node **child_src = NODE_CHILDREN8(node);
-	struct trie_node **child_dest;
-	unsigned int i, j, old_count;
-
-	old_count = node->chars_8bit_count;
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != NULL)
-			chars[j++] = chars[i];
+	while (start < i && data[start] == '\0')
+		start++;
+	if (i != start) {
+		if (squat_build_word(trie, uid, data + start, i - start) < 0)
+			ret = -1;
 	}
-
-	node->chars_8bit_count = j;
-	child_dest = NODE_CHILDREN8(node);
-
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != NULL)
-			child_dest[j++] = child_src[i];
-	}
-
-	if (node->chars_16bit_count > 0) {
-		chars16 = NODE_CHARS16(node, 0);
-		memmove(chars16, old_chars16,
-			ALIGN(sizeof(*chars16) * node->chars_16bit_count) +
-			sizeof(*child_src) * node->chars_16bit_count);
-	}
+	t_pop();
+	return ret;
 }
 
-static void squat_trie_compress_chars16(struct trie_node *node)
+static void node_drop_unused_children(struct squat_node *node)
 {
-	uint16_t *chars = NODE_CHARS16(node, 0);
-	struct trie_node **child_src = NODE_CHILDREN16(node, 0);
-	struct trie_node **child_dest;
-	unsigned int i, j, old_count;
+	unsigned char *chars;
+	struct squat_node *children_src, *children_dest;
+	unsigned int i, j, orig_child_count = node->child_count;
 
-	old_count = node->chars_16bit_count;
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != NULL)
+	chars = NODE_CHILDREN_CHARS(node);
+	children_src = NODE_CHILDREN_NODES(node);
+
+	/* move chars */
+	for (i = j = 0; i < orig_child_count; i++) {
+		if (children_src[i].next_uid != 0)
 			chars[j++] = chars[i];
 	}
-
-	node->chars_16bit_count = j;
-	child_dest = NODE_CHILDREN16(node, 0);
-
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != NULL)
-			child_dest[j++] = child_src[i];
-	}
-}
-
-static void squat_trie_compress_leaf_chars8(struct trie_node *node)
-{
-	uint8_t *chars = NODE_CHARS8(node);
-	uint32_t *child_src = (uint32_t *)NODE_CHILDREN8(node);
-	uint32_t *child_dest;
-	unsigned int i, j, old_count;
-
-	old_count = node->chars_8bit_count;
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != 0)
-			chars[j++] = chars[i];
-	}
-
-	node->chars_8bit_count = j;
-	child_dest = (uint32_t *)NODE_CHILDREN8(node);
+	node->child_count = j;
 
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != 0)
-			child_dest[j++] = child_src[i];
-	}
-}
-
-static void squat_trie_compress_leaf_chars16(struct trie_node *node)
-{
-	uint16_t *chars = NODE_CHARS16(node, BLOCK_SIZE);
-	uint32_t *child_src = (uint32_t *)NODE_CHILDREN16(node, BLOCK_SIZE);
-	uint32_t *child_dest;
-	unsigned int i, j, old_count;
-
-	old_count = node->chars_16bit_count;
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != 0)
-			chars[j++] = chars[i];
-	}
-
-	node->chars_16bit_count = j;
-	child_dest = (uint32_t *)NODE_CHILDREN16(node, BLOCK_SIZE);
-
-	for (i = j = 0; i < old_count; i++) {
-		if (child_src[i] != 0)
-			child_dest[j++] = child_src[i];
+	/* move children. note that children_dest may point to different
+	   location than children_src, although they both point to the
+	   same node. */
+	children_dest = NODE_CHILDREN_NODES(node);
+	for (i = j = 0; i < orig_child_count; i++) {
+		if (children_src[i].next_uid != 0)
+			children_dest[j++] = children_src[i];
 	}
 }
 
 static int
-squat_trie_compress_children(struct squat_trie_compress_context *ctx,
-			     struct trie_node **children, unsigned int count,
-			     unsigned int level)
+squat_write_node(struct squat_trie_build_context *ctx, struct squat_node *node,
+		 uoff_t *node_offset_r, int level)
 {
-	struct trie_node *child_node;
-	size_t child_idx;
+	struct squat_trie *trie = ctx->trie;
+	struct squat_node *children;
 	unsigned int i;
-	int ret = 0;
-	bool need_char_compress = FALSE;
-
-	for (i = 0; i < count; i++) {
-		if (children[i] == NULL) {
-			need_char_compress = TRUE;
-			continue;
-		}
+	uoff_t *node_offsets;
+	uint8_t child_count;
 
-		child_idx = POINTER_CAST_TO(children[i], size_t);
-		i_assert((child_idx & 1) != 0);
-		child_idx &= ~1;
-
-		if (trie_map_node(ctx->trie, child_idx, level, &child_node) < 0)
-			return -1;
+	i_assert(node->next_uid != 0);
 
-		ret = squat_trie_compress_node(ctx, child_node, level);
-		if (child_node->file_offset != 0)
-			children[i] = POINTER_CAST(child_node->file_offset | 1);
-		else {
-			children[i] = NULL;
-			need_char_compress = TRUE;
-		}
-		i_free(child_node);
-
-		if (ret < 0)
+	if (node->children_not_mapped && ctx->compress_nodes) {
+		if (node_read_children(trie, node, MAX_FAST_LEVEL) < 0)
 			return -1;
 	}
-	return need_char_compress ? 0 : 1;
+
+	node->have_sequential = FALSE;
+	node_drop_unused_children(node);
+
+	child_count = node->child_count;
+	if (child_count == 0) {
+		i_assert(!node->children_not_mapped ||
+			 node->leaf_string_length == 0);
+		*node_offset_r = !node->children_not_mapped ? 0 :
+			node->children.offset;
+		return 0;
+	}
+	i_assert(!node->children_not_mapped);
+
+	trie->hdr.node_count++;
+
+	children = NODE_CHILDREN_NODES(node);
+	node_offsets = t_new(uoff_t, child_count);
+	for (i = 0; i < child_count; i++) {
+		t_push();
+		if (squat_write_node(ctx, &children[i], &node_offsets[i],
+				     level + 1) < 0) {
+			t_pop();
+			return -1;
+		}
+		t_pop();
+	}
+
+	*node_offset_r = ctx->output->offset;
+	node_write_children(ctx, node, node_offsets);
+	return 0;
+}
+
+static int squat_write_nodes(struct squat_trie_build_context *ctx)
+{
+	struct squat_trie *trie = ctx->trie;
+	uoff_t node_offset;
+	int ret;
+
+	if (ctx->trie->root.next_uid == 0)
+		return 0;
+
+	t_push();
+	ret = squat_write_node(ctx, &ctx->trie->root, &node_offset, 0);
+	t_pop();
+	if (ret < 0)
+		return -1;
+
+	trie->hdr.root_offset = node_offset;
+	trie->hdr.root_unused_uids = trie->root.unused_uids;
+	trie->hdr.root_next_uid = trie->root.next_uid;
+	trie->hdr.root_uidlist_idx = trie->root.uid_list_idx;
+	return 0;
+}
+
+static struct squat_trie_iterate_context *
+squat_trie_iterate_uidlist_init(struct squat_trie *trie)
+{
+	struct squat_trie_iterate_context *ctx;
+
+	ctx = i_new(struct squat_trie_iterate_context, 1);
+	ctx->trie = trie;
+	ctx->cur.node = &trie->root;
+	i_array_init(&ctx->parents, MAX_FULL_LEN*2);
+	return ctx;
 }
 
 static int
-squat_trie_compress_leaf_uidlist(struct squat_trie_compress_context *ctx,
-				 struct trie_node *node)
+squat_trie_iterate_uidlist_deinit(struct squat_trie_iterate_context *ctx)
+{
+	int ret = ctx->failed ? -1 : 0;
+
+	array_free(&ctx->parents);
+	i_free(ctx);
+	return ret;
+}
+
+static struct squat_node *
+squat_trie_iterate_uidlist_first(struct squat_trie_iterate_context *ctx)
 {
-	uint32_t *idx8 = (uint32_t *)NODE_CHILDREN8(node);
-	uint32_t *idx16 = (uint32_t *)NODE_CHILDREN16(node, BLOCK_SIZE);
-	unsigned int i;
-	int ret;
-	bool compress_chars = FALSE;
+	struct squat_node *node = ctx->cur.node;
+	int level;
 
-	for (i = 0; i < node->chars_8bit_count; i++) {
-		ret = squat_uidlist_compress_next(ctx->uidlist_ctx, &idx8[i]);
-		if (ret < 0)
-			return -1;
-		if (ret == 0) {
-			idx8[i] = 0;
-			compress_chars = TRUE;
+	if (UIDLIST_IS_SINGLETON(node->uid_list_idx)) {
+		/* no uidlists */
+		i_assert(node == &ctx->trie->root);
+		return NULL;
+	}
+
+	if (node->children_not_mapped) {
+		level = array_count(&ctx->parents);
+
+		if (node_read_children(ctx->trie, node, level) < 0) {
+			ctx->failed = TRUE;
+			return NULL;
 		}
 	}
-	if (compress_chars) {
-		squat_trie_compress_leaf_chars8(node);
-		compress_chars = FALSE;
+	return node;
+}
+
+static struct squat_node *
+squat_trie_iterate_uidlist_next(struct squat_trie_iterate_context *ctx)
+{
+	struct squat_trie_iterate_node *iter_nodes;
+	struct squat_node *node = ctx->cur.node;
+	struct squat_node *children;
+	unsigned int count;
+
+	/* return our children first */
+	children = NODE_CHILDREN_NODES(node);
+	for (; ctx->cur.idx < node->child_count; ctx->cur.idx++) {
+		if (!UIDLIST_IS_SINGLETON(children[ctx->cur.idx].uid_list_idx))
+			return &children[ctx->cur.idx++];
+	}
+
+	ctx->cur.idx = 0;
+	for (;;) {
+		/* next start iterating our childrens' children */
+		while (ctx->cur.idx < node->child_count) {
+			uint32_t list_idx =
+				children[ctx->cur.idx++].uid_list_idx;
+
+			if (UIDLIST_IS_SINGLETON(list_idx))
+				continue;
+
+			array_append(&ctx->parents, &ctx->cur, 1);
+			ctx->cur.node = &children[ctx->cur.idx-1];
+			ctx->cur.idx = 0;
+			if (squat_trie_iterate_uidlist_first(ctx) == NULL)
+				return NULL;
+			return squat_trie_iterate_uidlist_next(ctx);
+		}
+
+		/* no more children. go to next sibling's children */
+		iter_nodes = array_get_modifiable(&ctx->parents, &count);
+		if (count == 0)
+			return NULL;
+
+		ctx->cur = iter_nodes[count-1];
+		array_delete(&ctx->parents, count-1, 1);
+
+		node = ctx->cur.node;
+		children = NODE_CHILDREN_NODES(node);
 	}
-	for (i = 0; i < node->chars_16bit_count; i++) {
-		ret = squat_uidlist_compress_next(ctx->uidlist_ctx, &idx16[i]);
-		if (ret < 0)
+}
+
+static int squat_trie_renumber_uidlists(struct squat_trie *trie, bool finish)
+{
+	struct squat_trie_iterate_context *iter;
+	struct squat_node *node;
+	struct squat_uidlist *uidlist = trie->uidlist;
+	struct squat_uidlist_rebuild_context *ctx;
+	ARRAY_TYPE(uint32_t) uids;
+	uint32_t new_uid_list_idx, max_count=0;
+	int ret = 0;
+
+	if ((ret = squat_uidlist_rebuild_init(uidlist, finish, &ctx)) <= 0)
+		return ret;
+
+	i_array_init(&uids, 1024);
+	iter = squat_trie_iterate_uidlist_init(trie);
+	node = squat_trie_iterate_uidlist_first(iter);
+	new_uid_list_idx = 0x100;
+	while (node != NULL) {
+		array_clear(&uids);
+		if (squat_uidlist_get(uidlist, node->uid_list_idx, &uids) < 0) {
+			ret = -1;
+			break;
+		}
+		max_count = I_MAX(max_count, array_count(&uids));
+		squat_uidlist_rebuild_next(ctx, &uids);
+		node->uid_list_idx = new_uid_list_idx << 1;
+		new_uid_list_idx++;
+
+		node = squat_trie_iterate_uidlist_next(iter);
+	}
+	squat_trie_iterate_uidlist_deinit(iter);
+	array_free(&uids);
+
+	return squat_uidlist_rebuild_finish(ctx, ret < 0);
+}
+
+static int squat_trie_map_header(struct squat_trie *trie)
+{
+	if (trie->locked_file_size == 0) {
+		/* newly created file */
+		squat_trie_header_init(trie);
+		return 0;
+	}
+	i_assert(trie->fd != -1);
+
+	if (trie->mmap_size != 0) {
+		if (munmap(trie->mmap_base, trie->mmap_size) < 0)
+			i_error("munmap(%s) failed: %m", trie->path);
+	}
+
+	trie->mmap_size = trie->locked_file_size;
+	trie->mmap_base = mmap(NULL, trie->mmap_size, PROT_READ | PROT_WRITE,
+			       MAP_SHARED, trie->fd, 0);
+	if (trie->mmap_base == MAP_FAILED) {
+		trie->mmap_base = NULL;
+		trie->mmap_size = 0;
+		i_error("mmap(%s) failed: %m", trie->path);
+		return -1;
+	}
+	memcpy(&trie->hdr, trie->mmap_base, sizeof(trie->hdr));
+
+	if (trie->hdr.root_offset == 0)
+		return 0;
+	if (trie->hdr.version != SQUAT_TRIE_VERSION ||
+	    trie->hdr.uidvalidity != trie->uidvalidity) {
+		squat_trie_delete(trie);
+		squat_trie_close(trie);
+		squat_trie_header_init(trie);
+		return 0;
+	}
+	return 0;
+}
+
+static int squat_trie_map(struct squat_trie *trie)
+{
+	struct file_lock *file_lock = NULL;
+	bool changed;
+	int ret;
+
+	if (trie->fd != -1) {
+		if (squat_trie_lock(trie, F_RDLCK, &file_lock) <= 0)
 			return -1;
-		if (ret == 0) {
-			idx16[i] = 0;
-			compress_chars = TRUE;
+	}
+
+	ret = squat_trie_map_header(trie);
+	changed = trie->root.children.offset != trie->hdr.root_offset;
+
+	if (changed || trie->hdr.root_offset == 0) {
+		memset(&trie->root, 0, sizeof(trie->root));
+		trie->root.want_sequential = TRUE;
+		trie->root.unused_uids = trie->hdr.root_unused_uids;
+		trie->root.next_uid = trie->hdr.root_next_uid;
+		trie->root.uid_list_idx = trie->hdr.root_uidlist_idx;
+		trie->root.children.offset = trie->hdr.root_offset;
+
+		if (trie->hdr.root_offset == 0) {
+			trie->unmapped_child_count = 0;
+			trie->root.children_not_mapped = FALSE;
+		} else {
+			trie->unmapped_child_count = 1;
+			trie->root.children_not_mapped = TRUE;
 		}
 	}
-	if (compress_chars) {
-		squat_trie_compress_leaf_chars16(node);
-		node->chars_16bit_count = i;
+
+	if (file_lock != NULL)
+		file_unlock(&file_lock);
+	if (ret < 0)
+		return -1;
+
+	return trie->hdr.root_offset == 0 || !changed ? 0 :
+		node_read_children(trie, &trie->root, 1);
+}
+
+int squat_trie_build_init(struct squat_trie *trie, uint32_t *last_uid_r,
+			  struct squat_trie_build_context **ctx_r)
+{
+	struct squat_trie_build_context *ctx;
+
+	if (trie->fd == -1) {
+		trie->fd = open(trie->path, O_RDWR | O_CREAT, 0600);
+		if (trie->fd == -1) {
+			i_error("creat(%s) failed: %m", trie->path);
+			return -1;
+		}
 	}
+
+	/* uidlist locks building */
+	if (squat_uidlist_build_init(trie->uidlist) < 0)
+		return -1;
+
+	if (squat_trie_map(trie) < 0)
+		return -1;
+
+	ctx = i_new(struct squat_trie_build_context, 1);
+	ctx->trie = trie;
+	ctx->first_uid = trie->root.next_uid;
+
+	*last_uid_r = I_MAX(trie->root.next_uid/2, 1) - 1;
+	*ctx_r = ctx;
+	return 0;
+}
+
+static int squat_trie_write(struct squat_trie_build_context *ctx)
+{
+	struct squat_trie *trie = ctx->trie;
+	struct file_lock *file_lock;
+	struct ostream *output;
+	const char *path;
+	int fd = -1, ret = 0;
+
+	if ((trie->hdr.used_file_size > sizeof(trie->hdr) &&
+	    trie->unmapped_child_count < trie->hdr.node_count/4) || 1) {
+		/* we might as well recreate the file */
+		ctx->compress_nodes = TRUE;
+
+		path = t_strconcat(trie->path, ".tmp", NULL);
+		fd = open(path, O_RDWR | O_CREAT, 0600);
+		if (fd == -1) {
+			i_error("creat(%s) failed: %m", path);
+			return -1;
+		}
+		output = o_stream_create_fd(fd, 0, FALSE);
+		o_stream_cork(output);
+		o_stream_send(output, &trie->hdr, sizeof(trie->hdr));
+		file_lock = NULL;
+	} else {
+		/* we need to lock only the header update */
+		if (squat_trie_lock(trie, F_WRLCK, &file_lock) <= 0)
+			return -1;
+
+		path = trie->path;
+		ctx->compress_nodes =
+			trie->hdr.used_file_size == sizeof(trie->hdr);
+		output = o_stream_create_fd(trie->fd, 0, FALSE);
+		o_stream_cork(output);
+
+		if (trie->hdr.used_file_size == 0)
+			o_stream_send(output, &trie->hdr, sizeof(trie->hdr));
+		else
+			o_stream_seek(output, trie->hdr.used_file_size);
+	}
+
+	ctx->output = output;
+	ret = squat_write_nodes(ctx);
+	ctx->output = NULL;
+
+	if (ret == 0) {
+		trie->hdr.used_file_size = output->offset;
+		o_stream_seek(output, 0);
+		o_stream_send(output, &trie->hdr, sizeof(trie->hdr));
+		o_stream_flush(output);
+	}
+	if (file_lock != NULL)
+		file_unlock(&file_lock);
+
+	if (output->last_failed_errno != 0) {
+		errno = output->last_failed_errno;
+		i_error("write() to %s failed: %m", path);
+		ret = -1;
+	}
+	o_stream_unref(&output);
+	if (fd != -1) {
+		if (ret < 0) {
+			if (close(fd) < 0)
+				i_error("close(%s) failed: %m", path);
+		} else if (rename(path, trie->path) < 0) {
+			i_error("rename(%s, %s) failed: %m", path, trie->path);
+			ret = -1;
+		}
+
+		if (ret < 0) {
+			if (unlink(path) < 0 && errno != ENOENT)
+				i_error("unlink(%s) failed: %m", path);
+		} else {
+			if (close(trie->fd) < 0)
+				i_error("close(%s) failed: %m", trie->path);
+			trie->fd = fd;
+		}
+	}
+	return ret;
+}
+
+int squat_trie_build_deinit(struct squat_trie_build_context **_ctx)
+{
+	struct squat_trie_build_context *ctx = *_ctx;
+	bool compress;
+	int ret;
+
+	*_ctx = NULL;
+
+	compress = (ctx->trie->root.next_uid - ctx->first_uid) > 10;
+
+	ret = squat_uidlist_build_deinit(ctx->trie->uidlist);
+	if (squat_trie_renumber_uidlists(ctx->trie, compress) < 0)
+		ret = -1;
+	else
+		ret = squat_trie_write(ctx);
+
+	i_free(ctx);
+	return ret;
+}
+
+int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *last_uid_r)
+{
+	if (trie->fd == -1) {
+		if (squat_trie_open(trie) < 0)
+			return -1;
+	}
+
+	*last_uid_r = I_MAX(trie->root.next_uid/2, 1) - 1;
 	return 0;
 }
 
 static int
-squat_trie_compress_node(struct squat_trie_compress_context *ctx,
-			 struct trie_node *node, unsigned int level)
+squat_trie_lookup_data(struct squat_trie *trie, const unsigned char *data,
+		       unsigned int size, ARRAY_TYPE(seq_range) *uids)
 {
-	struct squat_trie *trie = ctx->trie;
-	int ret;
-
-	if (level == BLOCK_SIZE) {
-		if (squat_trie_compress_leaf_uidlist(ctx, node))
-			return -1;
+	struct squat_node *node = &trie->root;
+	unsigned char *chars;
+	unsigned int idx;
+	int level = 0;
 
-		if (node->chars_8bit_count == 0 &&
-		    node->chars_16bit_count == 0) {
-			/* everything expunged */
-			ctx->node_count--;
-			node->file_offset = 0;
-			return 0;
-		}
-		node_pack_leaf(trie->buf, node);
-	} else {
-		struct trie_node **children8 = NODE_CHILDREN8(node);
-		struct trie_node **children16;
+	array_clear(uids);
 
-		if ((ret = squat_trie_compress_children(ctx, children8,
-							node->chars_8bit_count,
-							level + 1)) < 0)
-			return -1;
-		if (ret == 0)
-			squat_trie_compress_chars8(node);
+	for (;;) {
+		if (node->children_not_mapped) {
+			if (node_read_children(trie, node, level) < 0)
+				return -1;
+		}
+		if (node->leaf_string_length != 0) {
+			unsigned int str_len = node->leaf_string_length;
+			const unsigned char *str;
 
-		children16 = NODE_CHILDREN16(node, 0);
-		if ((ret = squat_trie_compress_children(ctx, children16,
-							node->chars_16bit_count,
-							level + 1)) < 0)
-			return -1;
-		if (ret == 0)
-			squat_trie_compress_chars16(node);
+			if (str_len > sizeof(node->children.static_leaf_string))
+				str = node->children.leaf_string;
+			else
+				str = node->children.static_leaf_string;
 
-		if (node->chars_8bit_count == 0 &&
-		    node->chars_16bit_count == 0) {
-			/* everything expunged */
-			ctx->node_count--;
-			node->file_offset = 0;
-			return 0;
+			if (size > str_len || memcmp(data, str, size) != 0)
+				return 0;
+
+			/* match */
+			break;
 		}
 
-		node_pack(trie->buf, node);
+		if (size == 0)
+			break;
+		level++;
+
+		if (node->have_sequential) {
+			if (*data < SEQUENTIAL_COUNT) {
+				idx = *data;
+				goto found;
+			}
+			idx = SEQUENTIAL_COUNT;
+		} else {
+			idx = 0;
+		}
+		chars = NODE_CHILDREN_CHARS(node);
+		for (; idx < node->child_count; idx++) {
+			if (chars[idx] == *data)
+				goto found;
+		}
+		return 0;
+	found:
+		/* follow to children */
+		if (level == 1) {
+			/* root level, add all UIDs */
+			if (squat_uidlist_get_seqrange(trie->uidlist,
+						       node->uid_list_idx,
+						       uids) < 0)
+				return -1;
+		} else {
+			if (squat_uidlist_filter(trie->uidlist,
+						 node->uid_list_idx, uids) < 0)
+				return -1;
+		}
+		data++;
+		size--;
+		node = NODE_CHILDREN_NODES(node) + idx;
 	}
 
-	if ((ctx->output->offset & 1) != 0)
-		o_stream_send(ctx->output, "", 1);
-	node->file_offset = ctx->output->offset;
-
-	o_stream_send(ctx->output, trie->buf->data, trie->buf->used);
-	return 0;
-}
-
-static int squat_trie_compress_init(struct squat_trie_compress_context *ctx,
-				    struct squat_trie *trie)
-{
-	struct squat_trie_header hdr;
-
-	memset(ctx, 0, sizeof(*ctx));
-
-	ctx->tmp_path = t_strconcat(trie->filepath, ".tmp", NULL);
-	ctx->fd = open(ctx->tmp_path, O_RDWR | O_CREAT | O_TRUNC, 0600);
-	if (ctx->fd == -1) {
-		i_error("open(%s, O_CREAT) failed: %m", ctx->tmp_path);
+	if (squat_uidlist_filter(trie->uidlist, node->uid_list_idx, uids) < 0)
 		return -1;
-	}
-
-	ctx->trie = trie;
-	ctx->output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
-	ctx->node_count = trie->hdr->node_count;
-
-	/* write a dummy header first */
-	memset(&hdr, 0, sizeof(hdr));
-	o_stream_send(ctx->output, &hdr, sizeof(hdr));
-	return 0;
+	return 1;
 }
 
 static void
-squat_trie_compress_write_header(struct squat_trie_compress_context *ctx,
-				 struct trie_node *root_node)
-{
-	struct squat_trie_header hdr;
-
-	memset(&hdr, 0, sizeof(hdr));
-	hdr.version = SQUAT_TRIE_VERSION;
-	hdr.uidvalidity = ctx->trie->uidvalidity;
-	hdr.root_offset = root_node->file_offset;
-	hdr.used_file_size = ctx->output->offset;
-	hdr.node_count = ctx->node_count;
-
-	o_stream_seek(ctx->output, 0);
-	o_stream_send(ctx->output, &hdr, sizeof(hdr));
-}
-
-int squat_trie_compress(struct squat_trie *trie,
-			const ARRAY_TYPE(seq_range) *existing_uids)
+squat_trie_filter_type(enum squat_index_type type,
+		       const ARRAY_TYPE(seq_range) *src,
+		       ARRAY_TYPE(seq_range) *dest)
 {
-	struct squat_trie_compress_context ctx;
-	struct trie_node *node;
-	struct file_lock *file_lock = NULL;
-	unsigned int orig_lock_count;
-	int ret;
+	const struct seq_range *src_range;
+	struct seq_range new_range;
+	unsigned int i, count, mask;
+	uint32_t next_seq, uid;
 
-	orig_lock_count = trie->lock_count;
-	if (squat_trie_lock(trie, F_WRLCK) <= 0)
-		return -1;
+	array_clear(dest);
+	src_range = array_get(src, &count);
+	if (count == 0)
+		return;
 
-	if (squat_trie_compress_init(&ctx, trie) < 0) {
-		squat_trie_unlock(trie);
-		return -1;
-	}
-
-	ret = trie_map_node(trie, trie->hdr->root_offset, 1, &node);
-	if (ret == 0) {
-		/* do the compression */
-		ctx.uidlist_ctx = squat_uidlist_compress_begin(trie->uidlist,
-							       existing_uids);
-		if ((ret = squat_trie_compress_node(&ctx, node, 1)) < 0)
-			squat_uidlist_compress_rollback(&ctx.uidlist_ctx);
-		else {
-			ret = squat_uidlist_compress_commit(&ctx.uidlist_ctx);
-
-			squat_trie_compress_write_header(&ctx, node);
+	if ((type & SQUAT_INDEX_TYPE_HEADER) != 0 &&
+	    (type & SQUAT_INDEX_TYPE_BODY) != 0) {
+		/* everything is fine, just fix the UIDs */
+		new_range.seq1 = src_range[0].seq1 / 2;
+		new_range.seq2 = src_range[0].seq2 / 2;
+		for (i = 1; i < count; i++) {
+			next_seq = src_range[i].seq1 / 2;
+			if (next_seq == new_range.seq2 + 1) {
+				/* we can continue the previous range */
+			} else {
+				array_append(dest, &new_range, 1);
+				new_range.seq1 = src_range[i].seq1 / 2;
+			}
+			new_range.seq2 = src_range[i].seq2 / 2;
 		}
+		array_append(dest, &new_range, 1);
+		return;
 	}
 
-	if (ret == 0 && orig_lock_count > 0) {
-		/* lock the file before renaming so we can keep it locked. */
-		if (squat_trie_file_lock(trie, ctx.fd, ctx.tmp_path, F_WRLCK,
-					 &file_lock) <= 0)
-			ret = -1;
-	}
-
-	if (ret == 0) {
-		if (rename(ctx.tmp_path, trie->filepath) < 0) {
-			i_error("rename(%s, %s) failed: %m",
-				ctx.tmp_path, trie->filepath);
-			ret = -1;
+	/* we'll have to drop either header or body UIDs */
+	mask = (type & SQUAT_INDEX_TYPE_HEADER) != 0 ? 0 : 1;
+	for (i = 0; i < count; i++) {
+		for (uid = src_range[i].seq1; uid <= src_range[i].seq2; uid++) {
+			if ((uid & 1) == mask)
+				seq_range_array_add(dest, 0, uid/2);
 		}
 	}
-
-	o_stream_destroy(&ctx.output);
-	squat_trie_unlock(trie);
-
-	if (ret < 0) {
-		if (file_lock != NULL)
-			file_lock_free(&file_lock);
-		(void)close(ctx.fd);
-		(void)unlink(ctx.tmp_path);
-	} else {
-		trie_file_close(trie);
-		trie_file_open_fd(trie, ctx.fd);
-
-		trie->file_lock = file_lock;
-		if (squat_trie_map(trie) <= 0)
-			return -1;
-	}
-	return ret;
-}
-
-int squat_trie_mark_having_expunges(struct squat_trie *trie,
-				    const ARRAY_TYPE(seq_range) *existing_uids,
-				    unsigned int current_message_count)
-{
-	bool compress;
-	int ret;
-
-	if ((ret = squat_trie_lock(trie, F_RDLCK)) <= 0)
-		return ret;
-	compress = squat_trie_need_compress(trie, current_message_count);
-	squat_trie_unlock(trie);
-
-	ret = squat_uidlist_mark_having_expunges(trie->uidlist, compress);
-
-	if (compress)
-		ret = squat_trie_compress(trie, existing_uids);
-	return ret;
-}
-
-size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r)
-{
-	*count_r = trie->hdr == NULL ? 0 : trie->hdr->node_count;
-
-	return trie->mmap_size;
 }
 
-static int squat_trie_lookup_init(struct squat_trie *trie, const char *str,
-				  const uint16_t **data_r, unsigned int *len_r)
+int squat_trie_lookup(struct squat_trie *trie, const char *str,
+		      enum squat_index_type type,
+		      ARRAY_TYPE(seq_range) *definite_uids,
+		      ARRAY_TYPE(seq_range) *maybe_uids)
 {
-	const uint16_t *data;
-	unsigned int len = strlen(str);
+	ARRAY_TYPE(seq_range) tmp_uids, tmp_uids2;
+	unsigned char *data, *block;
+	unsigned int size;
+	bool first;
+	int ret = 0;
 
-	if (len < BLOCK_SIZE)
-		return -1;
-
-	data = data_normalize(str, len, trie->buf);
+	t_push();
+	size = strlen(str);
+	data = t_malloc(size);
+	memcpy(data, str, size);
+	data = squat_data_normalize(trie, data, size);
+	t_array_init(&tmp_uids, 128);
+	t_array_init(&tmp_uids2, 128);
 
-	/* skip the blocks that can't exist */
-	while (!block_want_add(data + len - BLOCK_SIZE)) {
-		if (--len < BLOCK_SIZE)
-			return -1;
+	if (MAX_FULL_LEN > MAX_PARTIAL_LEN || size <= MAX_PARTIAL_LEN) {
+		ret = squat_trie_lookup_data(trie, data, size, &tmp_uids);
+		if (ret > 0)
+			squat_trie_filter_type(type, &tmp_uids, definite_uids);
+	} else {
+		array_clear(definite_uids);
 	}
 
-	if (squat_trie_lock(trie, F_RDLCK) <= 0)
-		return -1;
-
-	*data_r = data;
-	*len_r = len;
-	return 0;
-}
-
-static int
-squat_trie_lookup_locked(struct squat_trie *trie, ARRAY_TYPE(seq_range) *result,
-			 const uint16_t *data, unsigned int len)
-{
-	uint32_t list;
-
-	list = trie_lookup_node(trie, trie->root, data + len - BLOCK_SIZE, 1);
-	if (list == 0)
-		return 0;
-
-	if (squat_uidlist_get(trie->uidlist, list, result) < 0) {
-		squat_trie_set_corrupted(trie, "uidlist offset broken");
-		return -1;
-	}
-	while (len > BLOCK_SIZE) {
-		len--;
-
-		if (!block_want_add(data + len - BLOCK_SIZE))
-			continue;
-
-		list = trie_lookup_node(trie, trie->root,
-					data + len - BLOCK_SIZE, 1);
-		if (list == 0) {
-			array_clear(result);
-			return 0;
-		}
-		if (squat_uidlist_filter(trie->uidlist, list, result) < 0) {
-			squat_trie_set_corrupted(trie, "uidlist offset broken");
-			return -1;
-		}
+	if (size <= MAX_PARTIAL_LEN || MAX_PARTIAL_LEN == 0) {
+		/* we have the result */
+		array_clear(maybe_uids);
+	} else {
+		first = TRUE;
+		do {
+			block = data + size - MAX_PARTIAL_LEN;
+			ret = squat_trie_lookup_data(trie, block,
+						     MAX_PARTIAL_LEN,
+						     &tmp_uids);
+			if (ret <= 0) {
+				array_clear(maybe_uids);
+				break;
+			}
+			if (first) {
+				squat_trie_filter_type(type, &tmp_uids,
+						       maybe_uids);
+				first = FALSE;
+			} else {
+				squat_trie_filter_type(type, &tmp_uids,
+						       &tmp_uids2);
+				seq_range_array_remove_invert_range(maybe_uids,
+								    &tmp_uids2);
+			}
+		} while (--size >= MAX_PARTIAL_LEN);
 	}
-	return array_count(result) > 0 ? 1 : 0;
-}
-
-int squat_trie_lookup(struct squat_trie *trie, ARRAY_TYPE(seq_range) *result,
-		      const char *str)
-{
-	const uint16_t *data;
-	unsigned int len;
-	int ret;
-
-	if (squat_trie_lookup_init(trie, str, &data, &len) < 0)
-		return -1;
-
-	ret = squat_trie_lookup_locked(trie, result, data, len);
-	squat_trie_unlock(trie);
-	return ret;
-}
-
-static int
-squat_trie_filter_locked(struct squat_trie *trie, ARRAY_TYPE(seq_range) *result,
-			 const uint16_t *data, unsigned int len)
-{
-	uint32_t list;
-
-	for (; len >= BLOCK_SIZE; len--) {
-		if (!block_want_add(data + len - BLOCK_SIZE))
-			continue;
-
-		list = trie_lookup_node(trie, trie->root,
-					data + len - BLOCK_SIZE, 1);
-		if (list == 0) {
-			array_clear(result);
-			return 0;
-		}
-		if (squat_uidlist_filter(trie->uidlist, list, result) < 0) {
-			squat_trie_set_corrupted(trie, "uidlist offset broken");
-			return -1;
-		}
-	}
-	return array_count(result) > 0 ? 1 : 0;
-}
-
-int squat_trie_filter(struct squat_trie *trie, ARRAY_TYPE(seq_range) *result,
-		      const char *str)
-{
-	const uint16_t *data;
-	unsigned int len;
-	int ret;
-
-	if (squat_trie_lookup_init(trie, str, &data, &len) < 0)
-		return -1;
-	ret = squat_trie_filter_locked(trie, result, data, len);
-	squat_trie_unlock(trie);
-	return ret;
+	t_pop();
+	return ret < 0 ? -1 :
+		(array_count(maybe_uids) > 0 ||
+		 array_count(definite_uids) > 0 ? 1 : 0);
 }
 
 struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie)
 {
 	return trie->uidlist;
 }
+
+size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r)
+{
+	*count_r = trie->hdr.node_count;
+	return trie->node_alloc_size;
+}
--- a/src/plugins/fts-squat/squat-trie.h	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/squat-trie.h	Sun Dec 02 23:51:46 2007 +0200
@@ -4,41 +4,36 @@
 #include "file-lock.h"
 #include "seq-range-array.h"
 
+enum squat_index_type {
+	SQUAT_INDEX_TYPE_HEADER	= 0x01,
+	SQUAT_INDEX_TYPE_BODY	= 0x02
+};
+
+struct squat_trie_build_context;
+
 struct squat_trie *
-squat_trie_open(const char *path, uint32_t uidvalidity,
+squat_trie_init(const char *path, uint32_t uidvalidity,
 		enum file_lock_method lock_method, bool mmap_disable);
-void squat_trie_close(struct squat_trie *trie);
+void squat_trie_deinit(struct squat_trie **trie);
+
+void squat_trie_refresh(struct squat_trie *trie);
+
+int squat_trie_build_init(struct squat_trie *trie, uint32_t *last_uid_r,
+			  struct squat_trie_build_context **ctx_r);
+/* headers must be added before bodies */
+int squat_trie_build_more(struct squat_trie_build_context *ctx,
+			  uint32_t uid, enum squat_index_type type,
+			  const unsigned char *data, unsigned int size);
+int squat_trie_build_deinit(struct squat_trie_build_context **ctx);
 
 int squat_trie_get_last_uid(struct squat_trie *trie, uint32_t *last_uid_r);
-
-int squat_trie_lock(struct squat_trie *trie, int lock_type);
-void squat_trie_unlock(struct squat_trie *trie);
-
-struct squat_trie_build_context *
-squat_trie_build_init(struct squat_trie *trie, uint32_t *last_uid_r);
-int squat_trie_build_more(struct squat_trie_build_context *ctx, uint32_t uid,
-			  const unsigned char *data, size_t size);
-int squat_trie_build_deinit(struct squat_trie_build_context *ctx);
+/* type specifies if we're looking at header, body or both */
+int squat_trie_lookup(struct squat_trie *trie, const char *str,
+		      enum squat_index_type type,
+		      ARRAY_TYPE(seq_range) *definite_uids,
+		      ARRAY_TYPE(seq_range) *maybe_uids);
 
-int squat_trie_compress(struct squat_trie *trie,
-			const ARRAY_TYPE(seq_range) *existing_uids);
-
-int squat_trie_mark_having_expunges(struct squat_trie *trie,
-				    const ARRAY_TYPE(seq_range) *existing_uids,
-				    unsigned int current_message_count);
-
-int squat_trie_lookup(struct squat_trie *trie, ARRAY_TYPE(seq_range) *result,
-		      const char *str);
-int squat_trie_filter(struct squat_trie *trie, ARRAY_TYPE(seq_range) *result,
-		      const char *str);
-
+struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie);
 size_t squat_trie_mem_used(struct squat_trie *trie, unsigned int *count_r);
 
-struct squat_uidlist *squat_trie_get_uidlist(struct squat_trie *trie);
-
-void squat_trie_pack_num(buffer_t *buffer, uint32_t num);
-uint32_t squat_trie_unpack_num(const uint8_t **p, const uint8_t *end);
-
-void squat_trie_set_corrupted(struct squat_trie *trie, const char *reason);
-
 #endif
--- a/src/plugins/fts-squat/squat-uidlist.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/squat-uidlist.c	Sun Dec 02 23:51:46 2007 +0200
@@ -1,291 +1,351 @@
-/* Copyright (c) 2006-2007 Dovecot authors, see the included COPYING file */
+/* Copyright (c) 2007 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
 #include "array.h"
+#include "bsearch-insert-pos.h"
+#include "file-lock.h"
 #include "ostream.h"
-#include "file-cache.h"
-#include "mmap-util.h"
-#include "read-full.h"
 #include "write-full.h"
-#include "squat-trie.h"
+#include "squat-trie-private.h"
 #include "squat-uidlist.h"
 
 #include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
 #include <sys/stat.h>
+#include <sys/mman.h>
 
-#define UIDLIST_COMPRESS_PERCENTAGE 30
-#define UIDLIST_UID_COMPRESS_PERCENTAGE 20
-#define UIDLIST_COMPRESS_MIN_SIZE (1024*8)
-#define SQUAT_UIDLIST_FLUSH_THRESHOLD (1024*1024*31)
-
-#define UID_NODE_PREV_FLAG_OLD 0x00000001
-#define UID_LIST_IDX_FLAG_SINGLE 0x80000000
-
-struct squat_uidlist_header {
-	uint32_t uidvalidity; 
-	uint32_t used_file_size;
-	uint32_t deleted_space;
+#define UIDLIST_LIST_SIZE 31
+#define UIDLIST_BLOCK_LIST_COUNT 100
+#define UID_LIST_MASK_RANGE 0x80000000
 
-	uint32_t uid_max;
-	uint32_t uid_count;
-	uint8_t uids_expunged; /* updated without locking */
-	uint8_t unused[3];
-	uint32_t node_count;
-};
+/* set = points to uidlist index number, unset = points to uidlist offset */
+#define UID_LIST_POINTER_MASK_LIST_IDX 0x80000000
 
-struct uid_node {
-	struct uid_node *prev;
-	uint32_t uid;
-};
+#define UIDLIST_PACKED_FLAG_BITMASK 1
+#define UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER 2
 
-struct squat_uidlist_get_context {
-	struct squat_uidlist *uidlist;
-
-	ARRAY_TYPE(seq_range) *result;
-
-	uint32_t filter_uid_pos;
+struct uidlist_list {
+	uint32_t uid_count:31;
+	uint32_t uid_begins_with_pointer:1;
+	uint32_t uid_list[UIDLIST_LIST_SIZE];
 };
 
 struct squat_uidlist {
 	struct squat_trie *trie;
-	uint32_t uidvalidity; 
+
+	char *path;
+	struct ostream *output;
+	int fd;
+
+	struct file_lock *file_lock;
+	uoff_t locked_file_size;
+
+	ARRAY_DEFINE(lists, struct uidlist_list);
+	ARRAY_TYPE(uint32_t) block_offsets;
+	ARRAY_TYPE(uint32_t) block_end_indexes;
+	uint32_t list_start_idx;
 
-	char *filepath;
+	void *mmap_base;
+	size_t mmap_size;
+	struct squat_uidlist_file_header hdr;
+	struct squat_uidlist_file_header build_hdr;
+
+	unsigned int cur_block_count;
+	const uint32_t *cur_block_offsets;
+	const uint32_t *cur_block_end_indexes;
+
+	size_t max_size;
+	unsigned int corrupted:1;
+};
+
+struct squat_uidlist_rebuild_context {
+	struct squat_uidlist *uidlist;
 	int fd;
 	struct ostream *output;
 
-	dev_t dev;
-	ino_t ino;
-
-	void *mmap_base;
-	const uint8_t *const_mmap_base;
-	size_t mmap_size;
-
-	struct file_cache *file_cache;
-	struct squat_uidlist_header hdr;
+	ARRAY_TYPE(uint32_t) new_block_offsets, new_block_end_indexes;
+	uoff_t cur_block_start_offset;
 
-	ARRAY_DEFINE(lists, struct uid_node);
-	uint32_t first_new_list_idx;
-	uint32_t current_uid;
-
-	pool_t node_pool;
-	size_t node_pool_used;
-	buffer_t *tmp_buf, *list_buf;
-
-	unsigned int check_expunges:1;
-	unsigned int write_failed:1;
-	unsigned int mmap_disable:1;
+	uint32_t list_sizes[UIDLIST_BLOCK_LIST_COUNT];
+	unsigned int list_idx;
+	unsigned int new_count;
 };
 
-struct squat_uidlist_compress_ctx {
-	struct squat_uidlist *uidlist;
-	const ARRAY_TYPE(seq_range) *existing_uids;
-
-	struct ostream *output;
-	char *tmp_path;
-
-	pool_t node_pool;
-	struct uid_node *last_node;
-	ARRAY_TYPE(seq_range) seen_uids;
-
-	struct squat_uidlist_header hdr;
+void squat_uidlist_delete(struct squat_uidlist *uidlist)
+{
+	if (unlink(uidlist->path) < 0 && errno != ENOENT)
+		i_error("unlink(%s) failed: %m", uidlist->path);
+}
 
-	unsigned int seen_expunged:1;
-	unsigned int failed:1;
-};
-
-static void squat_uidlist_close(struct squat_uidlist *uidlist);
+static void squat_uidlist_set_corrupted(struct squat_uidlist *uidlist,
+					const char *reason)
+{
+	if (uidlist->corrupted)
+		return;
+	uidlist->corrupted = TRUE;
 
-static void
-squat_uidlist_set_syscall_error(struct squat_uidlist *uidlist,
-				const char *function)
-{
-	i_error("%s failed with index search uidlist file %s: %m",
-		function, uidlist->filepath);
+	i_error("Corrupted squat uidlist file %s: %s", uidlist->path, reason);
+	squat_trie_delete(uidlist->trie);
 }
 
-static int squat_uidlist_check_header(struct squat_uidlist *uidlist,
-				      const struct squat_uidlist_header *hdr,
-				      uoff_t file_size)
+static uint32_t
+uidlist_write_array(struct ostream *output, const uint32_t *uid_list,
+		    unsigned int uid_count, uint32_t packed_flags,
+		    uint32_t offset, bool write_size)
 {
-	if (hdr->used_file_size == 0) {
-		/* crashed before writing was finished */
+	uint8_t *uidbuf, *bufp, sizebuf[SQUAT_PACK_MAX_SIZE], *sizebufp;
+	uint8_t listbuf[SQUAT_PACK_MAX_SIZE], *listbufp = listbuf;
+	uint32_t uid, uid2, prev, base_uid, size_value;
+	unsigned int i, bitmask_len, uid_list_len;
+	unsigned int idx, max_idx, mask;
+	bool datastack;
+	int num;
+
+	if ((packed_flags & UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER) != 0)
+		squat_pack_num(&listbufp, offset);
+
+	/* @UNSAFE */
+	t_push();
+	base_uid = uid_list[0] & ~UID_LIST_MASK_RANGE;
+	datastack = uid_count < 1024*8/SQUAT_PACK_MAX_SIZE;
+	if (datastack)
+		uidbuf = t_malloc(SQUAT_PACK_MAX_SIZE * uid_count);
+	else
+		uidbuf = i_malloc(SQUAT_PACK_MAX_SIZE * uid_count);
+	bufp = uidbuf;
+	squat_pack_num(&bufp, base_uid);
+
+	bitmask_len = (uid_list[uid_count-1] - base_uid + 7) / 8 +
+		(bufp - uidbuf);
+	if (bitmask_len < uid_count) {
+	bitmask_build:
+		i_assert(bitmask_len < SQUAT_PACK_MAX_SIZE*uid_count);
+
+		memset(bufp, 0, bitmask_len - (bufp - uidbuf));
+		if ((uid_list[0] & UID_LIST_MASK_RANGE) == 0) {
+			i = 1;
+			uid = i == uid_count ? 0 : uid_list[i];
+		} else {
+			i = 0;
+			uid = uid_list[0] + 1;
+		}
+		base_uid++;
+
+		for (; i < uid_count; i++) {
+			if ((uid & UID_LIST_MASK_RANGE) == 0) {
+				uid -= base_uid;
+				uid2 = uid;
+			} else {
+				uid &= ~UID_LIST_MASK_RANGE;
+				uid -= base_uid;
+				uid2 = uid_list[i+1] - base_uid;
+				i++;
+			}
+
+			if (uid2 - uid < 3*8) {
+				for (; uid <= uid2; uid++)
+					bufp[uid / 8] |= 1 << (uid % 8);
+			} else {
+				/* first byte */
+				idx = uid / 8;
+				num = uid % 8;
+				if (num != 0) {
+					uid += 8 - num;
+					for (mask = 0; num < 8; num++)
+						mask |= 1 << num;
+					bufp[idx++] |= mask;
+				}
+
+				/* middle bytes */
+				num = uid2 % 8;
+				max_idx = idx + (uid2 - num - uid)/8;
+				for (; idx < max_idx; idx++, uid += 8)
+					bufp[idx] = 0xff;
+
+				/* last byte */
+				for (mask = 0; num >= 0; num--)
+					mask |= 1 << num;
+				bufp[idx] |= mask;
+			}
+			uid = i+1 == uid_count ? 0 : uid_list[i+1];
+		}
+		uid_list_len = bitmask_len;
+		packed_flags |= UIDLIST_PACKED_FLAG_BITMASK;
+	} else {
+		bufp = uidbuf;
+		prev = 0;
+		for (i = 0; i < uid_count; i++) {
+			uid = uid_list[i];
+			i_assert((uid & ~UID_LIST_MASK_RANGE) >= prev);
+			if ((uid & UID_LIST_MASK_RANGE) == 0) {
+				squat_pack_num(&bufp, (uid - prev) << 1);
+				prev = uid + 1;
+			} else {
+				uid &= ~UID_LIST_MASK_RANGE;
+				squat_pack_num(&bufp, 1 | (uid - prev) << 1);
+				squat_pack_num(&bufp, uid_list[i+1] - uid - 1);
+				prev = uid_list[i+1] + 1;
+				i++;
+			}
+		}
+		uid_list_len = bufp - uidbuf;
+		if (uid_list_len > bitmask_len) {
+			bufp = uidbuf;
+			squat_pack_num(&bufp, base_uid);
+			goto bitmask_build;
+		}
+	}
+
+	size_value = ((uid_list_len +
+		       (listbufp - listbuf)) << 2) | packed_flags;
+	if (write_size) {
+		sizebufp = sizebuf;
+		squat_pack_num(&sizebufp, size_value);
+		o_stream_send(output, sizebuf, sizebufp - sizebuf);
+	}
+	o_stream_send(output, listbuf, listbufp - listbuf);
+	o_stream_send(output, uidbuf, uid_list_len);
+	if (!datastack)
+		i_free(uidbuf);
+	t_pop();
+
+	return size_value;
+}
+
+static uint32_t
+uidlist_write(struct ostream *output, const struct uidlist_list *list,
+	      bool write_size)
+{
+	const uint32_t *uid_list = list->uid_list;
+	uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp;
+	uint32_t uid_count = list->uid_count;
+	uint32_t packed_flags = 0;
+	uint32_t offset = 0;
+
+	if (list->uid_begins_with_pointer) {
+		/* continued UID list */
+		packed_flags |= UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER;
+		if ((uid_list[0] & UID_LIST_POINTER_MASK_LIST_IDX) != 0) {
+			offset = ((uid_list[0] & ~UID_LIST_POINTER_MASK_LIST_IDX) << 1) | 1;
+			if (list->uid_count == 1) {
+				bufp = buf;
+				squat_pack_num(&bufp, offset);
+				o_stream_send(output, buf, bufp - buf);
+				return (bufp - buf) << 2 | packed_flags;
+			}
+		} else {
+			i_assert(list->uid_count > 1);
+			i_assert(output->offset > uid_list[0]);
+			offset = (output->offset - uid_list[0]) << 1;
+		}
+		uid_list++;
+		uid_count--;
+	}
+
+	return uidlist_write_array(output, uid_list, uid_count,
+				   packed_flags, offset, write_size);
+}
+
+static int node_uidlist_map_blocks(struct squat_uidlist *uidlist)
+{
+	const struct squat_uidlist_file_header *hdr = &uidlist->hdr;
+	const void *base;
+	uint32_t block_count, block_end_offset, i, verify_count;
+
+	block_end_offset = hdr->block_list_offset + sizeof(block_count);
+	if (block_end_offset > uidlist->mmap_size) {
+		squat_uidlist_set_corrupted(uidlist, "block list outside file");
 		return -1;
 	}
 
-	if (hdr->uidvalidity != uidlist->uidvalidity) {
-		squat_trie_set_corrupted(uidlist->trie,
-					 "uidlist: uidvalidity changed");
-		return -1;
-	}
-	if (hdr->used_file_size > file_size) {
-		squat_trie_set_corrupted(uidlist->trie,
-					 "uidlist: used_file_size too large");
+	base = CONST_PTR_OFFSET(uidlist->mmap_base, hdr->block_list_offset);
+	memcpy(&block_count, base, sizeof(block_count));
+	base = CONST_PTR_OFFSET(base, sizeof(block_count));
+
+	block_end_offset += block_count * sizeof(uint32_t)*2;
+	if (block_end_offset > uidlist->mmap_size) {
+		squat_uidlist_set_corrupted(uidlist, "block list outside file");
 		return -1;
 	}
 
+	uidlist->cur_block_count = block_count;
+	uidlist->cur_block_end_indexes = base;
+	uidlist->cur_block_offsets =
+		CONST_PTR_OFFSET(base, block_count * sizeof(uint32_t));
+
+	/* verify just a couple of the end indexes to make sure they
+	   look correct */
+	verify_count = I_MIN(block_count, 8);
+	for (i = 1; i < verify_count; i++) {
+		if (unlikely(uidlist->cur_block_end_indexes[i-1] >=
+			     uidlist->cur_block_end_indexes[i])) {
+			squat_uidlist_set_corrupted(uidlist,
+				"block list corrupted");
+			return -1;
+		}
+	}
 	return 0;
 }
 
-static int squat_uidlist_read_header(struct squat_uidlist *uidlist)
-{
-	int ret;
-
-	ret = pread_full(uidlist->fd, &uidlist->hdr, sizeof(uidlist->hdr), 0);
-	if (ret < 0)
-		squat_uidlist_set_syscall_error(uidlist, "pread_full()");
-	return ret;
-}
-
-static int squat_uidlist_map(struct squat_uidlist *uidlist)
+static int squat_uidlist_map(struct squat_uidlist *uidlist, uoff_t offset)
 {
 	struct stat st;
-	int ret;
 
-	if (!uidlist->mmap_disable) {
-		const struct squat_uidlist_header *hdr = uidlist->mmap_base;
-
-		if (hdr != NULL && hdr->used_file_size <= uidlist->mmap_size) {
-			/* everything is already mapped */
-			uidlist->hdr = *hdr;
-			return 1;
-		}
-	} else {
-		if ((ret = squat_uidlist_read_header(uidlist)) < 0)
-			return -1;
-		if (ret == 0)
-			return 0;
-	}
-
-	if (fstat(uidlist->fd, &st) < 0) {
-		squat_uidlist_set_syscall_error(uidlist, "fstat()");
-		return -1;
-	}
-	uidlist->dev = st.st_dev;
-	uidlist->ino = st.st_ino;
-
-	if (st.st_size <= (off_t)sizeof(uidlist->hdr))
+	if (uidlist->mmap_size > offset)
 		return 0;
 
-	if (uidlist->mmap_base != NULL) {
-		if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0)
-			squat_uidlist_set_syscall_error(uidlist, "munmap()");
+	if (fstat(uidlist->fd, &st) < 0) {
+		i_error("fstat(%s) failed: %m", uidlist->path);
+		return -1;
 	}
-	uidlist->const_mmap_base = NULL;
-
-	if (!uidlist->mmap_disable) {
-		uidlist->mmap_size = st.st_size;
-		uidlist->mmap_base =
-			mmap(NULL, uidlist->mmap_size, PROT_READ | PROT_WRITE,
-			     MAP_SHARED, uidlist->fd, 0);
-		if (uidlist->mmap_base == MAP_FAILED) {
-			uidlist->mmap_size = 0;
-			uidlist->mmap_base = NULL;
-			squat_uidlist_set_syscall_error(uidlist, "mmap()");
-			return -1;
-		}
-		uidlist->const_mmap_base = uidlist->mmap_base;
-		memcpy(&uidlist->hdr, uidlist->mmap_base, sizeof(uidlist->hdr));
-	} else {
-		/* the header is always read separately. everything between it
-		   and the used_file_size doesn't change */
-		file_cache_invalidate(uidlist->file_cache,
-				      uidlist->hdr.used_file_size, (uoff_t)-1);
+	if (st.st_size < (off_t)sizeof(uidlist->hdr)) {
+		squat_uidlist_set_corrupted(uidlist, "File too small");
+		return -1;
 	}
-
-	if (squat_uidlist_check_header(uidlist, &uidlist->hdr, st.st_size) < 0)
-		return 0;
-
-	if (uidlist->hdr.uids_expunged)
-		uidlist->check_expunges = TRUE;
-
-	uidlist->first_new_list_idx = uidlist->hdr.used_file_size;
-	return 1;
-}
-
-static int squat_uidlist_open(struct squat_uidlist *uidlist)
-{
-	int ret;
-
-	i_assert(uidlist->fd == -1);
-
-	uidlist->fd = open(uidlist->filepath, O_RDWR, 0600);
-	if (uidlist->fd == -1) {
-		if (errno == ENOENT)
-			return 0;
-
-		squat_uidlist_set_syscall_error(uidlist, "open()");
+	if (offset >= (uoff_t)st.st_size && offset != (uoff_t)-1) {
+		squat_uidlist_set_corrupted(uidlist,
+					    "Offset points outside file");
 		return -1;
 	}
 
-	if (uidlist->mmap_disable)
-		uidlist->file_cache = file_cache_new(uidlist->fd);
-
-	if ((ret = squat_uidlist_map(uidlist)) == 0) {
-		/* broken */
-		if (unlink(uidlist->filepath) < 0)
-			squat_uidlist_set_syscall_error(uidlist, "unlink()");
-		squat_uidlist_close(uidlist);
+	if (uidlist->mmap_size != 0) {
+		if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0)
+			i_error("munmap(%s) failed: %m", uidlist->path);
 	}
-	return ret;
-}
-
-static int squat_uidlist_create(struct squat_uidlist *uidlist)
-{
-	i_assert(uidlist->fd == -1);
-
-	/* we should get here only if normal file opening failed */
-	uidlist->fd = open(uidlist->filepath, O_RDWR | O_CREAT | O_TRUNC, 0600);
-	if (uidlist->fd == -1) {
-		squat_uidlist_set_syscall_error(uidlist, "open()");
+	uidlist->mmap_size = st.st_size;
+	uidlist->mmap_base = mmap(NULL, uidlist->mmap_size,
+				  PROT_READ | PROT_WRITE,
+				  MAP_SHARED, uidlist->fd, 0);
+	if (uidlist->mmap_base == MAP_FAILED) {
+		uidlist->mmap_base = NULL;
+		uidlist->mmap_size = 0;
+		i_error("mmap(%s) failed: %m", uidlist->path);
 		return -1;
 	}
+	memcpy(&uidlist->hdr, uidlist->mmap_base, sizeof(uidlist->hdr));
 
-	if (uidlist->mmap_disable)
-		uidlist->file_cache = file_cache_new(uidlist->fd);
+	if (uidlist->hdr.indexid != uidlist->trie->hdr.indexid) {
+		squat_uidlist_set_corrupted(uidlist, "wrong indexid");
+		return -1;
+	}
+	if (uidlist->hdr.used_file_size < sizeof(uidlist->hdr) ||
+	    uidlist->hdr.used_file_size > uidlist->mmap_size) {
+		squat_uidlist_set_corrupted(uidlist, "broken used_file_size");
+		return -1;
+	}
+	if (node_uidlist_map_blocks(uidlist) < 0)
+		return -1;
 	return 0;
 }
 
-static void squat_uidlist_close(struct squat_uidlist *uidlist)
-{
-	if (uidlist->file_cache != NULL)
-		file_cache_free(&uidlist->file_cache);
-	if (uidlist->mmap_base != NULL) {
-		if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0)
-			squat_uidlist_set_syscall_error(uidlist, "munmap()");
-		uidlist->mmap_base = NULL;
-	}
-	uidlist->const_mmap_base = NULL;
-	uidlist->mmap_size = 0;
-
-	if (uidlist->fd != -1) {
-		if (close(uidlist->fd) < 0)
-			squat_uidlist_set_syscall_error(uidlist, "close()");
-		uidlist->fd = -1;
-	}
-}
-
-struct squat_uidlist *
-squat_uidlist_init(struct squat_trie *trie, const char *path,
-		   uint32_t uidvalidity, bool mmap_disable)
+struct squat_uidlist *squat_uidlist_init(struct squat_trie *trie)
 {
 	struct squat_uidlist *uidlist;
 
 	uidlist = i_new(struct squat_uidlist, 1);
 	uidlist->trie = trie;
-	uidlist->filepath = i_strdup(path);
-	uidlist->uidvalidity = uidvalidity;
+	uidlist->path = i_strconcat(trie->path, ".uids", NULL);
 	uidlist->fd = -1;
-	uidlist->first_new_list_idx = 1;
-	uidlist->mmap_disable = mmap_disable;
-	i_array_init(&uidlist->lists, 65536);
-	uidlist->node_pool =
-		pool_alloconly_create(MEMPOOL_GROWING"squat uidlist node pool",
-				      65536);
-	uidlist->tmp_buf = buffer_create_dynamic(default_pool, 16);
-	uidlist->list_buf = buffer_create_dynamic(default_pool, 256);
+
 	return uidlist;
 }
 
@@ -293,797 +353,945 @@
 {
 	squat_uidlist_close(uidlist);
 
-	pool_unref(&uidlist->node_pool);
-	array_free(&uidlist->lists);
-	buffer_free(&uidlist->tmp_buf);
-	buffer_free(&uidlist->list_buf);
-	i_free(uidlist->filepath);
+	if (array_is_created(&uidlist->block_offsets))
+		array_free(&uidlist->block_offsets);
+	if (array_is_created(&uidlist->block_end_indexes))
+		array_free(&uidlist->block_end_indexes);
+	if (array_is_created(&uidlist->lists))
+		array_free(&uidlist->lists);
+	i_free(uidlist->path);
 	i_free(uidlist);
 }
 
-int squat_uidlist_refresh(struct squat_uidlist *uidlist)
+int squat_uidlist_open(struct squat_uidlist *uidlist)
 {
-	struct stat st;
-	int ret;
-
-	if (uidlist->fd != -1) {
-		if (stat(uidlist->filepath, &st) < 0) {
-			if (errno == ENOENT)
-				return 0;
-
-			squat_uidlist_set_syscall_error(uidlist, "stat()");
-			return -1;
-		}
-		if (st.st_ino == uidlist->ino &&
-		    CMP_DEV_T(st.st_dev, uidlist->dev)) {
-			/* no need to reopen, just remap */
-			if ((ret = squat_uidlist_map(uidlist)) != 0)
-				return ret < 0 ? -1 : 0;
-			/* broken file */
-		}
-		squat_uidlist_close(uidlist);
-	}
-
-	if (squat_uidlist_open(uidlist) < 0)
-		return -1;
-	return 0;
-}
-
-int squat_uidlist_get_last_uid(struct squat_uidlist *uidlist, uint32_t *uid_r)
-{
-	*uid_r = uidlist->hdr.uid_max;
-	return 0;
-}
+	squat_uidlist_close(uidlist);
 
-int squat_uidlist_add(struct squat_uidlist *uidlist, uint32_t *_uid_list_idx,
-		      uint32_t uid)
-{
-	uint32_t uid_list_idx = *_uid_list_idx;
-	struct uid_node *node, *old_node;
-
-	i_assert(uid > uidlist->hdr.uid_max || uid == uidlist->current_uid);
-
-	if (uid_list_idx == 0) {
-		*_uid_list_idx = uid | UID_LIST_IDX_FLAG_SINGLE;
-		return 0;
-	}
-
-	if (uid > uidlist->hdr.uid_max) {
-		uidlist->current_uid = uid;
-		uidlist->hdr.uid_max = uid;
-		uidlist->hdr.uid_count++;
-	}
-
-	if (uid_list_idx < uidlist->first_new_list_idx) {
-		/* continue an existing list in the uidlist file */
-		old_node = POINTER_CAST((uid_list_idx << 1) |
-					UID_NODE_PREV_FLAG_OLD);
-		uid_list_idx = uidlist->first_new_list_idx +
-			array_count(&uidlist->lists);
-		node = array_append_space(&uidlist->lists);
-
-		uidlist->hdr.node_count++;
-	} else if ((uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) {
-		uint32_t old_uid = uid_list_idx & ~UID_LIST_IDX_FLAG_SINGLE;
-
-		if (uid == old_uid) {
-			/* trying to add the same uid again */
+	uidlist->fd = open(uidlist->path, O_RDWR);
+	if (uidlist->fd == -1) {
+		if (errno == ENOENT) {
+			memset(&uidlist->hdr, 0, sizeof(uidlist->hdr));
 			return 0;
 		}
-
-		/* convert single UID to a list */
-		uidlist->node_pool_used += sizeof(struct uid_node);
-		old_node = p_new(uidlist->node_pool, struct uid_node, 1);
-		old_node->uid = old_uid;
-
-		uid_list_idx = uidlist->first_new_list_idx +
-			array_count(&uidlist->lists);
-		node = array_append_space(&uidlist->lists);
-
-		uidlist->hdr.node_count++;
-	} else {
-		/* update an in-memory list */
-		uint32_t arr_idx = uid_list_idx - uidlist->first_new_list_idx;
-		if (arr_idx >= array_count(&uidlist->lists)) {
-			/* broken */
-			squat_trie_set_corrupted(uidlist->trie,
-				"corrupted uidlist index (adding)");
-			return -1;
-		}
-
-		node = array_idx_modifiable(&uidlist->lists, arr_idx);
-		if (node->uid == uid) {
-			/* trying to add the same uid again */
-			return 0;
-		}
-
-		uidlist->node_pool_used += sizeof(struct uid_node);
-		old_node = p_new(uidlist->node_pool, struct uid_node, 1);
-		*old_node = *node;
-	}
-
-	node->prev = old_node;
-	node->uid = uid;
-	*_uid_list_idx = uid_list_idx;
-	return 0;
-}
-
-static int
-squat_uidlist_map_area(struct squat_uidlist *uidlist,
-		       size_t offset, size_t size)
-{
-	ssize_t ret;
-
-	if (uidlist->file_cache == NULL)
-		return 0;
-
-	ret = file_cache_read(uidlist->file_cache, offset, size);
-	if (ret < 0) {
-		squat_uidlist_set_syscall_error(uidlist, "file_cache_read()");
+		i_error("open(%s) failed: %m", uidlist->path);
 		return -1;
 	}
-	uidlist->const_mmap_base =
-		file_cache_get_map(uidlist->file_cache, &uidlist->mmap_size);
-	return 0;
-}
-
-static int
-squat_uidlist_map_list(struct squat_uidlist *uidlist, size_t offset,
-		       const uint8_t **data_r, uint32_t *size_r)
-{
-	const uint8_t *data, *end;
-	size_t data_offset;
-	uint32_t size;
-
-	if (squat_uidlist_map_area(uidlist, offset, 128) < 0)
-		return -1;
-	if (offset >= uidlist->mmap_size)
-		return -1;
-
-	data = uidlist->const_mmap_base + offset;
-	end = uidlist->const_mmap_base + uidlist->mmap_size;
-
-	size = squat_trie_unpack_num(&data, end);
-	data_offset = data - uidlist->const_mmap_base;
-
-	if (squat_uidlist_map_area(uidlist, data_offset, size) < 0)
-		return -1;
-	if (data_offset + size > uidlist->mmap_size)
-		return -1;
-
-	*data_r = uidlist->const_mmap_base + data_offset;
-	*size_r = size;
-	return 0;
+	return squat_uidlist_map(uidlist, 0);
 }
 
-static int
-squat_uidlist_copy_existing(struct squat_uidlist *uidlist, size_t offset,
-			    uint32_t *prev_uid_r, uint32_t *written_uid_r)
+static int squat_uidlist_is_file_stale(struct squat_uidlist *uidlist)
 {
-	const uint8_t *data, *data_start, *end, *p = NULL;
-	uint32_t size, num, prev_uid, next_uid;
+	struct stat st, st2;
 
-	if (squat_uidlist_map_list(uidlist, offset, &data, &size) < 0)
-		return -1;
-
-	data_start = data;
-	end = data + size;
-
-	prev_uid = next_uid = squat_trie_unpack_num(&data, end);
-	p = data;
-	while (data != end) {
-		num = squat_trie_unpack_num(&data, end);
-		next_uid = prev_uid + (num >> 1) + 1;
+	if (stat(uidlist->path, &st) < 0) {
+		if (errno == ENOENT)
+			return 1;
 
-		if ((num & 1) != 0) {
-			/* prev_uid..next_uid */
-			if (data == end) {
-				/* try to increase this range */
-				break;
-			}
-
-			/* beginning a new uid/range */
-			num = squat_trie_unpack_num(&data, end);
-			next_uid += num + 1;
+		i_error("stat(%s) failed: %m", uidlist->path);
+		return -1;
+	}
+	if (fstat(uidlist->fd, &st2) < 0) {
+		i_error("fstat(%s) failed: %m", uidlist->path);
+		return -1;
+	}
+	uidlist->locked_file_size = st2.st_size;
 
-			prev_uid = next_uid;
-			p = data;
-		}
-
-		prev_uid = next_uid;
-		p = data;
-	}
-
-	*written_uid_r = prev_uid;
-	*prev_uid_r = next_uid;
-
-	uidlist->hdr.deleted_space +=
-		(end - (const uint8_t *)uidlist->const_mmap_base) - offset;
-
-	buffer_append(uidlist->list_buf, data_start, p - data_start);
-	return 0;
+	return st.st_ino == st2.st_ino &&
+		CMP_DEV_T(st.st_dev, st2.st_dev) ? 0 : 1;
 }
 
-static int
-squat_uidlist_write_range(struct squat_uidlist *uidlist,
-			  const struct uid_node *node,
-			  uint32_t *prev_uid_r, uint32_t *written_uid_r,
-			  int level)
+static int squat_uidlist_lock(struct squat_uidlist *uidlist)
 {
-	buffer_t *buffer = uidlist->list_buf;
-	uint32_t written_uid, prev_uid;
-	uint32_t prev_idx = POINTER_CAST_TO(node->prev, uint32_t);
-
-	*prev_uid_r = node->uid;
-
-	if (node->prev == NULL) {
-		/* first UID */
-		squat_trie_pack_num(buffer, node->uid);
-	} else {
-		if ((prev_idx & UID_NODE_PREV_FLAG_OLD) != 0) {
-			prev_idx >>= 1;
-			if (squat_uidlist_copy_existing(uidlist, prev_idx,
-							&prev_uid,
-							&written_uid) < 0 ||
-			    prev_uid >= node->uid) {
-				squat_trie_set_corrupted(uidlist->trie,
-					"corrupted continued uidlist index");
-				return -1;
-			}
-		} else {
-			if (squat_uidlist_write_range(uidlist, node->prev,
-						      &prev_uid, &written_uid,
-						      level+1) < 0)
-				return -1;
-		}
+	int ret;
 
-		/* prev_uid contains the previous node's UID.
-		   written_uid contains the last written UID. */
-		if (prev_uid + 1 == node->uid) {
-			if (level != 0) {
-				/* this node continue the range */
-				*written_uid_r = written_uid;
-				return 0;
-			} else {
-				/* finishing range */
-				squat_trie_pack_num(buffer, 1 |
-					((node->uid - written_uid - 1) << 1));
-				return 0;
-			}
-		}
-		i_assert(prev_uid < node->uid);
-		if (written_uid != prev_uid) {
-			i_assert(written_uid < prev_uid);
-
-			/* range ends at prev_uid */
-			squat_trie_pack_num(buffer, 1 |
-				((prev_uid - written_uid - 1) << 1));
-			/* next uid/range */
-			squat_trie_pack_num(buffer, node->uid - prev_uid - 1);
-		} else {
-			/* no range */
-			squat_trie_pack_num(buffer,
-					     ((node->uid - prev_uid - 1) << 1));
-		}
-	}
-
-	*written_uid_r = node->uid;
-	return 0;
-}
-
-static int squat_uidlist_write_init(struct squat_uidlist *uidlist)
-{
-	i_assert(uidlist->output == NULL);
+	for (;;) {
+		i_assert(uidlist->file_lock == NULL);
 
-	if (uidlist->fd == -1) {
-		if (squat_uidlist_create(uidlist) < 0)
+		ret = file_wait_lock(uidlist->fd, uidlist->path, F_WRLCK,
+				     uidlist->trie->lock_method,
+				     SQUAT_TRIE_LOCK_TIMEOUT,
+				     &uidlist->file_lock);
+		if (ret == 0) {
+			i_error("file_wait_lock(%s) failed: %m", uidlist->path);
+			return 0;
+		}
+		if (ret < 0)
 			return -1;
-	}
-
-	uidlist->output =
-		o_stream_create_fd_file(uidlist->fd, (uoff_t)-1, FALSE);
-	o_stream_cork(uidlist->output);
-	if (uidlist->hdr.used_file_size < sizeof(uidlist->hdr)) {
-		/* creating a new file, write a dummy header */
-		o_stream_seek(uidlist->output, 0);
-		o_stream_send(uidlist->output, &uidlist->hdr,
-			      sizeof(uidlist->hdr));
-	} else {
-		o_stream_seek(uidlist->output,
-			      uidlist->hdr.used_file_size);
-	}
-	return 0;
-}
-
-static int squat_uidlist_write_listbuf(struct squat_uidlist *uidlist,
-				       struct ostream *output)
-{
-	/* write size + buffer */
-	buffer_set_used_size(uidlist->tmp_buf, 0);
-	squat_trie_pack_num(uidlist->tmp_buf, uidlist->list_buf->used);
-
-	if (o_stream_send(output, uidlist->tmp_buf->data,
-			  uidlist->tmp_buf->used) < 0 ||
-	    o_stream_send(output, uidlist->list_buf->data,
-			  uidlist->list_buf->used) < 0) {
-		return -1;
-	}
-	return 0;
-}
 
-int squat_uidlist_finish_list(struct squat_uidlist *uidlist,
-			      uint32_t *_uid_list_idx)
-{
-	uint32_t uid_list_idx = *_uid_list_idx;
-	const struct uid_node *node;
-	uint32_t prev_uid, written_uid;
-
-	if ((uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) {
-		/* this is a single UID "list" */
-		return 0;
-	}
-	if (uid_list_idx < uidlist->first_new_list_idx) {
-		/* the list hasn't changed */
-		return 0;
-	}
+		ret = squat_uidlist_is_file_stale(uidlist);
+		if (ret == 0)
+			break;
 
-	uid_list_idx -= uidlist->first_new_list_idx;
-	if (uid_list_idx >= array_count(&uidlist->lists)) {
-		/* broken */
-		squat_trie_set_corrupted(uidlist->trie,
-					 "corrupted uidlist index (finishing)");
-		return -1;
-	}
+		file_unlock(&uidlist->file_lock);
+		if (ret < 0)
+			return -1;
 
-	/* write the uidlist into a buffer */
-	node = array_idx(&uidlist->lists, uid_list_idx);
-	buffer_set_used_size(uidlist->list_buf, 0);
-	if (squat_uidlist_write_range(uidlist, node,
-				      &prev_uid, &written_uid, 0) < 0) {
-		uidlist->write_failed = TRUE;
-		return -1;
-	}
-
-	if (uidlist->output == NULL) {
-		if (squat_uidlist_write_init(uidlist) < 0) {
-			uidlist->write_failed = TRUE;
+		squat_uidlist_close(uidlist);
+		uidlist->fd = open(uidlist->path, O_RDWR | O_CREAT, 0600);
+		if (uidlist->fd == -1) {
+			i_error("open(%s) failed: %m", uidlist->path);
 			return -1;
 		}
 	}
-
-	/* new uidlist index is the offset in uidlist file */
-	*_uid_list_idx = uidlist->output->offset;
-
-	if (squat_uidlist_write_listbuf(uidlist, uidlist->output) < 0)
-		uidlist->write_failed = TRUE;
-	return 0;
-}
-
-static void squat_uidlist_write_header(struct squat_uidlist *uidlist)
-{
-	uidlist->hdr.used_file_size = uidlist->output->offset;
-
-	o_stream_seek(uidlist->output, 0);
-	o_stream_send(uidlist->output, &uidlist->hdr, sizeof(uidlist->hdr));
+	return 1;
 }
 
-int squat_uidlist_flush(struct squat_uidlist *uidlist, uint32_t uid_validity)
+static int squat_uidlist_open_or_create(struct squat_uidlist *uidlist)
 {
-	int ret = uidlist->write_failed ? -1 : 0;
-
-	if (uidlist->output != NULL) {
-		if (ret == 0) {
-			uidlist->hdr.uidvalidity = uid_validity;
-			squat_uidlist_write_header(uidlist);
+	if (uidlist->fd == -1) {
+		uidlist->fd = open(uidlist->path, O_RDWR | O_CREAT, 0600);
+		if (uidlist->fd == -1) {
+			i_error("creat(%s) failed: %m", uidlist->path);
+			return -1;
 		}
-		o_stream_destroy(&uidlist->output);
 	}
-
-	array_clear(&uidlist->lists);
-	p_clear(uidlist->node_pool);
-	uidlist->node_pool_used = 0;
-	uidlist->write_failed = FALSE;
-	uidlist->current_uid = 0;
-
-	if (uidlist->fd != -1) {
-		if (squat_uidlist_map(uidlist) <= 0)
-			ret = -1;
-	}
-	return ret;
-}
-
-bool squat_uidlist_need_compress(struct squat_uidlist *uidlist,
-				 unsigned int current_message_count)
-{
-	uint32_t max_del_space, max_uid_del_count;
+	if (squat_uidlist_lock(uidlist) <= 0)
+		return -1;
 
-	if (uidlist->hdr.used_file_size >= UIDLIST_COMPRESS_MIN_SIZE) {
-		/* see if we've reached the max. deleted space in file */
-		max_del_space = uidlist->hdr.used_file_size / 100 *
-			UIDLIST_COMPRESS_PERCENTAGE;
-		if (uidlist->hdr.deleted_space > max_del_space)
-			return TRUE;
-	}
-	if (uidlist->hdr.uid_count > current_message_count) {
-		if (current_message_count == 0)
-			return TRUE;
-
-		max_uid_del_count = uidlist->hdr.uid_count *
-			UIDLIST_UID_COMPRESS_PERCENTAGE / 100;
-		if ((uidlist->hdr.uid_count - current_message_count) >
-		    max_uid_del_count)
-			return TRUE;
+	if (uidlist->locked_file_size != 0) {
+		if (squat_uidlist_map(uidlist, 0) < 0) {
+			/* broken file, truncate */
+			if (ftruncate(uidlist->fd, 0) < 0) {
+				i_error("ftruncate(%s) failed: %m",
+					uidlist->path);
+				return -1;
+			}
+			uidlist->locked_file_size = 0;
+		}
 	}
-	return FALSE;
-}
-
-int squat_uidlist_mark_having_expunges(struct squat_uidlist *uidlist,
-				       bool update_disk)
-{
-	uint8_t flag = 1;
-	size_t offset;
-
-	uidlist->check_expunges = TRUE;
-
-	if (update_disk) {
-		/* NOTE: we're writing this flag without locking */
-		offset = offsetof(struct squat_uidlist_header, uids_expunged);
-		if (pwrite_full(uidlist->fd, &flag, sizeof(flag), offset) < 0) {
-			squat_uidlist_set_syscall_error(uidlist,
-							"pwrite_full()");
+	if (uidlist->locked_file_size == 0) {
+		/* write using 0 until we're finished */
+		uidlist->hdr.indexid = 0;
+		uidlist->hdr.used_file_size = sizeof(uidlist->hdr);
+		if (write_full(uidlist->fd, &uidlist->hdr,
+			       sizeof(uidlist->hdr)) < 0) {
+			i_error("write(%s) failed: %m", uidlist->path);
 			return -1;
 		}
 	}
 	return 0;
 }
 
-struct squat_uidlist_compress_ctx *
-squat_uidlist_compress_begin(struct squat_uidlist *uidlist,
-			     const ARRAY_TYPE(seq_range) *existing_uids)
+void squat_uidlist_close(struct squat_uidlist *uidlist)
 {
-	struct squat_uidlist_compress_ctx *ctx;
-	int fd;
-
-	ctx = i_new(struct squat_uidlist_compress_ctx, 1);
-	ctx->uidlist = uidlist;
-	ctx->tmp_path = i_strconcat(uidlist->filepath, ".tmp", NULL);
-
-	if (existing_uids != NULL) {
-		ctx->node_pool = pool_alloconly_create("compress node pool",
-						       1024);
-		ctx->existing_uids = existing_uids;
-		i_array_init(&ctx->seen_uids,
-			     I_MIN(128, array_count(existing_uids)));
+	if (uidlist->file_lock != NULL)
+		file_lock_free(&uidlist->file_lock);
+	if (uidlist->mmap_size != 0) {
+		if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0)
+			i_error("munmap(%s) failed: %m", uidlist->path);
+		uidlist->mmap_size = 0;
 	}
-
-	fd = open(ctx->tmp_path, O_RDWR | O_CREAT | O_TRUNC, 0600);
-	if (fd == -1) {
-		ctx->failed = TRUE;
-		i_error("open(%s) failed: %m", ctx->tmp_path);
-	} else {
-		ctx->output = o_stream_create_fd_file(fd, 0, TRUE);
-		o_stream_send(ctx->output, &ctx->hdr, sizeof(ctx->hdr));
+	if (uidlist->output != NULL)
+		o_stream_unref(&uidlist->output);
+	if (uidlist->fd != -1) {
+		if (close(uidlist->fd) < 0)
+			i_error("close(%s) failed: %m", uidlist->path);
+		uidlist->fd = -1;
 	}
-
-	if (squat_uidlist_refresh(uidlist) < 0)
-		ctx->failed = TRUE;
-	return ctx;
+	uidlist->corrupted = FALSE;
 }
 
-static bool
-squat_uidlist_is_expunged(struct squat_uidlist_compress_ctx *ctx, uint32_t uid)
+int squat_uidlist_build_init(struct squat_uidlist *uidlist)
 {
-	if (ctx->existing_uids == NULL)
-		return FALSE;
-
-	return !seq_range_exists(ctx->existing_uids, uid);
-}
-
-static void
-squat_uidlist_compress_add_uid(struct squat_uidlist_compress_ctx *ctx,
-			       uint32_t uid)
-{
-	struct uid_node *node;
-
-	if (squat_uidlist_is_expunged(ctx, uid)) {
-		ctx->seen_expunged = TRUE;
-		return;
+	if (squat_uidlist_open_or_create(uidlist) < 0) {
+		if (uidlist->file_lock != NULL)
+			file_unlock(&uidlist->file_lock);
+		return -1;
+	}
+	if (lseek(uidlist->fd, uidlist->hdr.used_file_size, SEEK_SET) < 0) {
+		i_error("lseek(%s) failed: %m", uidlist->path);
+		if (uidlist->file_lock != NULL)
+			file_unlock(&uidlist->file_lock);
+		return -1;
 	}
 
-	if (!seq_range_exists(&ctx->seen_uids, uid)) {
-		if (uid > ctx->hdr.uid_max)
-			ctx->hdr.uid_max = uid;
-		ctx->hdr.uid_count++;
-		seq_range_array_add(&ctx->seen_uids, 0, uid);
+	uidlist->output = o_stream_create_fd(uidlist->fd, 0, FALSE);
+	if (uidlist->output->offset == 0) {
+		struct squat_uidlist_file_header hdr;
+
+		memset(&hdr, 0, sizeof(hdr));
+		o_stream_send(uidlist->output, &hdr, sizeof(hdr));
 	}
-
-	node = p_new(ctx->node_pool, struct uid_node, 1);
-	node->prev = ctx->last_node;
-	node->uid = uid;
-
-	ctx->last_node = node;
+	o_stream_cork(uidlist->output);
+	i_array_init(&uidlist->lists, 10240);
+	i_array_init(&uidlist->block_offsets, 128);
+	i_array_init(&uidlist->block_end_indexes, 128);
+	uidlist->list_start_idx = uidlist->hdr.count;
+	uidlist->build_hdr = uidlist->hdr;
+	return 0;
 }
 
 static int
-squat_uidlist_remove_expunged(struct squat_uidlist_compress_ctx *ctx,
-			      const uint8_t *data, size_t size,
-			      bool *all_expunged_r)
+uidlist_write_block_list_and_header(struct squat_uidlist *uidlist,
+				    struct ostream *output,
+				    ARRAY_TYPE(uint32_t) *block_offsets,
+				    ARRAY_TYPE(uint32_t) *block_end_indexes,
+				    bool write_old_blocks)
 {
-	const uint8_t *end;
-	uint32_t num, prev_uid, next_uid, written_uid;
+	unsigned int align, old_block_count, new_block_count;
+	uint32_t block_offset_count;
+	uoff_t block_list_offset;
+
+	align = output->offset % sizeof(uint32_t);
+	if (align != 0) {
+		static char null[sizeof(uint32_t)-1] = { 0, };
+
+		o_stream_send(output, null, sizeof(uint32_t) - align);
+	}
+	block_list_offset = output->offset;
+
+	new_block_count = array_count(block_offsets);
+	old_block_count = write_old_blocks ? uidlist->cur_block_count : 0;
 
-	end = data + size;
+	block_offset_count = new_block_count + old_block_count;
+	o_stream_send(output, &block_offset_count, sizeof(block_offset_count));
+	/* write end indexes */
+	o_stream_send(output, uidlist->cur_block_end_indexes,
+		      old_block_count * sizeof(uint32_t));
+	o_stream_send(output, array_idx(block_end_indexes, 0),
+		      new_block_count * sizeof(uint32_t));
+	/* write offsets */
+	o_stream_send(output, uidlist->cur_block_offsets,
+		      old_block_count * sizeof(uint32_t));
+	o_stream_send(output, array_idx(block_offsets, 0),
+		      new_block_count * sizeof(uint32_t));
 
-	p_clear(ctx->node_pool);
-	ctx->seen_expunged = FALSE;
-	ctx->last_node = NULL;
+	/* write header */
+	uidlist->build_hdr.indexid = uidlist->trie->hdr.indexid;
+	uidlist->build_hdr.block_list_offset = block_list_offset;
+	uidlist->build_hdr.used_file_size = output->offset;
+	uidlist->hdr = uidlist->build_hdr;
 
-	prev_uid = squat_trie_unpack_num(&data, end);
-	squat_uidlist_compress_add_uid(ctx, prev_uid);
+	o_stream_seek(output, 0);
+	o_stream_send(output, &uidlist->build_hdr, sizeof(uidlist->build_hdr));
+	o_stream_seek(output, uidlist->build_hdr.used_file_size);
+	o_stream_flush(output);
+	return 0;
+}
+
+static int squat_uidlist_build_flush(struct squat_uidlist *uidlist)
+{
+	struct uidlist_list *lists;
+	uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp;
+	unsigned int i, j, count, max;
+	uint32_t block_offset, block_end_idx, start_offset;
+	uint32_t list_sizes[UIDLIST_BLOCK_LIST_COUNT];
+	size_t mem_size;
+
+	if (uidlist->corrupted)
+		return -1;
+
+	lists = array_get_modifiable(&uidlist->lists, &count);
+	if (count == 0)
+		return 0;
 
-	while (data != end) {
-		num = squat_trie_unpack_num(&data, end);
-		next_uid = prev_uid + (num >> 1) + 1;
-		if ((num & 1) != 0) {
-			for (prev_uid++; prev_uid <= next_uid; prev_uid++)
-				squat_uidlist_compress_add_uid(ctx, prev_uid);
+	/* write the lists and save the written sizes to uid_list[0] */
+	for (i = 0; i < count; i += UIDLIST_BLOCK_LIST_COUNT) {
+		start_offset = uidlist->output->offset;
+		max = I_MIN(count - i, UIDLIST_BLOCK_LIST_COUNT);
+		for (j = 0; j < max; j++) {
+			list_sizes[j] = uidlist_write(uidlist->output,
+						      &lists[i+j], FALSE);
+		}
 
-			if (data == end)
-				break;
-			num = squat_trie_unpack_num(&data, end);
-			next_uid += num + 1;
+		block_offset = uidlist->output->offset;
+		block_end_idx = uidlist->list_start_idx + i + max;
+		array_append(&uidlist->block_offsets, &block_offset, 1);
+		array_append(&uidlist->block_end_indexes, &block_end_idx, 1);
+
+		/* write the full size of the uidlists */
+		bufp = buf;
+		squat_pack_num(&bufp, block_offset - start_offset);
+		o_stream_send(uidlist->output, buf, bufp - buf);
+
+		/* write the sizes/flags */
+		for (j = 0; j < max; j++) {
+			bufp = buf;
+			squat_pack_num(&bufp, list_sizes[j]);
+			o_stream_send(uidlist->output, buf, bufp - buf);
 		}
-		squat_uidlist_compress_add_uid(ctx, next_uid);
-		prev_uid = next_uid;
 	}
 
-	if (!ctx->seen_expunged) {
-		/* no changes */
-		return 0;
-	}
-	if (ctx->last_node == NULL) {
-		/* everything expunged */
-		*all_expunged_r = TRUE;
-		return 1;
+	mem_size = uidlist->lists.arr.buffer->used +
+		uidlist->block_offsets.arr.buffer->used +
+		uidlist->block_end_indexes.arr.buffer->used;
+	if (uidlist->max_size < mem_size)
+		uidlist->max_size = mem_size;
+
+	uidlist->list_start_idx += count;
+	array_clear(&uidlist->lists);
+
+	if (uidlist_write_block_list_and_header(uidlist, uidlist->output,
+						&uidlist->block_offsets,
+						&uidlist->block_end_indexes,
+						TRUE) < 0)
+		return -1;
+	if (uidlist->output->last_failed_errno != 0) {
+		errno = uidlist->output->last_failed_errno;
+		i_error("write() to %s failed: %m", uidlist->path);
+		return -1;
 	}
 
-	/* recreate the list and write it */
-	buffer_set_used_size(ctx->uidlist->list_buf, 0);
-	if (squat_uidlist_write_range(ctx->uidlist, ctx->last_node,
-				      &prev_uid, &written_uid, 0) < 0)
+	(void)squat_uidlist_map(uidlist, (uoff_t)-1);
+
+	array_clear(&uidlist->block_offsets);
+	array_clear(&uidlist->block_end_indexes);
+	return 0;
+}
+
+int squat_uidlist_build_deinit(struct squat_uidlist *uidlist)
+{
+	int ret;
+
+	ret = squat_uidlist_build_flush(uidlist);
+	file_unlock(&uidlist->file_lock);
+	return ret;
+}
+
+int squat_uidlist_rebuild_init(struct squat_uidlist *uidlist, bool finish,
+			       struct squat_uidlist_rebuild_context **ctx_r)
+{
+	struct squat_uidlist_rebuild_context *ctx;
+	struct squat_uidlist_file_header hdr;
+	const char *temp_path;
+	int fd;
+
+	if (uidlist->hdr.link_count == 0)
+		return 0;
+
+	if (!finish) {
+		if (uidlist->hdr.link_count < uidlist->hdr.count*2/3)
+			return 0;
+	}
+
+	temp_path = t_strconcat(uidlist->path, ".tmp", NULL);
+	fd = open(temp_path, O_RDWR | O_TRUNC | O_CREAT, 0600);
+	if (fd < 0) {
+		i_error("open(%s) failed: %m", temp_path);
 		return -1;
-	if (squat_uidlist_write_listbuf(ctx->uidlist, ctx->output) < 0)
-		return -1;
-	*all_expunged_r = FALSE;
+	}
+
+	ctx = i_new(struct squat_uidlist_rebuild_context, 1);
+	ctx->uidlist = uidlist;
+	ctx->fd = fd;
+	ctx->output = o_stream_create_fd(ctx->fd, 0, FALSE);
+	o_stream_cork(ctx->output);
+
+	memset(&hdr, 0, sizeof(hdr));
+	o_stream_send(ctx->output, &hdr, sizeof(hdr));
+
+	ctx->cur_block_start_offset = ctx->output->offset;
+	i_array_init(&ctx->new_block_offsets,
+		     uidlist->build_hdr.count / UIDLIST_BLOCK_LIST_COUNT);
+	i_array_init(&ctx->new_block_end_indexes,
+		     uidlist->build_hdr.count / UIDLIST_BLOCK_LIST_COUNT);
+	*ctx_r = ctx;
 	return 1;
 }
 
-int squat_uidlist_compress_next(struct squat_uidlist_compress_ctx *ctx,
-				uint32_t *uid_list_idx)
+static void
+uidlist_rebuild_flush_block(struct squat_uidlist_rebuild_context *ctx)
+{
+	uint8_t buf[SQUAT_PACK_MAX_SIZE], *bufp;
+	uint32_t block_offset, block_end_idx;
+	unsigned int i;
+
+	ctx->new_count += ctx->list_idx;
+
+	block_offset = ctx->output->offset;
+	block_end_idx = ctx->new_count;
+	array_append(&ctx->new_block_offsets, &block_offset, 1);
+	array_append(&ctx->new_block_end_indexes, &block_end_idx, 1);
+
+	/* this block's contents started from cur_block_start_offset and
+	   ended to current offset. write the size of this area. */
+	bufp = buf;
+	squat_pack_num(&bufp, block_offset - ctx->cur_block_start_offset);
+	o_stream_send(ctx->output, buf, bufp - buf);
+
+	/* write the sizes/flags */
+	for (i = 0; i < ctx->list_idx; i++) {
+		bufp = buf;
+		squat_pack_num(&bufp, ctx->list_sizes[i]);
+		o_stream_send(ctx->output, buf, bufp - buf);
+	}
+	ctx->cur_block_start_offset = ctx->output->offset;
+}
+
+void squat_uidlist_rebuild_next(struct squat_uidlist_rebuild_context *ctx,
+				const ARRAY_TYPE(uint32_t) *uids)
+{
+	ctx->list_sizes[ctx->list_idx] =
+		uidlist_write_array(ctx->output, array_idx(uids, 0),
+				    array_count(uids), 0, 0, FALSE);
+	if (++ctx->list_idx == UIDLIST_BLOCK_LIST_COUNT) {
+		uidlist_rebuild_flush_block(ctx);
+		ctx->list_idx = 0;
+	}
+}
+
+int squat_uidlist_rebuild_finish(struct squat_uidlist_rebuild_context *ctx,
+				 bool cancel)
 {
 	struct squat_uidlist *uidlist = ctx->uidlist;
-	const uint8_t *data, *data_start;
-	uint32_t size, old_offset;
-	int ret;
-
-	if ((*uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) {
-		uint32_t uid = *uid_list_idx & ~UID_LIST_IDX_FLAG_SINGLE;
+	const char *temp_path;
+	int ret = 1;
 
-		if (ctx->uidlist->check_expunges) {
-			if (squat_uidlist_is_expunged(ctx, uid))
-				return 0;
-		}
-		return 1;
-	}
+	if (ctx->list_idx != 0)
+		uidlist_rebuild_flush_block(ctx);
+	if (array_count(&ctx->new_block_end_indexes) == 0 || cancel)
+		ret = 0;
 
-	if (ctx->output == NULL)
-		return -1;
-
-	if (squat_uidlist_map_list(uidlist, *uid_list_idx, &data, &size) < 0) {
-		squat_trie_set_corrupted(uidlist->trie,
-			"corrupted uidlist index (compressing)");
-		return -1;
-	}
+	temp_path = t_strconcat(ctx->uidlist->path, ".tmp", NULL);
+	squat_uidlist_close(ctx->uidlist);
 
-	old_offset = *uid_list_idx;
-	*uid_list_idx = ctx->output->offset;
-
-	if (!ctx->uidlist->check_expunges)
-		ret = 0;
-	else {
-		bool all_expunged;
-
-		ret = squat_uidlist_remove_expunged(ctx, data, size,
-						    &all_expunged);
-		if (ret < 0) {
-			ctx->failed = TRUE;
-			return -1;
-		}
-		if (ret > 0 && all_expunged)
-			return 0;
-	}
-
-	if (ret == 0) {
-		data_start = data = uidlist->const_mmap_base + old_offset;
-		(void)squat_trie_unpack_num(&data, NULL);
-
-		if (o_stream_send(ctx->output, data_start,
-				  data - data_start + size) < 0) {
-			ctx->failed = TRUE;
-			return -1;
+	if (ret > 0) {
+		uidlist->build_hdr.count = ctx->new_count;
+		uidlist->build_hdr.link_count = 0;
+		uidlist_write_block_list_and_header(uidlist, ctx->output,
+						    &ctx->new_block_offsets,
+						    &ctx->new_block_end_indexes,
+						    FALSE);
+		if (ctx->output->last_failed_errno != 0) {
+			errno = ctx->output->last_failed_errno;
+			i_error("write() to %s failed: %m", temp_path);
+			ret = -1;
+		} else if (rename(temp_path, uidlist->path) < 0) {
+			i_error("rename(%s, %s) failed: %m",
+				temp_path, uidlist->path);
+			ret = -1;
 		}
 	}
 
-	ctx->hdr.node_count++;
-	return 1;
+	if (ret <= 0) {
+		o_stream_unref(&ctx->output);
+		if (close(ctx->fd) < 0)
+			i_error("close(%s) failed: %m", temp_path);
+		if (unlink(temp_path) < 0)
+			i_error("unlink(%s) failed: %m", temp_path);
+	} else {
+		array_clear(&uidlist->block_offsets);
+		array_clear(&uidlist->block_end_indexes);
+		uidlist->fd = ctx->fd;
+		uidlist->output = ctx->output;
+		uidlist->list_start_idx = ctx->new_count;
+
+		i_assert(array_count(&uidlist->lists) == 0);
+		i_assert(uidlist->mmap_size == 0);
+
+		(void)squat_uidlist_map(uidlist, (uoff_t)-1);
+	}
+	array_free(&ctx->new_block_offsets);
+	array_free(&ctx->new_block_end_indexes);
+	i_free(ctx);
+	return ret < 0 ? -1 : 0;
 }
 
-void squat_uidlist_compress_rollback(struct squat_uidlist_compress_ctx **_ctx)
+static int uidlist_rebuild(struct squat_uidlist *uidlist)
 {
-	struct squat_uidlist_compress_ctx *ctx = *_ctx;
-
-	*_ctx = NULL;
-
-	if (ctx->node_pool != NULL)
-		pool_unref(&ctx->node_pool);
-	if (array_is_created(&ctx->seen_uids))
-		array_free(&ctx->seen_uids);
-	if (ctx->output != NULL) {
-		if (ctx->failed)
-			(void)unlink(ctx->tmp_path);
-		o_stream_destroy(&ctx->output);
-	}
-	i_free(ctx->tmp_path);
-	i_free(ctx);
-}
-
-int squat_uidlist_compress_commit(struct squat_uidlist_compress_ctx **_ctx)
-{
-	struct squat_uidlist_compress_ctx *ctx = *_ctx;
+	struct squat_uidlist_rebuild_context *ctx;
+	unsigned int i;
+	ARRAY_TYPE(uint32_t) uids;
 	int ret = 0;
 
-	if (ctx->failed) {
-		squat_uidlist_compress_rollback(_ctx);
+	if (uidlist->hdr.link_count == 0)
+		return 0;
+
+	if (squat_uidlist_rebuild_init(uidlist, TRUE, &ctx) < 0)
 		return -1;
+
+	i_array_init(&uids, 1024);
+	for (i = 0; i < uidlist->hdr.count; i++) {
+		array_clear(&uids);
+		if (squat_uidlist_get(uidlist, (i + 0x100) << 1, &uids) < 0) {
+			ret = -1;
+			break;
+		}
+		squat_uidlist_rebuild_next(ctx, &uids);
 	}
+	array_free(&uids);
+
+	return squat_uidlist_rebuild_finish(ctx, ret < 0);
+}
+
+static void
+uidlist_flush(struct squat_uidlist *uidlist, struct uidlist_list *list,
+	      uint32_t uid)
+{
+	uint32_t offset = uidlist->output->offset;
 
-	/* write the header */
-	ctx->hdr.uidvalidity = ctx->uidlist->uidvalidity;
-	ctx->hdr.used_file_size = ctx->output->offset;
+	uidlist->build_hdr.link_count++;
+	uidlist_write(uidlist->output, list, TRUE);
+
+	list->uid_count = 2;
+	list->uid_begins_with_pointer = TRUE;
+
+	list->uid_list[0] = offset;
+	list->uid_list[1] = uid;
+}
 
-	if (ctx->existing_uids == NULL) {
-		ctx->hdr.uid_max = ctx->uidlist->hdr.uid_max;
-		ctx->hdr.uid_count = ctx->uidlist->hdr.uid_count;
-	}
+static struct uidlist_list *
+uidlist_add_new(struct squat_uidlist *uidlist, unsigned int count,
+		uint32_t *uid_list_idx_r)
+{
+	struct uidlist_list *list;
+
+	i_assert(array_count(&uidlist->lists) +
+		 uidlist->list_start_idx == uidlist->build_hdr.count);
+	*uid_list_idx_r = (uidlist->build_hdr.count + 0x100) << 1;
+	list = array_append_space(&uidlist->lists);
+	uidlist->build_hdr.count++;
+
+	list->uid_count = count;
+	return list;
+}
 
-	o_stream_seek(ctx->output, 0);
-	if (o_stream_send(ctx->output, &ctx->hdr, sizeof(ctx->hdr)) < 0)
-		ret = -1;
+uint32_t squat_uidlist_build_add_uid(struct squat_uidlist *uidlist,
+				     uint32_t uid_list_idx, uint32_t uid)
+{
+	struct uidlist_list *list;
+	unsigned int idx, mask;
+	uint32_t *p;
+
+	if ((uid_list_idx & 1) != 0) {
+		/* adding second UID */
+		uint32_t prev_uid = uid_list_idx >> 1;
+
+		i_assert(prev_uid != uid);
+		list = uidlist_add_new(uidlist, 2, &uid_list_idx);
+		list->uid_list[0] = prev_uid;
+		if (prev_uid + 1 == uid)
+			list->uid_list[0] |= UID_LIST_MASK_RANGE;
+		list->uid_list[1] = uid;
+		return uid_list_idx;
+	} else if (uid_list_idx < (0x100 << 1)) {
+		uint32_t old_list_idx;
 
-	if (ret == 0) {
-		if (rename(ctx->tmp_path, ctx->uidlist->filepath) < 0) {
-			i_error("rename(%s, %s) failed: %m",
-				ctx->tmp_path, ctx->uidlist->filepath);
-			ret = -1;
-		} else {
-			/* reopen */
-			ctx->uidlist->check_expunges = FALSE;
-			squat_uidlist_close(ctx->uidlist);
-			(void)squat_uidlist_open(ctx->uidlist);
+		if (uid < 8) {
+			/* UID lists containing only UIDs 0-7 are saved as
+			   uidlist values 2..511. think of it as a bitmask. */
+			uid_list_idx |= 1 << (uid + 1);
+			i_assert((uid_list_idx & 1) == 0);
+			return uid_list_idx;
+		}
+
+		if (uid_list_idx == 0) {
+			/* first UID */
+			return (uid << 1) | 1;
+		}
+
+		/* create a new list */
+		old_list_idx = uid_list_idx >> 1;
+		list = uidlist_add_new(uidlist, 1, &uid_list_idx);
+		/* add the first UID ourself */
+		idx = 0;
+		i_assert((old_list_idx & 0xff) != 0);
+		for (mask = 1; mask <= 128; mask <<= 1, idx++) {
+			if ((old_list_idx & mask) != 0) {
+				list->uid_list[0] = idx;
+				idx++; mask <<= 1;
+				break;
+			}
+		}
+		for (; mask <= 128; mask <<= 1, idx++) {
+			if ((old_list_idx & mask) != 0) {
+				squat_uidlist_build_add_uid(uidlist,
+							    uid_list_idx, idx);
+			}
 		}
 	}
 
-	if (ret < 0)
-		ctx->failed = TRUE;
+	/* add to existing list */
+	idx = (uid_list_idx >> 1) - 0x100;
+	if (idx < uidlist->list_start_idx) {
+		list = uidlist_add_new(uidlist, 2, &uid_list_idx);
+		list->uid_list[0] = UID_LIST_POINTER_MASK_LIST_IDX | idx;
+		list->uid_list[1] = uid;
+		list->uid_begins_with_pointer = TRUE;
+		uidlist->build_hdr.link_count++;
+		return uid_list_idx;
+	}
+
+	idx -= uidlist->list_start_idx;
+	if (idx >= array_count(&uidlist->lists)) {
+		squat_uidlist_set_corrupted(uidlist, "missing/broken uidlist");
+		return 0;
+	}
+	list = array_idx_modifiable(&uidlist->lists, idx);
+	i_assert(list->uid_count > 0);
 
-	squat_uidlist_compress_rollback(_ctx);
-	return ret;
+	p = &list->uid_list[list->uid_count-1];
+	i_assert(uid != *p || uidlist->corrupted ||
+		 (list->uid_count == 1 && list->uid_begins_with_pointer));
+	if (uid == *p + 1 &&
+	    (list->uid_count > 1 || !list->uid_begins_with_pointer)) {
+		/* use a range */
+		if (list->uid_count > 1 && (p[-1] & UID_LIST_MASK_RANGE) != 0 &&
+		   (list->uid_count > 2 || !list->uid_begins_with_pointer)) {
+			/* increase the existing range */
+			*p += 1;
+			return uid_list_idx;
+		}
+
+		if (list->uid_count == UIDLIST_LIST_SIZE) {
+			uidlist_flush(uidlist, list, uid);
+			return uid_list_idx;
+		}
+		/* create a new range */
+		*p |= UID_LIST_MASK_RANGE;
+	}
+
+	if (list->uid_count == UIDLIST_LIST_SIZE) {
+		uidlist_flush(uidlist, list, uid);
+		return uid_list_idx;
+	}
+
+	p++;
+	list->uid_count++;
+
+	*p = uid;
+	return uid_list_idx;
 }
 
-static void
-squat_uidlist_get_add_uid(struct squat_uidlist_get_context *ctx, uint32_t uid)
+static void uidlist_array_append(ARRAY_TYPE(uint32_t) *uids, uint32_t uid)
 {
-	if (ctx->filter_uid_pos == 0) {
-		seq_range_array_add(ctx->result, 0, uid);
+	uint32_t *uidlist;
+	unsigned int count;
+
+	uidlist = array_get_modifiable(uids, &count);
+	if (count == 0) {
+		array_append(uids, &uid, 1);
 		return;
 	}
+	if (uidlist[count-1] + 1 == uid) {
+		if (count > 1 && (uidlist[count-2] &
+				  UID_LIST_MASK_RANGE) != 0) {
+			uidlist[count-1]++;
+			return;
+		}
+		uidlist[count-1] |= UID_LIST_MASK_RANGE;
+	}
+	array_append(uids, &uid, 1);
+}
 
-	if (ctx->filter_uid_pos < uid) {
-		seq_range_array_remove_range(ctx->result,
-					     ctx->filter_uid_pos, uid-1);
+static void uidlist_array_append_range(ARRAY_TYPE(uint32_t) *uids,
+				       uint32_t uid1, uint32_t uid2)
+{
+	uint32_t *uidlist;
+	unsigned int count;
+
+	i_assert(uid1 < uid2);
+
+	uidlist = array_get_modifiable(uids, &count);
+	if (count == 0) {
+		uid1 |= UID_LIST_MASK_RANGE;
+		array_append(uids, &uid1, 1);
+		array_append(uids, &uid2, 1);
+		return;
 	}
-	ctx->filter_uid_pos = uid+1;
+	if (uidlist[count-1] + 1 == uid1) {
+		if (count > 1 && (uidlist[count-2] &
+				  UID_LIST_MASK_RANGE) != 0) {
+			uidlist[count-1] = uid2;
+			return;
+		}
+		uidlist[count-1] |= UID_LIST_MASK_RANGE;
+	} else {
+		uid1 |= UID_LIST_MASK_RANGE;
+		array_append(uids, &uid1, 1);
+	}
+	array_append(uids, &uid2, 1);
 }
 
 static int
-squat_uidlist_get_range_list(struct squat_uidlist_get_context *ctx,
-			     size_t offset)
+node_uidlist_get_at_offset(struct squat_uidlist *uidlist, uoff_t offset,
+			   uint32_t num, ARRAY_TYPE(uint32_t) *uids)
 {
-	const uint8_t *data, *end;
-	uint32_t size, num, prev_uid, next_uid;
+	const uint8_t *p, *end;
+	uint32_t size, base_uid;
+	unsigned int i, j, extra = 0;
+
+	if (squat_uidlist_map(uidlist, offset) < 0)
+		return -1;
+	p = CONST_PTR_OFFSET(uidlist->mmap_base, offset);
+	end = CONST_PTR_OFFSET(uidlist->mmap_base, uidlist->mmap_size);
 
-	if (squat_uidlist_map_list(ctx->uidlist, offset, &data, &size) < 0)
+	if (num == 0) {
+		/* not given, read it */
+		num = squat_unpack_num(&p, end);
+	}
+	size = num >> 2;
+	if (p + size > end) {
+		squat_uidlist_set_corrupted(uidlist,
+					    "size points outside file");
 		return -1;
-	end = data + size;
+	}
+	end = p + size;
 
-	prev_uid = squat_trie_unpack_num(&data, end);
-	squat_uidlist_get_add_uid(ctx, prev_uid);
+	if ((num & UIDLIST_PACKED_FLAG_BEGINS_WITH_POINTER) != 0) {
+		/* link to the file */
+		uint32_t prev = squat_unpack_num(&p, end);
 
-	while (data != end) {
-		num = squat_trie_unpack_num(&data, end);
-		next_uid = prev_uid + (num >> 1) + 1;
-		if ((num & 1) != 0) {
-			for (prev_uid++; prev_uid <= next_uid; prev_uid++)
-				squat_uidlist_get_add_uid(ctx, prev_uid);
+		if ((prev & 1) != 0) {
+			/* pointer to uidlist */
+			prev = ((prev >> 1) + 0x100) << 1;
+			if (squat_uidlist_get(uidlist, prev, uids) < 0)
+				return -1;
+		} else {
+			prev = offset - (prev >> 1);
+			if (node_uidlist_get_at_offset(uidlist, prev,
+						       0, uids) < 0)
+				return -1;
+		}
+	}
+
+	if ((num & UIDLIST_PACKED_FLAG_BITMASK) != 0) {
+		/* bitmask */
+		base_uid = squat_unpack_num(&p, end);
+		size = end - p;
 
-			if (data == end)
-				break;
-			num = squat_trie_unpack_num(&data, end);
-			next_uid += num + 1;
+		uidlist_array_append(uids, base_uid++);
+		for (i = 0; i < size; i++) {
+			for (j = 0; j < 8; j++, base_uid++) {
+				if ((p[i] & (1 << j)) != 0)
+					uidlist_array_append(uids, base_uid);
+			}
 		}
-		squat_uidlist_get_add_uid(ctx, next_uid);
-		prev_uid = next_uid;
+	} else {
+		/* range */
+		base_uid = 0;
+		while (p < end) {
+			num = squat_unpack_num(&p, end);
+			base_uid += (num >> 1) + extra;
+			if ((num & 1) == 0) {
+				uidlist_array_append(uids, base_uid);
+			} else {
+				/* range */
+				uint32_t seq1 = base_uid;
+				base_uid += squat_unpack_num(&p, end) + 1;
+				uidlist_array_append_range(uids, seq1,
+							   base_uid);
+			}
+			extra = 1;
+		}
 	}
 	return 0;
 }
 
-static int
-squat_uidlist_get_ctx(struct squat_uidlist_get_context *ctx,
-		      uint32_t uid_list_idx)
+static int uint32_cmp(const void *key, const void *data)
 {
-	if ((uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) {
-		uint32_t uid = uid_list_idx & ~UID_LIST_IDX_FLAG_SINGLE;
-		squat_uidlist_get_add_uid(ctx, uid);
-		return 0;
-	}
+	const uint32_t *i1 = key, *i2 = data;
 
-	return squat_uidlist_get_range_list(ctx, uid_list_idx);
+	return (int)*i1 - (int)*i2;
 }
 
-int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
-		      ARRAY_TYPE(seq_range) *result)
-{
-	struct squat_uidlist_get_context ctx;
-
-	memset(&ctx, 0, sizeof(ctx));
-	ctx.uidlist = uidlist;
-	ctx.result = result;
-
-	return squat_uidlist_get_ctx(&ctx, uid_list_idx);
-}
-
-int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
-			 ARRAY_TYPE(seq_range) *result)
+static int
+node_uidlist_get_offset(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+			uint32_t *offset_r, uint32_t *num_r)
 {
-	struct squat_uidlist_get_context ctx;
-	const struct seq_range *range;
-	unsigned int count;
+	const uint8_t *p, *end;
+	unsigned int idx;
+	uint32_t num, skip_bytes, uidlists_offset;
+
+	if (bsearch_insert_pos(&uid_list_idx, uidlist->cur_block_end_indexes,
+			       uidlist->cur_block_count,
+			       sizeof(uint32_t), uint32_cmp, &idx))
+		idx++;
+	if (unlikely(idx == uidlist->cur_block_count)) {
+		squat_uidlist_set_corrupted(uidlist, "uidlist not found");
+		return -1;
+	}
+	if (unlikely(idx > 0 &&
+		     uidlist->cur_block_end_indexes[idx-1] > uid_list_idx)) {
+		squat_uidlist_set_corrupted(uidlist, "broken block list");
+		return -1;
+	}
 
-	memset(&ctx, 0, sizeof(ctx));
-	ctx.uidlist = uidlist;
-	ctx.result = result;
-	ctx.filter_uid_pos = 1;
+	/* find the uidlist inside the block */
+	p = CONST_PTR_OFFSET(uidlist->mmap_base,
+			     uidlist->cur_block_offsets[idx]);
+	end = CONST_PTR_OFFSET(uidlist->mmap_base, uidlist->mmap_size);
 
-	if (squat_uidlist_get_ctx(&ctx, uid_list_idx) < 0)
-		return -1;
+	uidlists_offset = uidlist->cur_block_offsets[idx] -
+		squat_unpack_num(&p, end);
+	uid_list_idx -= idx == 0 ? 0 : uidlist->cur_block_end_indexes[idx-1];
+	for (skip_bytes = 0; uid_list_idx > 0; uid_list_idx--) {
+		num = squat_unpack_num(&p, end);
+		skip_bytes += num >> 2;
+	}
+	*offset_r = uidlists_offset + skip_bytes;
+	*num_r = squat_unpack_num(&p, end);
 
-	range = array_get(ctx.result, &count);
-	if (count > 0) {
-		seq_range_array_remove_range(result, ctx.filter_uid_pos,
-					     range[count-1].seq2);
+	if (unlikely(*offset_r > uidlist->mmap_size)) {
+		squat_uidlist_set_corrupted(uidlist, "broken offset");
+		return -1;
 	}
 	return 0;
 }
 
-bool squat_uidlist_want_flush(struct squat_uidlist *uidlist)
+int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+		      ARRAY_TYPE(uint32_t) *uids)
+{
+	unsigned int mask;
+	uint32_t uid, offset, num;
+
+	if ((uid_list_idx & 1) != 0) {
+		/* single UID */
+		uid = uid_list_idx >> 1;
+		uidlist_array_append(uids, uid);
+		return 0;
+	} else if (uid_list_idx < (0x100 << 1)) {
+		/* bitmask */
+		for (uid = 0, mask = 2; mask <= 256; mask <<= 1, uid++) {
+			if ((uid_list_idx & mask) != 0)
+				uidlist_array_append(uids, uid);
+		}
+		return 0;
+	}
+
+	uid_list_idx = (uid_list_idx >> 1) - 0x100;
+	if (node_uidlist_get_offset(uidlist, uid_list_idx, &offset, &num) < 0)
+		return -1;
+	return node_uidlist_get_at_offset(uidlist, offset, num, uids);
+}
+
+uint32_t squat_uidlist_singleton_last_uid(uint32_t uid_list_idx)
+{
+	unsigned int idx, mask;
+
+	if ((uid_list_idx & 1) != 0) {
+		/* single UID */
+		return uid_list_idx >> 1;
+	} else if (uid_list_idx < (0x100 << 1)) {
+		/* bitmask */
+		if (uid_list_idx == 2) {
+			/* just a quick optimization */
+			return 0;
+		}
+		for (idx = 7, mask = 256; mask > 2; mask >>= 1, idx--) {
+			if ((uid_list_idx & mask) != 0)
+				return idx;
+		}
+	}
+
+	i_unreached();
+	return 0;
+}
+
+int squat_uidlist_get_seqrange(struct squat_uidlist *uidlist,
+			       uint32_t uid_list_idx,
+			       ARRAY_TYPE(seq_range) *seq_range_arr)
 {
-	return uidlist->node_pool_used >= SQUAT_UIDLIST_FLUSH_THRESHOLD;
+	ARRAY_TYPE(uint32_t) tmp_uid_arr;
+	struct seq_range range;
+	const uint32_t *tmp_uids;
+	unsigned int i, count;
+
+	t_push();
+	t_array_init(&tmp_uid_arr, 128);
+	if (squat_uidlist_get(uidlist, uid_list_idx, &tmp_uid_arr) < 0) {
+		t_pop();
+		return -1;
+	}
+
+	tmp_uids = array_get(&tmp_uid_arr, &count);
+	for (i = 0; i < count; i++) {
+		if ((tmp_uids[i] & UID_LIST_MASK_RANGE) == 0)
+			range.seq1 = range.seq2 = tmp_uids[i];
+		else {
+			range.seq1 = tmp_uids[i] & ~UID_LIST_MASK_RANGE;
+			range.seq2 = tmp_uids[++i];
+		}
+		array_append(seq_range_arr, &range, 1);
+	}
+	t_pop();
+	return 0;
+}
+
+int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+			 ARRAY_TYPE(seq_range) *uids)
+{
+	const struct seq_range *parent_range;
+	ARRAY_TYPE(seq_range) dest_uids;
+	ARRAY_TYPE(uint32_t) relative_uids;
+	const uint32_t *rel_range;
+	unsigned int i, rel_count, parent_idx, parent_count, diff, parent_uid;
+	uint32_t prev_seq, seq1, seq2;
+
+	parent_range = array_get(uids, &parent_count);
+	if (parent_count == 0)
+		return 0;
+
+	i_array_init(&relative_uids, 128);
+	i_array_init(&dest_uids, 128);
+	squat_uidlist_get(uidlist, uid_list_idx, &relative_uids);
+
+	parent_idx = 0;
+	rel_range = array_get(&relative_uids, &rel_count);
+	prev_seq = 0; parent_uid = parent_range[0].seq1;
+	for (i = 0; i < rel_count; i++) {
+		if (unlikely(parent_uid == (uint32_t)-1)) {
+			i_error("broken UID ranges");
+			return -1;
+		}
+		if ((rel_range[i] & UID_LIST_MASK_RANGE) == 0)
+			seq1 = seq2 = rel_range[i];
+		else {
+			seq1 = (rel_range[i] & ~UID_LIST_MASK_RANGE);
+			seq2 = rel_range[++i];
+		}
+		i_assert(seq1 >= prev_seq);
+		diff = seq1 - prev_seq;
+		while (diff > 0) {
+			if (unlikely(parent_uid == (uint32_t)-1)) {
+				i_error("broken UID ranges");
+				return -1;
+			}
+
+			for (; parent_idx < parent_count; parent_idx++) {
+				if (parent_range[parent_idx].seq2 <= parent_uid)
+					continue;
+				if (parent_uid < parent_range[parent_idx].seq1)
+					parent_uid = parent_range[parent_idx].seq1;
+				else
+					parent_uid++;
+				break;
+			}
+			diff--;
+		}
+		diff = seq2 - seq1 + 1;
+		while (diff > 0) {
+			if (unlikely(parent_uid == (uint32_t)-1)) {
+				i_error("broken UID ranges");
+				return -1;
+			}
+			seq_range_array_add(&dest_uids, 0, parent_uid);
+			for (; parent_idx < parent_count; parent_idx++) {
+				if (parent_range[parent_idx].seq2 <= parent_uid)
+					continue;
+				if (parent_uid < parent_range[parent_idx].seq1)
+					parent_uid = parent_range[parent_idx].seq1;
+				else
+					parent_uid++;
+				break;
+			}
+			diff--;
+		}
+
+		prev_seq = seq2 + 1;
+	}
+
+	buffer_set_used_size(uids->arr.buffer, 0);
+	array_append_array(uids, &dest_uids);
+
+	array_free(&relative_uids);
+	array_free(&dest_uids);
+	return 0;
 }
 
 size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist,
 			      unsigned int *count_r)
 {
-	*count_r = uidlist->hdr.node_count;
-
-	return uidlist->hdr.used_file_size;
+	*count_r = uidlist->hdr.count;
+	return uidlist->max_size;
 }
--- a/src/plugins/fts-squat/squat-uidlist.h	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts-squat/squat-uidlist.h	Sun Dec 02 23:51:46 2007 +0200
@@ -1,63 +1,64 @@
 #ifndef SQUAT_UIDLIST_H
 #define SQUAT_UIDLIST_H
 
-#include "seq-range-array.h"
+struct squat_trie;
+struct squat_uidlist_rebuild_context;
+
+struct squat_uidlist_file_header {
+	uint32_t indexid;
+	uint32_t used_file_size;
+	uint32_t block_list_offset;
+	uint32_t count, link_count;
+};
+
+/*
+   uidlist file:
+
+   struct uidlist_header;
 
-struct squat_trie;
-struct squat_uidlist;
+   // size includes both prev_offset and uidlist
+   packed (size << 2) | packed_flags; // UIDLIST_PACKED_FLAG_*
+   [packed prev_offset;] // If UIDLIST_PACKED_FLAG_BEGINS_WITH_OFFSET is set
+   if (UIDLIST_PACKED_FLAG_BITMASK) {
+     packed base_uid; // first UID in uidlist
+     uint8_t bitmask[]; // first bit is base_uid+1
+   } else {
+     // FIXME: packed range
+   }
+*/
 
-struct squat_uidlist *
-squat_uidlist_init(struct squat_trie *trie, const char *path,
-		   uint32_t uidvalidity, bool mmap_disable);
+#define UIDLIST_IS_SINGLETON(idx) \
+	(((idx) & 1) != 0 || (idx) < (0x100 << 1))
+
+struct squat_uidlist *squat_uidlist_init(struct squat_trie *trie);
 void squat_uidlist_deinit(struct squat_uidlist *uidlist);
 
-/* Make sure that we've the latest uidlist file fully mapped. */
-int squat_uidlist_refresh(struct squat_uidlist *uidlist);
+int squat_uidlist_open(struct squat_uidlist *uidlist);
+void squat_uidlist_close(struct squat_uidlist *uidlist);
 
-/* Get the last UID added to the file. */
-int squat_uidlist_get_last_uid(struct squat_uidlist *uidlist, uint32_t *uid_r);
+int squat_uidlist_build_init(struct squat_uidlist *uidlist);
+uint32_t squat_uidlist_build_add_uid(struct squat_uidlist *uidlist,
+				     uint32_t uid_list_idx, uint32_t uid);
+int squat_uidlist_build_deinit(struct squat_uidlist *uidlist);
 
-/* Add new UID to given UID list. The uid_list_idx is updated to contain the
-   new list index. It must be put through _finish_list() before it's actually
-   written to disk. */
-int squat_uidlist_add(struct squat_uidlist *uidlist, uint32_t *uid_list_idx,
-		      uint32_t uid);
-/* Write UID list into disk. The uid_list_idx is updated to contain the new
-   permanent index for it. */
-int squat_uidlist_finish_list(struct squat_uidlist *uidlist,
-			      uint32_t *uid_list_idx);
-int squat_uidlist_flush(struct squat_uidlist *uidlist, uint32_t uid_validity);
-/* Returns TRUE if uidlist should be compressed. current_message_count can be
-   (unsigned int)-1 if you don't want include it in the check. */
-bool squat_uidlist_need_compress(struct squat_uidlist *uidlist,
-				 unsigned int current_message_count);
-/* Mark the uidlist containing expunged messages. update_disk=FALSE should be
-   done when the uidlist is going to be compressed and this function only tells
-   the compression to check for the expunged messages. */
-int squat_uidlist_mark_having_expunges(struct squat_uidlist *uidlist,
-				       bool update_disk);
+int squat_uidlist_rebuild_init(struct squat_uidlist *uidlist, bool finish,
+			       struct squat_uidlist_rebuild_context **ctx_r);
+void squat_uidlist_rebuild_next(struct squat_uidlist_rebuild_context *ctx,
+				const ARRAY_TYPE(uint32_t) *uids);
+int squat_uidlist_rebuild_finish(struct squat_uidlist_rebuild_context *ctx,
+				 bool cancel);
 
-/* Compress the uidlist file. existing_uids may be NULL if they're not known. */
-struct squat_uidlist_compress_ctx *
-squat_uidlist_compress_begin(struct squat_uidlist *uidlist,
-			     const ARRAY_TYPE(seq_range) *existing_uids);
-int squat_uidlist_compress_next(struct squat_uidlist_compress_ctx *ctx,
-				uint32_t *uid_list_idx);
-void squat_uidlist_compress_rollback(struct squat_uidlist_compress_ctx **ctx);
-int squat_uidlist_compress_commit(struct squat_uidlist_compress_ctx **ctx);
+int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
+		      ARRAY_TYPE(uint32_t) *uids);
+uint32_t squat_uidlist_singleton_last_uid(uint32_t uid_list_idx);
 
-/* Returns UIDs for a given UID list index. */
-int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
-		      ARRAY_TYPE(seq_range) *result);
-/* Filter out UIDs which don't appear in the given UID list from the given
-   result array */
+int squat_uidlist_get_seqrange(struct squat_uidlist *uidlist,
+			       uint32_t uid_list_idx,
+			       ARRAY_TYPE(seq_range) *seq_range_arr);
 int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx,
-			 ARRAY_TYPE(seq_range) *result);
+			 ARRAY_TYPE(seq_range) *uids);
 
-/* Returns TRUE when uidlist has used so much memory that it'd prefer to
-   get flushed. */
-bool squat_uidlist_want_flush(struct squat_uidlist *uidlist);
-
+void squat_uidlist_delete(struct squat_uidlist *uidlist);
 size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist,
 			      unsigned int *count_r);
 
--- a/src/plugins/fts/Makefile.am	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts/Makefile.am	Sun Dec 02 23:51:46 2007 +0200
@@ -12,12 +12,14 @@
 lib20_fts_plugin_la_SOURCES = \
 	fts-api.c \
 	fts-plugin.c \
+	fts-search.c \
 	fts-storage.c
 
 noinst_HEADERS = \
 	fts-api.h \
 	fts-api-private.h \
-	fts-plugin.h
+	fts-plugin.h \
+	fts-storage.h
 
 install-exec-local:
 	for d in imap lda; do \
--- a/src/plugins/fts/fts-api-private.h	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts/fts-api-private.h	Sun Dec 02 23:51:46 2007 +0200
@@ -9,9 +9,8 @@
 
 	int (*get_last_uid)(struct fts_backend *backend, uint32_t *last_uid_r);
 
-	struct fts_backend_build_context *
-		(*build_init)(struct fts_backend *backend,
-			      uint32_t *last_uid_r);
+	int (*build_init)(struct fts_backend *backend, uint32_t *last_uid_r,
+			  struct fts_backend_build_context **ctx_r);
 	int (*build_more)(struct fts_backend_build_context *ctx, uint32_t uid,
 			  const unsigned char *data, size_t size, bool headers);
 	int (*build_deinit)(struct fts_backend_build_context *ctx);
@@ -23,21 +22,21 @@
 	int (*lock)(struct fts_backend *backend);
 	void (*unlock)(struct fts_backend *backend);
 
-	int (*lookup)(struct fts_backend *backend, enum fts_lookup_flags flags,
-		      const char *key, ARRAY_TYPE(seq_range) *result);
-	int (*filter)(struct fts_backend *backend, enum fts_lookup_flags flags,
-		      const char *key, ARRAY_TYPE(seq_range) *result);
+	int (*lookup)(struct fts_backend *backend, const char *key, 
+		      enum fts_lookup_flags flags,
+		      ARRAY_TYPE(seq_range) *definite_uids,
+		      ARRAY_TYPE(seq_range) *maybe_uids);
+	int (*filter)(struct fts_backend *backend, const char *key,
+		      enum fts_lookup_flags flags,
+		      ARRAY_TYPE(seq_range) *definite_uids,
+		      ARRAY_TYPE(seq_range) *maybe_uids);
 };
 
 enum fts_backend_flags {
-	/* If set, lookup() and filter() are trusted to return only
-	   actual matches. Otherwise the returned mails are opened and
-	   searched. */
-	FTS_BACKEND_FLAG_DEFINITE_LOOKUPS	= 0x01,
-	/* If set, the backend is used also for TEXT and BODY search
+	/* If set, the backend is used for TEXT and BODY search
 	   optimizations. Otherwise only TEXT_FAST and BODY_FAST are
 	   optimized. */
-	FTS_BACKEND_FLAG_EXACT_LOOKUPS		= 0x02
+	FTS_BACKEND_FLAG_SUBSTRING_LOOKUPS	= 0x01
 };
 
 struct fts_backend {
@@ -46,6 +45,7 @@
 
 	struct fts_backend_vfuncs v;
 
+	unsigned int locked:1;
 	unsigned int building:1;
 };
 
--- a/src/plugins/fts/fts-api.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts/fts-api.c	Sun Dec 02 23:51:46 2007 +0200
@@ -50,8 +50,11 @@
 	return NULL;
 }
 
-void fts_backend_deinit(struct fts_backend *backend)
+void fts_backend_deinit(struct fts_backend **_backend)
 {
+	struct fts_backend *backend = *_backend;
+
+	*_backend = NULL;
 	backend->v.deinit(backend);
 }
 
@@ -60,14 +63,14 @@
 	return backend->v.get_last_uid(backend, last_uid_r);
 }
 
-struct fts_backend_build_context *
-fts_backend_build_init(struct fts_backend *backend, uint32_t *last_uid_r)
+int fts_backend_build_init(struct fts_backend *backend, uint32_t *last_uid_r,
+			   struct fts_backend_build_context **ctx_r)
 {
 	i_assert(!backend->building);
 
 	backend->building = TRUE;
 
-	return backend->v.build_init(backend, last_uid_r);
+	return backend->v.build_init(backend, last_uid_r, ctx_r);
 }
 
 int fts_backend_build_more(struct fts_backend_build_context *ctx, uint32_t uid,
@@ -76,8 +79,11 @@
 	return ctx->backend->v.build_more(ctx, uid, data, size, headers);
 }
 
-int fts_backend_build_deinit(struct fts_backend_build_context *ctx)
+int fts_backend_build_deinit(struct fts_backend_build_context **_ctx)
 {
+	struct fts_backend_build_context *ctx = *_ctx;
+
+	*_ctx = NULL;
 	ctx->backend->building = FALSE;
 	return ctx->backend->v.build_deinit(ctx);
 }
@@ -100,64 +106,129 @@
 
 int fts_backend_lock(struct fts_backend *backend)
 {
-	return backend->v.lock(backend);
+	int ret;
+
+	i_assert(!backend->locked);
+
+	ret = backend->v.lock(backend);
+	if (ret > 0)
+		backend->locked = TRUE;
+	return ret;
 }
 
 void fts_backend_unlock(struct fts_backend *backend)
 {
+	i_assert(backend->locked);
+
+	backend->locked = FALSE;
 	backend->v.unlock(backend);
 }
 
-int fts_backend_lookup(struct fts_backend *backend, enum fts_lookup_flags flags,
-		       const char *key, ARRAY_TYPE(seq_range) *result)
+static void fts_lookup_invert(ARRAY_TYPE(seq_range) *definite_uids,
+			      const ARRAY_TYPE(seq_range) *maybe_uids)
+{
+	/* we'll begin by inverting definite UIDs */
+	seq_range_array_invert(definite_uids, 1, (uint32_t)-1);
+
+	/* from that list remove UIDs in the maybe list.
+	   the maybe list itself isn't touched. */
+	(void)seq_range_array_remove_seq_range(definite_uids, maybe_uids);
+}
+
+int fts_backend_lookup(struct fts_backend *backend, const char *key,
+		       enum fts_lookup_flags flags,
+		       ARRAY_TYPE(seq_range) *definite_uids,
+		       ARRAY_TYPE(seq_range) *maybe_uids)
 {
-	if (backend->v.lookup(backend, flags & ~FTS_LOOKUP_FLAG_INVERT,
-			      key, result) < 0)
-		return -1;
+	int ret;
+
+	ret = backend->v.lookup(backend, key, flags & ~FTS_LOOKUP_FLAG_INVERT,
+				definite_uids, maybe_uids);
+	if (ret <= 0) {
+		if (unlikely(ret < 0))
+			return -1;
+		i_assert(array_count(definite_uids) == 0 &&
+			 array_count(maybe_uids) == 0);
+	} else {
+		i_assert(array_count(definite_uids) > 0 ||
+			 array_count(maybe_uids) > 0);
+	}
+
 	if ((flags & FTS_LOOKUP_FLAG_INVERT) != 0)
-		seq_range_array_invert(result, 1, (uint32_t)-1);
+		fts_lookup_invert(definite_uids, maybe_uids);
 	return 0;
 }
 
-int fts_backend_filter(struct fts_backend *backend, enum fts_lookup_flags flags,
-		       const char *key, ARRAY_TYPE(seq_range) *result)
+static void
+fts_merge_maybies(ARRAY_TYPE(seq_range) *dest_maybe,
+		  const ARRAY_TYPE(seq_range) *dest_definite,
+		  const ARRAY_TYPE(seq_range) *src_maybe,
+		  const ARRAY_TYPE(seq_range) *src_definite)
 {
-	ARRAY_TYPE(seq_range) tmp_result;
+	const struct seq_range *dest, *src;
+	unsigned int i, dest_count, src_count;
+	uint32_t seq, seq2;
+	bool removals;
+
+	/* add/leave to dest_maybe if at least one list has maybe,
+	   and no lists have none */
+	dest = array_get(dest_maybe, &dest_count);
+	src = array_get(dest_maybe, &src_count);
+
+	/* drop unwanted uids */
+	for (i = 0; i < dest_count; ) {
+		seq2 = dest[i].seq2;
+		removals = FALSE;
+		for (seq = dest[i].seq1; seq <= seq2; seq++) {
+			if (!seq_range_exists(src_definite, seq) &&
+			    !seq_range_exists(src_maybe, seq)) {
+				if (seq_range_array_remove(dest_maybe, seq))
+					removals = TRUE;
+			}
+		}
+		if (!removals)
+			i++;
+	}
+
+	/* add new uids */
+	for (i = 0; i < src_count; i++) {
+		for (seq = src[i].seq1; seq <= src[i].seq2; seq++) {
+			if (seq_range_exists(dest_definite, seq))
+				seq_range_array_add(dest_maybe, 0, seq);
+		}
+	}
+}
+
+int fts_backend_filter(struct fts_backend *backend, const char *key,
+		       enum fts_lookup_flags flags,
+		       ARRAY_TYPE(seq_range) *definite_uids,
+		       ARRAY_TYPE(seq_range) *maybe_uids)
+{
+	ARRAY_TYPE(seq_range) tmp_definite, tmp_maybe;
 	int ret;
 
-	if (backend->v.filter != NULL)
-		return backend->v.filter(backend, flags, key, result);
+	if (backend->v.filter != NULL) {
+		return backend->v.filter(backend, key, flags,
+					 definite_uids, maybe_uids);
+	}
 
 	/* do this ourself */
-	i_array_init(&tmp_result, 64);
-	ret = fts_backend_lookup(backend, flags, key, &tmp_result);
-	if (ret == 0) {
-		const struct seq_range *range;
-		unsigned int i, count;
-		uint32_t next_seq = 1;
-
-		if ((flags & FTS_LOOKUP_FLAG_INVERT) != 0) {
-			/* if the lookups aren't definite, we can't just
-			   invert the result. */
-			i_assert((backend->flags &
-				  FTS_BACKEND_FLAG_DEFINITE_LOOKUPS) != 0);
-			seq_range_array_invert(&tmp_result, 1, (uint32_t)-1);
-		}
-		range = array_get(&tmp_result, &count);
-		for (i = 0; i < count; i++) {
-			if (next_seq != range[i].seq1) {
-				seq_range_array_remove_range(result, next_seq,
-							     range[i].seq1 - 1);
-			}
-			next_seq = range[i].seq2 + 1;
-		}
-
-		range = array_get(result, &count);
-		if (count > 0) {
-			seq_range_array_remove_range(result, next_seq,
-						     range[count-1].seq2);
-		}
+	i_array_init(&tmp_definite, 64);
+	i_array_init(&tmp_maybe, 64);
+	ret = fts_backend_lookup(backend, key, flags,
+				 &tmp_definite, &tmp_maybe);
+	if (ret < 0) {
+		array_clear(definite_uids);
+		array_clear(maybe_uids);
+	} else {
+		fts_merge_maybies(maybe_uids, definite_uids,
+				  &tmp_maybe, &tmp_definite);
+		/* keep only what exists in both lists. the rest is in
+		   maybies or not wanted */
+		seq_range_array_remove_invert_range(definite_uids,
+						    &tmp_definite);
 	}
-	array_free(&tmp_result);
+	array_free(&tmp_maybe);
+	array_free(&tmp_definite);
 	return ret;
 }
--- a/src/plugins/fts/fts-api.h	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts/fts-api.h	Sun Dec 02 23:51:46 2007 +0200
@@ -3,26 +3,27 @@
 
 struct mail;
 struct mailbox;
+struct fts_backend_build_context;
 
 #include "seq-range-array.h"
 
 enum fts_lookup_flags {
-	FTS_LOOKUP_FLAG_HEADERS	= 0x01,
+	FTS_LOOKUP_FLAG_HEADER	= 0x01,
 	FTS_LOOKUP_FLAG_BODY	= 0x02,
 	FTS_LOOKUP_FLAG_INVERT	= 0x04
 };
 
 struct fts_backend *
 fts_backend_init(const char *backend_name, struct mailbox *box);
-void fts_backend_deinit(struct fts_backend *backend);
+void fts_backend_deinit(struct fts_backend **backend);
 
 /* Get the last_uid. */
 int fts_backend_get_last_uid(struct fts_backend *backend, uint32_t *last_uid_r);
 
 /* Initialize adding new data to the index. last_uid_r is set to the last UID
    that exists in the index. */
-struct fts_backend_build_context *
-fts_backend_build_init(struct fts_backend *backend, uint32_t *last_uid_r);
+int fts_backend_build_init(struct fts_backend *backend, uint32_t *last_uid_r,
+			   struct fts_backend_build_context **ctx_r);
 /* Add more contents to the index. The data must contain only full valid
    UTF-8 characters, but it doesn't need to be NUL-terminated. size contains
    the data size in bytes, not characters. headers is TRUE if the data contains
@@ -31,7 +32,7 @@
 			   const unsigned char *data, size_t size,
 			   bool headers);
 /* Finish adding new data to the index. */
-int fts_backend_build_deinit(struct fts_backend_build_context *ctx);
+int fts_backend_build_deinit(struct fts_backend_build_context **ctx);
 
 /* Returns TRUE if there exists a build context. */
 bool fts_backend_is_building(struct fts_backend *backend);
@@ -52,12 +53,16 @@
 void fts_backend_unlock(struct fts_backend *backend);
 
 /* Lookup key from the index and return the found UIDs in result. */
-int fts_backend_lookup(struct fts_backend *backend, enum fts_lookup_flags flags,
-		       const char *key, ARRAY_TYPE(seq_range) *result);
+int fts_backend_lookup(struct fts_backend *backend, const char *key,
+		       enum fts_lookup_flags flags,
+		       ARRAY_TYPE(seq_range) *definite_uids,
+		       ARRAY_TYPE(seq_range) *maybe_uids);
 /* Drop UIDs from the result list for which the key doesn't exist. The idea
    is that with multiple search keywords you first lookup one and then filter
    the rest. */
-int fts_backend_filter(struct fts_backend *backend, enum fts_lookup_flags flags,
-		       const char *key, ARRAY_TYPE(seq_range) *result);
+int fts_backend_filter(struct fts_backend *backend, const char *key,
+		       enum fts_lookup_flags flags,
+		       ARRAY_TYPE(seq_range) *definite_uids,
+		       ARRAY_TYPE(seq_range) *maybe_uids);
 
 #endif
--- a/src/plugins/fts/fts-storage.c	Sun Dec 02 23:22:28 2007 +0200
+++ b/src/plugins/fts/fts-storage.c	Sun Dec 02 23:51:46 2007 +0200
@@ -10,6 +10,7 @@
 #include "mail-search.h"
 #include "mail-storage-private.h"
 #include "fts-api-private.h"
+#include "fts-storage.h"
 #include "fts-plugin.h"
 
 #include <stdlib.h>
@@ -22,30 +23,6 @@
 #define FTS_SEARCH_NONBLOCK_COUNT 10
 #define FTS_BUILD_NOTIFY_INTERVAL_SECS 10
 
-struct fts_mailbox {
-	union mailbox_module_context module_ctx;
-	struct fts_backend *backend_exact;
-	struct fts_backend *backend_fast;
-
-	const char *env;
-	unsigned int backend_set:1;
-};
-
-struct fts_search_context {
-	union mail_search_module_context module_ctx;
-
-	ARRAY_TYPE(seq_range) result;
-	unsigned int result_pos;
-
-	struct mail_search_arg *args, *best_arg;
-	struct fts_backend *backend;
-	struct fts_storage_build_context *build_ctx;
-	struct mailbox_transaction_context *t;
-
-	unsigned int build_initialized:1;
-	unsigned int locked:1;
-};
-
 struct fts_storage_build_context {
 	struct mail_search_context *search_ctx;
 	struct mail_search_seqset seqset;
@@ -57,12 +34,20 @@
 
 	uint32_t uid;
 	string_t *headers;
-	bool save_part;
+
+	unsigned int save_part:1;
 };
 
 struct fts_transaction_context {
 	union mailbox_transaction_module_context module_ctx;
-	bool expunges;
+
+	struct fts_storage_build_context *build_ctx;
+	struct mail *mail;
+
+	uint32_t last_uid;
+
+	unsigned int free_mail:1;
+	unsigned int expunges:1;
 };
 
 static MODULE_CONTEXT_DEFINE_INIT(fts_storage_module,
@@ -74,34 +59,16 @@
 	struct fts_mailbox *fbox = FTS_CONTEXT(box);
 	int ret;
 
-	if (fbox->backend_exact != NULL)
-		fts_backend_deinit(fbox->backend_exact);
+	if (fbox->backend_substr != NULL)
+		fts_backend_deinit(&fbox->backend_substr);
 	if (fbox->backend_fast != NULL)
-		fts_backend_deinit(fbox->backend_fast);
+		fts_backend_deinit(&fbox->backend_fast);
 
 	ret = fbox->module_ctx.super.close(box);
 	i_free(fbox);
 	return ret;
 }
 
-static void uid_range_to_seq(struct mailbox *box,
-			     ARRAY_TYPE(seq_range) *uid_range,
-			     ARRAY_TYPE(seq_range) *seq_range)
-{
-	const struct seq_range *range;
-	struct seq_range new_range;
-	unsigned int i, count;
-
-	range = array_get(uid_range, &count);
-	i_array_init(seq_range, count);
-	for (i = 0; i < count; i++) {
-		mailbox_get_uids(box, range[i].seq1, range[i].seq2,
-				 &new_range.seq1, &new_range.seq2);
-		if (new_range.seq1 != 0)
-			array_append(seq_range, &new_range, 1);
-	}
-}
-
 static int fts_build_mail_flush(struct fts_storage_build_context *ctx)
 {
 	if (str_len(ctx->headers) == 0)
@@ -152,7 +119,7 @@
 	return fts_build_mail_flush(ctx);
 }
 
-static int fts_build_mail(struct fts_storage_build_context *ctx)
+static int fts_build_mail(struct fts_storage_build_context *ctx, uint32_t uid)
 {
 	struct istream *input;
 	struct message_parser_ctx *parser;
@@ -161,7 +128,7 @@
 	struct message_part *prev_part, *skip_part;
 	int ret;
 
-	ctx->uid = ctx->mail->uid;
+	ctx->uid = uid;
 
 	if (mail_get_stream(ctx->mail, NULL, NULL, &input) < 0)
 		return -1;
@@ -208,7 +175,7 @@
 					break;
 			}
 		} else {
-			if (fts_backend_build_more(ctx->build, ctx->mail->uid,
+			if (fts_backend_build_more(ctx->build, ctx->uid,
 						   block.data, block.size,
 						   FALSE) < 0) {
 				ret = -1;
@@ -224,7 +191,7 @@
 static int fts_build_init(struct fts_search_context *fctx)
 {
 	struct mailbox_transaction_context *t = fctx->t;
-	struct fts_backend *backend = fctx->backend;
+	struct fts_backend *backend = fctx->build_backend;
 	struct fts_storage_build_context *ctx;
 	struct fts_backend_build_context *build;
 	struct mail_search_seqset seqset;
@@ -233,12 +200,6 @@
 	if (fts_backend_get_last_uid(backend, &last_uid) < 0)
 		return -1;
 
-	if (last_uid == 0 && fctx->best_arg->type == SEARCH_HEADER) {
-		/* index doesn't exist. we're not creating it just for
-		   header lookups. */
-		return -1;
-	}
-
 	memset(&seqset, 0, sizeof(seqset));
 	mailbox_get_uids(t->box, last_uid+1, (uint32_t)-1,
 			 &seqset.seq1, &seqset.seq2);
@@ -246,8 +207,15 @@
 		/* no new messages */
 		return 0;
 	}
+	fctx->first_nonindexed_seq = seqset.seq1;
 
-	build = fts_backend_build_init(backend, &last_uid_locked);
+	if (fctx->best_arg->type == SEARCH_HEADER) {
+		/* we're not updating the index just for header lookups */
+		return 0;
+	}
+
+	if (fts_backend_build_init(backend, &last_uid_locked, &build) < 0)
+		return -1;
 	if (last_uid != last_uid_locked) {
 		/* changed, need to get again the sequences */
 		i_assert(last_uid < last_uid_locked);
@@ -257,7 +225,7 @@
 				 &seqset.seq1, &seqset.seq2);
 		if (seqset.seq1 == 0) {
 			/* no new messages */
-			(void)fts_backend_build_deinit(build);
+			(void)fts_backend_build_deinit(&build);
 			return 0;
 		}
 	}
@@ -276,16 +244,19 @@
 	return 0;
 }
 
-static int fts_build_deinit(struct fts_storage_build_context *ctx)
+static int fts_build_deinit(struct fts_storage_build_context **_ctx)
 {
+	struct fts_storage_build_context *ctx = *_ctx;
 	struct mailbox *box = ctx->mail->transaction->box;
 	int ret = 0;
 
+	*_ctx = NULL;
+
 	if (mailbox_search_deinit(&ctx->search_ctx) < 0)
 		ret = -1;
 	mail_free(&ctx->mail);
 
-	if (fts_backend_build_deinit(ctx->build) < 0)
+	if (fts_backend_build_deinit(&ctx->build) < 0)
 		ret = -1;
 
 	if (ioloop_time - ctx->search_start_time.tv_sec >=
@@ -343,7 +314,7 @@
 
 	while (mailbox_search_next(ctx->search_ctx, ctx->mail) > 0) {
 		t_push();
-		ret = fts_build_mail(ctx);
+		ret = fts_build_mail(ctx, ctx->mail->uid);
 		t_pop();
 
 		if (ret < 0)
@@ -356,206 +327,27 @@
 	return 1;
 }
 
-static void fts_search_filter_args(struct fts_search_context *fctx,
-				   struct mail_search_arg *args,
-				   ARRAY_TYPE(seq_range) *uid_result)
-{
-	const char *key;
-	enum fts_lookup_flags flags;
-
-	for (; args != NULL; args = args->next) {
-		switch (args->type) {
-		case SEARCH_BODY_FAST:
-		case SEARCH_TEXT_FAST:
-			if ((fctx->backend->flags &
-			     FTS_BACKEND_FLAG_EXACT_LOOKUPS) == 0)
-				break;
-			/* fall through */
-		case SEARCH_BODY:
-		case SEARCH_TEXT:
-		case SEARCH_HEADER:
-			if (args == fctx->best_arg) {
-				/* already handled this one */
-				break;
-			}
-			if (args->not &&
-			    (fctx->backend->flags &
-			     FTS_BACKEND_FLAG_DEFINITE_LOOKUPS) == 0) {
-				/* can't optimize this */
-				break;
-			}
-
-			key = args->value.str;
-			if (*key == '\0') {
-				i_assert(args->type == SEARCH_HEADER);
-
-				/* we're only checking the existence
-				   of the header. */
-				key = args->hdr_field_name;
-			}
-
-			flags = FTS_LOOKUP_FLAG_BODY;
-			if (args->type == SEARCH_TEXT_FAST ||
-			    args->type == SEARCH_TEXT)
-				flags |= FTS_LOOKUP_FLAG_HEADERS;
-			if (args->not)
-				flags |= FTS_LOOKUP_FLAG_INVERT;
-			if (fts_backend_filter(fctx->backend, flags, key,
-					       uid_result) < 0) {
-				/* failed, but we already have limited
-				   the search, so just ignore this */
-				break;
-			}
-			if (args->type != SEARCH_HEADER &&
-			    (fctx->backend->flags &
-			     FTS_BACKEND_FLAG_DEFINITE_LOOKUPS) != 0) {
-				args->match_always = TRUE;
-				args->result = 1;
-			}
-			break;
-		case SEARCH_OR:
-		case SEARCH_SUB:
-			fts_search_filter_args(fctx, args->value.subargs,
-					       uid_result);
-			break;
-		default:
-			break;
-		}
-	}
-}
-
-static void fts_search_init(struct mailbox *box,
-			    struct fts_search_context *fctx)
-{
-	struct fts_backend *backend = fctx->backend;
-	enum fts_lookup_flags flags;
-	const char *key;
-	ARRAY_TYPE(seq_range) uid_result;
-
-	if (fts_backend_lock(backend) <= 0)
-		return;
-	fctx->locked = TRUE;
-
-	key = fctx->best_arg->value.str;
-	if (*key == '\0') {
-		i_assert(fctx->best_arg->type == SEARCH_HEADER);
-
-		/* we're only checking the existence
-		   of the header. */
-		flags = FTS_LOOKUP_FLAG_HEADERS;
-		key = fctx->best_arg->hdr_field_name;
-	} else {
-		flags = FTS_LOOKUP_FLAG_BODY;
-		if (fctx->best_arg->type == SEARCH_TEXT_FAST ||
-		    fctx->best_arg->type == SEARCH_TEXT)
-			flags |= FTS_LOOKUP_FLAG_HEADERS;
-	}
-	if (fctx->best_arg->not)
-		flags |= FTS_LOOKUP_FLAG_INVERT;
-
-	i_array_init(&uid_result, 64);
-	if (fts_backend_lookup(backend, flags, key, &uid_result) < 0) {
-		/* failed, fallback to reading everything */
-		array_free(&uid_result);
-		return;
-	}
-
-	if ((backend->flags & FTS_BACKEND_FLAG_DEFINITE_LOOKUPS) != 0) {
-		fctx->best_arg->match_always = TRUE;
-		fctx->best_arg->result = 1;
-	}
-
-	fts_search_filter_args(fctx, fctx->args, &uid_result);
-
-	uid_range_to_seq(box, &uid_result, &fctx->result);
-	array_free(&uid_result);
-}
-
-static bool arg_is_better(const struct mail_search_arg *new_arg,
-			  const struct mail_search_arg *old_arg)
-{
-	if (old_arg == NULL)
-		return TRUE;
-	if (new_arg == NULL)
-		return FALSE;
-
-	if (old_arg->not && !new_arg->not)
-		return TRUE;
-	if (!old_arg->not && new_arg->not)
-		return FALSE;
-
-	/* prefer not to use headers. they have a larger possibility of
-	   having lots of identical strings */
-	if (old_arg->type == SEARCH_HEADER)
-		return TRUE;
-	else if (new_arg->type == SEARCH_HEADER)
-		return FALSE;
-
-	return strlen(new_arg->value.str) > strlen(old_arg->value.str);
-}
-
-static void fts_search_args_check(struct mail_search_arg *args,
-				  bool *have_fast_r, bool *have_exact_r,
-				  struct mail_search_arg **best_fast_arg,
-				  struct mail_search_arg **best_exact_arg)
-{
-	for (; args != NULL; args = args->next) {
-		switch (args->type) {
-		case SEARCH_BODY_FAST:
-		case SEARCH_TEXT_FAST:
-			if (*args->value.str == '\0') {
-				/* this matches everything */
-				args->match_always = TRUE;
-				args->result = 1;
-				break;
-			}
-			if (arg_is_better(args, *best_fast_arg)) {
-				*best_fast_arg = args;
-				*have_fast_r = TRUE;
-			}
-			break;
-		case SEARCH_BODY:
-		case SEARCH_TEXT:
-			if (*args->value.str == '\0') {
-				/* this matches everything */
-				args->match_always = TRUE;
-				args->result = 1;
-				break;
-			}
-		case SEARCH_HEADER:
-			if (arg_is_better(args, *best_exact_arg)) {
-				*best_exact_arg = args;
-				*have_exact_r = TRUE;
-			}
-			break;
-		case SEARCH_OR:
-		case SEARCH_SUB:
-			fts_search_args_check(args->value.subargs,
-					      have_fast_r, have_exact_r,
-					      best_fast_arg, best_exact_arg);
-			break;
-		default:
-			break;
-		}
-	}
-}
-
 static bool fts_try_build_init(struct fts_search_context *fctx)
 {
-	if (fctx->backend == NULL) {
+	if (fctx->build_backend == NULL) {
 		fctx->build_initialized = TRUE;
 		return TRUE;
 	}
 
-	if (fts_backend_is_building(fctx->backend))
+	if (fts_backend_is_building(fctx->build_backend)) {
+		/* this process is already building the indexes */
 		return FALSE;
+	}
 	fctx->build_initialized = TRUE;
 
-	if (fts_build_init(fctx) < 0)
-		fctx->backend = NULL;
-	else if (fctx->build_ctx == NULL) {
+	if (fts_build_init(fctx) < 0) {
+		fctx->build_backend = NULL;
+		return TRUE;
+	}
+
+	if (fctx->build_ctx == NULL) {
 		/* the index was up to date */
-		fts_search_init(fctx->t->box, fctx);
+		fts_search_lookup(fctx);
 	}
 	return TRUE;
 }
@@ -568,42 +360,21 @@
 	struct fts_mailbox *fbox = FTS_CONTEXT(t->box);
 	struct mail_search_context *ctx;
 	struct fts_search_context *fctx;
-	struct mail_search_arg *best_fast_arg, *best_exact_arg;
-	bool have_fast, have_exact;
 
 	ctx = fbox->module_ctx.super.
 		search_init(t, charset, args, sort_program);
 
 	fctx = i_new(struct fts_search_context, 1);
+	fctx->fbox = fbox;
 	fctx->t = t;
 	fctx->args = args;
 	MODULE_CONTEXT_SET(ctx, fts_storage_module, fctx);
 
-	if (fbox->backend_exact == NULL && fbox->backend_fast == NULL)
+	if (fbox->backend_substr == NULL && fbox->backend_fast == NULL)
 		return ctx;
 
-	have_fast = have_exact = FALSE;
-	best_fast_arg = best_exact_arg = NULL;
-	fts_search_args_check(args, &have_fast, &have_exact,
-			      &best_fast_arg, &best_exact_arg);
-	if (have_fast && fbox->backend_fast != NULL) {
-		/* use fast backend whenever possible */
-		fctx->backend = fbox->backend_fast;
-		fctx->best_arg = best_fast_arg;
-	} else if (have_exact || have_fast) {
-		fctx->backend = fbox->backend_exact;
-		fctx->best_arg = arg_is_better(best_exact_arg, best_fast_arg) ?
-			best_exact_arg : best_fast_arg;
-	}
-
-	if (fctx->best_arg != NULL && fctx->best_arg->not &&
-	    (fctx->backend->flags & FTS_BACKEND_FLAG_DEFINITE_LOOKUPS) == 0) {
-		/* NOTs can't be handled without definite lookups */
-		fctx->backend = NULL;
-		fctx->best_arg = NULL;
-	}
-
-	fts_try_build_init(fctx);
+	fts_search_analyze(fctx);
+	(void)fts_try_build_init(fctx);
 	return ctx;
 }
 
@@ -615,6 +386,8 @@
 	int ret;
 
 	if (!fctx->build_initialized) {
+		/* we're still waiting for this process (but another command)
+		   to finish building the indexes */
 		if (!fts_try_build_init(fctx)) {
 			*tryagain_r = TRUE;
 			return 0;
@@ -622,7 +395,7 @@
 	}
 
 	if (fctx->build_ctx != NULL) {
-		/* still building the index */
+		/* this command is still building the indexes */
 		ret = fts_build_more(fctx->build_ctx);
 		if (ret == 0) {
 			*tryagain_r = TRUE;
@@ -630,54 +403,118 @@
 		}
 
 		/* finished / error */
-		fts_build_deinit(fctx->build_ctx);
-		fctx->build_ctx = NULL;
+		fts_build_deinit(&fctx->build_ctx);
+		if (ret > 0)
+			fts_search_lookup(fctx);
+	}
 
-		if (ret > 0)
-			fts_search_init(ctx->transaction->box, fctx);
-	}
+	/* if we're here, the indexes are either built or they're not used */
 	return fbox->module_ctx.super.
 		search_next_nonblock(ctx, mail, tryagain_r);
+}
 
+static void
+fts_mailbox_search_args_definite_set(struct fts_search_context *fctx)
+{
+	struct mail_search_arg *arg;
+
+	for (arg = fctx->args; arg != NULL; arg = arg->next) {
+		switch (arg->type) {
+		case SEARCH_TEXT:
+		case SEARCH_BODY:
+		case SEARCH_BODY_FAST:
+		case SEARCH_TEXT_FAST:
+			arg->result = 1;
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static int
+search_next_update_seq_finish(struct mail_search_context *ctx,
+			      struct fts_search_context *fctx)
+{
+	struct fts_mailbox *fbox = FTS_CONTEXT(ctx->transaction->box);
+
+	if (fctx->first_nonindexed_seq == 0) {
+		/* everything was indexed. we're done */
+		return 0;
+	}
+	if (ctx->seq < fctx->first_nonindexed_seq) {
+		/* scan the non-indexed messages */
+		fctx->seqs_set = FALSE;
+		ctx->seq = fctx->first_nonindexed_seq - 1;
+	}
+	return fbox->module_ctx.super.search_next_update_seq(ctx);
 }
 
 static int fts_mailbox_search_next_update_seq(struct mail_search_context *ctx)
 {
 	struct fts_mailbox *fbox = FTS_CONTEXT(ctx->transaction->box);
 	struct fts_search_context *fctx = FTS_CONTEXT(ctx);
-	struct seq_range *range;
-	unsigned int count;
+	struct seq_range *def_range, *maybe_range, *range;
+	unsigned int def_count, maybe_count;
 	uint32_t wanted_seq;
+	bool use_maybe;
 	int ret;
 
-	if (!array_is_created(&fctx->result))
+	if (!fctx->seqs_set)
 		return fbox->module_ctx.super.search_next_update_seq(ctx);
 
+	/* fts_search_lookup() was called successfully */
 	do {
-		range = array_get_modifiable(&fctx->result, &count);
-		while (fctx->result_pos < count &&
-		       ctx->seq > range[fctx->result_pos].seq2)
-			fctx->result_pos++;
-
-		if (fctx->result_pos == count)
-			return 0;
+		def_range = array_get_modifiable(&fctx->definite_seqs,
+						 &def_count);
+		maybe_range = array_get_modifiable(&fctx->maybe_seqs,
+						   &maybe_count);
+		/* if we're ahead of current positions, skip them */
+		while (fctx->definite_idx < def_count &&
+		       ctx->seq > def_range[fctx->definite_idx].seq2)
+			fctx->definite_idx++;
+		while (fctx->maybe_idx < maybe_count &&
+		       ctx->seq > maybe_range[fctx->maybe_idx].seq2)
+			fctx->maybe_idx++;
 
-		if (ctx->seq > range[fctx->result_pos].seq1)
-			range[fctx->result_pos].seq1 = ctx->seq+1;
-		else {
-			ctx->seq = range[fctx->result_pos].seq1 - 1;
-
-			if (fctx->result_pos < count &&
-			    ctx->seq + 1 == range[fctx->result_pos].seq2)
-				fctx->result_pos++;
-			else
-				range[fctx->result_pos].seq1++;
+		/* use whichever is lower of definite/maybe */
+		if (fctx->definite_idx == def_count) {
+			if (fctx->maybe_idx == maybe_count)
+				return search_next_update_seq_finish(ctx, fctx);
+			use_maybe = TRUE;
+		} else if (fctx->maybe_idx == maybe_count) {
+			use_maybe = FALSE;
+		} else {
+			use_maybe = maybe_range[fctx->maybe_idx].seq1 <
+				def_range[fctx->definite_idx].seq2;
 		}
 
+		if (use_maybe)
+			range = maybe_range + fctx->maybe_idx;
+		else
+			range = def_range + fctx->definite_idx;
+
+		i_assert(range->seq1 <= range->seq2);
+		if (ctx->seq > range->seq1) {
+			/* current sequence is already larger than where
+			   range begins, so use the current sequence. */
+			range->seq1 = ctx->seq+1;
+		} else {
+			ctx->seq = range->seq1 - 1;
+			if (++range->seq1 > range->seq2)
+				range->seq2 = 0;
+		}
+
+		/* ctx->seq points to previous sequence we want */
 		wanted_seq = ctx->seq + 1;
 		ret = fbox->module_ctx.super.search_next_update_seq(ctx);
 	} while (ret > 0 && wanted_seq != ctx->seq);
 
+	if (!use_maybe) {
+		/* we have definite results, update args */
+		fts_mailbox_search_args_definite_set(fctx);
+	}
+
 	return ret;
 }
 
@@ -688,14 +525,13 @@
 
 	if (fctx->build_ctx != NULL) {
 		/* the search was cancelled */
-		fts_build_deinit(fctx->build_ctx);
+		fts_build_deinit(&fctx->build_ctx);
 	}
 
-	if (fctx->locked)
-		fts_backend_unlock(fctx->backend);
-
-	if (array_is_created(&fctx->result))
-		array_free(&fctx->result);
+	if (array_is_created(&fctx->definite_seqs))
+		array_free(&fctx->definite_seqs);
+	if (array_is_created(&fctx->maybe_seqs))
+		array_free(&fctx->maybe_seqs);
 	i_free(fctx);
 	return fbox->module_ctx.super.search_deinit(ctx);
 }
@@ -708,8 +544,8 @@
 	struct fts_transaction_context *ft = FTS_CONTEXT(_mail->transaction);
 
 	ft->expunges = TRUE;
-	if (fbox->backend_exact != NULL)
-		fts_backend_expunge(fbox->backend_exact, _mail);
+	if (fbox->backend_substr != NULL)
+		fts_backend_expunge(fbox->backend_substr, _mail);
 	if (fbox->backend_fast != NULL)
 		fts_backend_expunge(fbox->backend_fast, _mail);
 
@@ -728,7 +564,7 @@
 
 	_mail = fbox->module_ctx.super.
 		mail_alloc(t, wanted_fields, wanted_headers);
-	if (fbox->backend_exact != NULL || fbox->backend_fast != NULL) {
+	if (fbox->backend_substr != NULL || fbox->backend_fast != NULL) {
 		mail = (struct mail_private *)_mail;
 
 		fmail = p_new(mail->pool, union mail_module_context, 1);
@@ -752,12 +588,12 @@
 			continue;
 
 		if ((backend->flags &
-		     FTS_BACKEND_FLAG_EXACT_LOOKUPS) != 0) {
-			if (fbox->backend_exact != NULL) {
-				i_fatal("fts: duplicate exact backend: %s",
+		     FTS_BACKEND_FLAG_SUBSTRING_LOOKUPS) != 0) {
+			if (fbox->backend_substr != NULL) {
+				i_fatal("fts: duplicate substring backend: %s",
 					*tmp);
 			}
-			fbox->backend_exact = backend;
+			fbox->backend_substr = backend;
 		} else {
 			if (fbox->backend_fast != NULL) {
 				i_fatal("fts: duplicate fast backend: %s",
@@ -791,6 +627,14 @@
 }
 
 static void
+fts_storage_build_context_deinit(struct fts_storage_build_context *build_ctx)
+{
+	(void)fts_backend_build_deinit(&build_ctx->build);
+	str_free(&build_ctx->headers);
+	i_free(build_ctx);
+}
+
+static void
 fts_transaction_finish(struct mailbox *box, struct fts_transaction_context *ft,
 		       bool committed)
 {
@@ -811,6 +655,13 @@
 	struct fts_mailbox *fbox = FTS_CONTEXT(box);
 	struct fts_transaction_context *ft = FTS_CONTEXT(t);
 
+	if (ft->build_ctx != NULL) {
+		fts_storage_build_context_deinit(ft->build_ctx);
+		ft->build_ctx = NULL;
+	}
+	if (ft->free_mail)
+		mail_free(&ft->mail);
+
 	fbox->module_ctx.super.transaction_rollback(t);
 	fts_transaction_finish(box, ft, FALSE);
 }
@@ -825,6 +676,13 @@
 	struct fts_transaction_context *ft = FTS_CONTEXT(t);
 	int ret;
 
+	if (ft->build_ctx != NULL) {
+		fts_storage_build_context_deinit(ft->build_ctx);
+		ft->build_ctx = NULL;
+	}
+	if (ft->free_mail)
+		mail_free(&ft->mail);
+
 	ret = fbox->module_ctx.super.transaction_commit(t,
 							uid_validity_r,
 							first_saved_uid_r,