Mercurial > dovecot > original-hg > dovecot-1.2
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,