diff options
Diffstat (limited to 'src/lib-index/mail-index-sync-keywords.c')
-rw-r--r-- | src/lib-index/mail-index-sync-keywords.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-sync-keywords.c b/src/lib-index/mail-index-sync-keywords.c new file mode 100644 index 0000000..2c45156 --- /dev/null +++ b/src/lib-index/mail-index-sync-keywords.c @@ -0,0 +1,347 @@ +/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "buffer.h" +#include "mail-index-modseq.h" +#include "mail-index-view-private.h" +#include "mail-index-sync-private.h" +#include "mail-transaction-log.h" + +static bool +keyword_lookup(struct mail_index_sync_map_ctx *ctx, + const char *keyword_name, unsigned int *idx_r) +{ + struct mail_index_map *map = ctx->view->map; + const unsigned int *idx_map; + unsigned int i, count, keyword_idx; + + if (array_is_created(&map->keyword_idx_map) && + mail_index_keyword_lookup(ctx->view->index, keyword_name, + &keyword_idx)) { + /* FIXME: slow. maybe create index -> file mapping as well */ + idx_map = array_get(&map->keyword_idx_map, &count); + for (i = 0; i < count; i++) { + if (idx_map[i] == keyword_idx) { + *idx_r = i; + return TRUE; + } + } + } + return FALSE; +} + +static buffer_t * +keywords_get_header_buf(struct mail_index_map *map, + const struct mail_index_ext *ext, + unsigned int new_count, unsigned int *keywords_count_r, + size_t *rec_offset_r, size_t *name_offset_root_r, + size_t *name_offset_r) +{ + buffer_t *buf; + const struct mail_index_keyword_header *kw_hdr; + const struct mail_index_keyword_header_rec *kw_rec; + const char *name; + struct mail_index_keyword_header new_kw_hdr; + uint32_t offset; + + kw_hdr = MAIL_INDEX_MAP_HDR_OFFSET(map, ext->hdr_offset); + kw_rec = (const void *)(kw_hdr + 1); + name = (const char *)(kw_rec + kw_hdr->keywords_count); + + if (kw_hdr->keywords_count == 0) + return NULL; + + i_assert((size_t)(name - (const char *)kw_hdr) < ext->hdr_size); + + new_kw_hdr = *kw_hdr; + new_kw_hdr.keywords_count += new_count; + *keywords_count_r = new_kw_hdr.keywords_count; + + offset = kw_rec[kw_hdr->keywords_count-1].name_offset; + offset += strlen(name + offset) + 1; + + buf = t_buffer_create(512); + buffer_append(buf, &new_kw_hdr, sizeof(new_kw_hdr)); + buffer_append(buf, kw_rec, sizeof(*kw_rec) * kw_hdr->keywords_count); + *rec_offset_r = buf->used; + buffer_write(buf, buf->used + sizeof(*kw_rec) * new_count, + name, offset); + *name_offset_root_r = buf->used; + *name_offset_r = offset; + return buf; +} + +static void keywords_ext_register(struct mail_index_sync_map_ctx *ctx, + uint32_t ext_map_idx, uint32_t reset_id, + uint32_t hdr_size, uint32_t keywords_count) +{ + buffer_t ext_intro_buf; + struct mail_transaction_ext_intro *u; + unsigned char ext_intro_data[sizeof(*u) + + sizeof(MAIL_INDEX_EXT_KEYWORDS)-1]; + + i_assert(keywords_count > 0); + + buffer_create_from_data(&ext_intro_buf, ext_intro_data, + sizeof(ext_intro_data)); + + u = buffer_append_space_unsafe(&ext_intro_buf, sizeof(*u)); + u->ext_id = ext_map_idx; + u->reset_id = reset_id; + u->hdr_size = hdr_size; + u->record_size = (keywords_count + CHAR_BIT - 1) / CHAR_BIT; + if ((u->record_size % 4) != 0) { + /* since we aren't properly aligned anyway, + reserve one extra byte for future */ + u->record_size++; + } + u->record_align = 1; + + if (ext_map_idx == (uint32_t)-1) { + u->name_size = strlen(MAIL_INDEX_EXT_KEYWORDS); + buffer_append(&ext_intro_buf, MAIL_INDEX_EXT_KEYWORDS, + u->name_size); + } + + ctx->internal_update = TRUE; + if (mail_index_sync_ext_intro(ctx, u) < 0) + i_panic("Keyword extension growing failed"); + ctx->internal_update = FALSE; +} + +static void +keywords_header_add(struct mail_index_sync_map_ctx *ctx, + const char *keyword_name, unsigned int *keyword_idx_r) +{ + struct mail_index_map *map; + const struct mail_index_ext *ext = NULL; + struct mail_index_keyword_header *kw_hdr; + struct mail_index_keyword_header_rec kw_rec; + uint32_t ext_map_idx; + buffer_t *buf = NULL; + size_t keyword_len, rec_offset, name_offset, name_offset_root; + unsigned int keywords_count; + + /* if we crash in the middle of writing the header, the + keywords are more or less corrupted. avoid that by + making sure the header is updated atomically. */ + map = mail_index_sync_get_atomic_map(ctx); + + if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS, + &ext_map_idx)) + ext_map_idx = (uint32_t)-1; + else { + /* update existing header */ + ext = array_idx(&map->extensions, ext_map_idx); + buf = keywords_get_header_buf(map, ext, 1, &keywords_count, + &rec_offset, &name_offset_root, + &name_offset); + } + + if (buf == NULL) { + /* create new / replace broken header */ + const unsigned int initial_keywords_count = 1; + + buf = t_buffer_create(512); + kw_hdr = buffer_append_space_unsafe(buf, sizeof(*kw_hdr)); + kw_hdr->keywords_count = initial_keywords_count; + + keywords_count = kw_hdr->keywords_count; + rec_offset = buf->used; + name_offset_root = rec_offset + + initial_keywords_count * sizeof(kw_rec); + name_offset = 0; + } + + /* add the keyword */ + i_zero(&kw_rec); + kw_rec.name_offset = name_offset; + + keyword_len = strlen(keyword_name) + 1; + buffer_write(buf, rec_offset, &kw_rec, sizeof(kw_rec)); + buffer_write(buf, name_offset_root, keyword_name, keyword_len); + + rec_offset += sizeof(kw_rec); + kw_rec.name_offset += keyword_len; + name_offset_root += keyword_len; + + if ((buf->used % 4) != 0) + buffer_append_zero(buf, 4 - (buf->used % 4)); + + if (ext == NULL || buf->used > ext->hdr_size || + (uint32_t)ext->record_size * CHAR_BIT < keywords_count) { + /* if we need to grow the buffer, add some padding */ + buffer_append_zero(buf, 128); + keywords_ext_register(ctx, ext_map_idx, + ext == NULL ? 0 : ext->reset_id, + buf->used, keywords_count); + + /* map may have changed */ + map = ctx->view->map; + + if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS, + &ext_map_idx)) + i_unreached(); + ext = array_idx(&map->extensions, ext_map_idx); + + i_assert(ext->hdr_size == buf->used); + } + + buffer_copy(map->hdr_copy_buf, ext->hdr_offset, buf, 0, buf->used); + i_assert(map->hdr_copy_buf->used == map->hdr.header_size); + + if (mail_index_map_parse_keywords(map) < 0) + i_panic("Keyword update corrupted keywords header"); + + *keyword_idx_r = keywords_count - 1; + i_assert(*keyword_idx_r / CHAR_BIT < ext->record_size); +} + +static int +keywords_update_records(struct mail_index_sync_map_ctx *ctx, + const struct mail_index_ext *ext, + unsigned int keyword_idx, enum modify_type type, + uint32_t uid1, uint32_t uid2) +{ + struct mail_index_view *view = ctx->view; + struct mail_index_record *rec; + unsigned char *data, data_mask; + unsigned int data_offset; + uint32_t seq1, seq2; + + i_assert(keyword_idx != UINT_MAX); + + if (!mail_index_lookup_seq_range(view, uid1, uid2, &seq1, &seq2)) + return 1; + + mail_index_modseq_update_keyword(ctx->modseq_ctx, keyword_idx, + seq1, seq2); + + data_offset = keyword_idx / CHAR_BIT; + data_mask = 1 << (keyword_idx % CHAR_BIT); + + i_assert(data_offset < ext->record_size); + data_offset += ext->record_offset; + + i_assert(data_offset >= MAIL_INDEX_RECORD_MIN_SIZE); + + switch (type) { + case MODIFY_ADD: + for (; seq1 <= seq2; seq1++) { + rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq1); + data = PTR_OFFSET(rec, data_offset); + *data |= data_mask; + } + break; + case MODIFY_REMOVE: + data_mask = (unsigned char)~data_mask; + for (; seq1 <= seq2; seq1++) { + rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq1); + data = PTR_OFFSET(rec, data_offset); + *data &= data_mask; + } + break; + default: + i_unreached(); + } + return 1; +} + +int mail_index_sync_keywords(struct mail_index_sync_map_ctx *ctx, + const struct mail_transaction_header *hdr, + const struct mail_transaction_keyword_update *rec) +{ + struct mail_index_view *view = ctx->view; + const char *keyword_name; + const struct mail_index_ext *ext; + const uint32_t *uid, *end; + uint32_t seqset_offset, ext_map_idx; + unsigned int keyword_idx; + int ret; + + i_assert(rec->name_size > 0); + + seqset_offset = sizeof(*rec) + rec->name_size; + if ((seqset_offset % 4) != 0) + seqset_offset += 4 - (seqset_offset % 4); + i_assert(seqset_offset < hdr->size); + + uid = CONST_PTR_OFFSET(rec, seqset_offset); + end = CONST_PTR_OFFSET(rec, hdr->size); + + keyword_name = t_strndup(rec + 1, rec->name_size); + if (!keyword_lookup(ctx, keyword_name, &keyword_idx)) + keywords_header_add(ctx, keyword_name, &keyword_idx); + + /* if the keyword wasn't found, the "keywords" extension was created. + if it was found, the record size should already be correct, but + in case it isn't just fix it ourself. */ + if (!mail_index_map_lookup_ext(view->map, MAIL_INDEX_EXT_KEYWORDS, + &ext_map_idx)) + i_unreached(); + + ext = array_idx(&view->map->extensions, ext_map_idx); + if (keyword_idx / CHAR_BIT >= ext->record_size) { + if (rec->modify_type == MODIFY_REMOVE) { + /* nothing to do */ + return 1; + } + + /* grow the record size */ + keywords_ext_register(ctx, ext_map_idx, ext->reset_id, + ext->hdr_size, + array_count(&view->map->keyword_idx_map)); + if (!mail_index_map_lookup_ext(view->map, + MAIL_INDEX_EXT_KEYWORDS, + &ext_map_idx)) + i_unreached(); + ext = array_idx(&view->map->extensions, ext_map_idx); + } + + while (uid+2 <= end) { + ret = keywords_update_records(ctx, ext, keyword_idx, + rec->modify_type, + uid[0], uid[1]); + if (ret <= 0) + return ret; + + uid += 2; + } + + return 1; +} + +int +mail_index_sync_keywords_reset(struct mail_index_sync_map_ctx *ctx, + const struct mail_transaction_header *hdr, + const struct mail_transaction_keyword_reset *r) +{ + struct mail_index_map *map = ctx->view->map; + struct mail_index_record *rec; + const struct mail_index_ext *ext; + const struct mail_transaction_keyword_reset *end; + uint32_t ext_map_idx, seq1, seq2; + + if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS, + &ext_map_idx)) { + /* nothing to do */ + return 1; + } + + ext = array_idx(&map->extensions, ext_map_idx); + end = CONST_PTR_OFFSET(r, hdr->size); + for (; r != end; r++) { + if (!mail_index_lookup_seq_range(ctx->view, r->uid1, r->uid2, + &seq1, &seq2)) + continue; + + mail_index_modseq_reset_keywords(ctx->modseq_ctx, seq1, seq2); + for (; seq1 <= seq2; seq1++) { + rec = MAIL_INDEX_REC_AT_SEQ(map, seq1); + memset(PTR_OFFSET(rec, ext->record_offset), + 0, ext->record_size); + } + } + return 1; +} |