mail-index-sync-keywords.c revision 1af0c5d8814c2304d09d8ca844a84f0b9b0c1f61
1516N/A/* Copyright (c) 2004-2016 Dovecot authors, see the included COPYING file */
38N/A
38N/A#include "lib.h"
38N/A#include "array.h"
38N/A#include "buffer.h"
38N/A#include "mail-index-modseq.h"
38N/A#include "mail-index-view-private.h"
38N/A#include "mail-index-sync-private.h"
38N/A#include "mail-transaction-log.h"
38N/A
38N/Astatic bool
38N/Akeyword_lookup(struct mail_index_sync_map_ctx *ctx,
38N/A const char *keyword_name, unsigned int *idx_r)
38N/A{
38N/A struct mail_index_map *map = ctx->view->map;
38N/A const unsigned int *idx_map;
38N/A unsigned int i, count, keyword_idx;
38N/A
38N/A if (array_is_created(&map->keyword_idx_map) &&
38N/A mail_index_keyword_lookup(ctx->view->index, keyword_name,
38N/A &keyword_idx)) {
38N/A /* FIXME: slow. maybe create index -> file mapping as well */
38N/A idx_map = array_get(&map->keyword_idx_map, &count);
3356N/A for (i = 0; i < count; i++) {
38N/A if (idx_map[i] == keyword_idx) {
38N/A *idx_r = i;
38N/A return TRUE;
3234N/A }
3234N/A }
2456N/A }
443N/A return FALSE;
2456N/A}
49N/A
2276N/Astatic buffer_t *
2276N/Akeywords_get_header_buf(struct mail_index_map *map,
3234N/A const struct mail_index_ext *ext,
3234N/A unsigned int new_count, unsigned int *keywords_count_r,
38N/A size_t *rec_offset_r, size_t *name_offset_root_r,
2456N/A size_t *name_offset_r)
2456N/A{
38N/A buffer_t *buf;
2276N/A const struct mail_index_keyword_header *kw_hdr;
2276N/A const struct mail_index_keyword_header_rec *kw_rec;
3155N/A const char *name;
2456N/A struct mail_index_keyword_header new_kw_hdr;
38N/A uint32_t offset;
38N/A
38N/A kw_hdr = CONST_PTR_OFFSET(map->hdr_base, ext->hdr_offset);
1292N/A kw_rec = (const void *)(kw_hdr + 1);
38N/A name = (const char *)(kw_rec + kw_hdr->keywords_count);
2276N/A
2276N/A if (kw_hdr->keywords_count == 0)
2276N/A return NULL;
2276N/A
2276N/A i_assert((size_t)(name - (const char *)kw_hdr) < ext->hdr_size);
2276N/A
2276N/A new_kw_hdr = *kw_hdr;
2276N/A new_kw_hdr.keywords_count += new_count;
2276N/A *keywords_count_r = new_kw_hdr.keywords_count;
2276N/A
3356N/A offset = kw_rec[kw_hdr->keywords_count-1].name_offset;
2276N/A offset += strlen(name + offset) + 1;
2276N/A
2456N/A buf = buffer_create_dynamic(pool_datastack_create(), 512);
42N/A buffer_append(buf, &new_kw_hdr, sizeof(new_kw_hdr));
42N/A buffer_append(buf, kw_rec, sizeof(*kw_rec) * kw_hdr->keywords_count);
42N/A *rec_offset_r = buf->used;
42N/A buffer_write(buf, buf->used + sizeof(*kw_rec) * new_count,
42N/A name, offset);
42N/A *name_offset_root_r = buf->used;
38N/A *name_offset_r = offset;
42N/A return buf;
2276N/A}
2276N/A
2276N/Astatic void keywords_ext_register(struct mail_index_sync_map_ctx *ctx,
2276N/A uint32_t ext_map_idx, uint32_t reset_id,
2456N/A uint32_t hdr_size, uint32_t keywords_count)
42N/A{
1292N/A buffer_t ext_intro_buf;
1292N/A struct mail_transaction_ext_intro *u;
1420N/A unsigned char ext_intro_data[sizeof(*u) +
1420N/A sizeof(MAIL_INDEX_EXT_KEYWORDS)-1];
2456N/A
1292N/A i_assert(keywords_count > 0);
1292N/A
83N/A buffer_create_from_data(&ext_intro_buf, ext_intro_data,
83N/A sizeof(ext_intro_data));
83N/A
338N/A u = buffer_append_space_unsafe(&ext_intro_buf, sizeof(*u));
338N/A u->ext_id = ext_map_idx;
42N/A u->reset_id = reset_id;
504N/A u->hdr_size = hdr_size;
504N/A u->record_size = (keywords_count + CHAR_BIT - 1) / CHAR_BIT;
504N/A if ((u->record_size % 4) != 0) {
504N/A /* since we aren't properly aligned anyway,
504N/A reserve one extra byte for future */
504N/A u->record_size++;
2304N/A }
42N/A u->record_align = 1;
338N/A
1420N/A if (ext_map_idx == (uint32_t)-1) {
49N/A u->name_size = strlen(MAIL_INDEX_EXT_KEYWORDS);
1420N/A buffer_append(&ext_intro_buf, MAIL_INDEX_EXT_KEYWORDS,
2456N/A u->name_size);
42N/A }
2304N/A
2304N/A ctx->internal_update = TRUE;
2304N/A if (mail_index_sync_ext_intro(ctx, u) < 0)
2304N/A i_panic("Keyword extension growing failed");
2304N/A ctx->internal_update = FALSE;
2456N/A}
2456N/A
83N/Astatic void
2304N/Akeywords_header_add(struct mail_index_sync_map_ctx *ctx,
2304N/A const char *keyword_name, unsigned int *keyword_idx_r)
83N/A{
83N/A struct mail_index_map *map;
83N/A const struct mail_index_ext *ext = NULL;
83N/A struct mail_index_keyword_header *kw_hdr;
83N/A struct mail_index_keyword_header_rec kw_rec;
42N/A uint32_t ext_map_idx;
42N/A buffer_t *buf = NULL;
42N/A size_t keyword_len, rec_offset, name_offset, name_offset_root;
338N/A unsigned int keywords_count;
338N/A
338N/A /* if we crash in the middle of writing the header, the
338N/A keywords are more or less corrupted. avoid that by
338N/A making sure the header is updated atomically. */
42N/A map = mail_index_sync_get_atomic_map(ctx);
42N/A
40N/A if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS,
2456N/A &ext_map_idx))
2456N/A ext_map_idx = (uint32_t)-1;
2456N/A else {
2456N/A /* update existing header */
2304N/A ext = array_idx(&map->extensions, ext_map_idx);
2304N/A buf = keywords_get_header_buf(map, ext, 1, &keywords_count,
2304N/A &rec_offset, &name_offset_root,
1420N/A &name_offset);
2304N/A }
1420N/A
2456N/A if (buf == NULL) {
2456N/A /* create new / replace broken header */
2456N/A const unsigned int initial_keywords_count = 1;
2456N/A
2456N/A buf = buffer_create_dynamic(pool_datastack_create(), 512);
2456N/A kw_hdr = buffer_append_space_unsafe(buf, sizeof(*kw_hdr));
2456N/A kw_hdr->keywords_count = initial_keywords_count;
2456N/A
2456N/A keywords_count = kw_hdr->keywords_count;
2456N/A rec_offset = buf->used;
2456N/A name_offset_root = rec_offset +
2456N/A initial_keywords_count * sizeof(kw_rec);
49N/A name_offset = 0;
49N/A }
1292N/A
2456N/A /* add the keyword */
2456N/A memset(&kw_rec, 0, sizeof(kw_rec));
2456N/A kw_rec.name_offset = name_offset;
2456N/A
2456N/A keyword_len = strlen(keyword_name) + 1;
1420N/A buffer_write(buf, rec_offset, &kw_rec, sizeof(kw_rec));
2276N/A buffer_write(buf, name_offset_root, keyword_name, keyword_len);
2276N/A
1420N/A rec_offset += sizeof(kw_rec);
1420N/A kw_rec.name_offset += keyword_len;
1420N/A name_offset_root += keyword_len;
1420N/A
1420N/A if ((buf->used % 4) != 0)
1420N/A buffer_append_zero(buf, 4 - (buf->used % 4));
1420N/A
2304N/A if (ext == NULL || buf->used > ext->hdr_size ||
3253N/A (uint32_t)ext->record_size * CHAR_BIT < keywords_count) {
3253N/A /* if we need to grow the buffer, add some padding */
2276N/A buffer_append_zero(buf, 128);
1292N/A keywords_ext_register(ctx, ext_map_idx,
2456N/A ext == NULL ? 0 : ext->reset_id,
1292N/A buf->used, keywords_count);
443N/A
2304N/A /* map may have changed */
2304N/A map = ctx->view->map;
2304N/A
2304N/A if (!mail_index_map_lookup_ext(map, MAIL_INDEX_EXT_KEYWORDS,
2304N/A &ext_map_idx))
2304N/A i_unreached();
2304N/A ext = array_idx(&map->extensions, ext_map_idx);
2304N/A
2304N/A i_assert(ext->hdr_size == buf->used);
2304N/A }
3234N/A
2304N/A buffer_copy(map->hdr_copy_buf, ext->hdr_offset, buf, 0, buf->used);
2304N/A map->hdr_base = map->hdr_copy_buf->data;
49N/A i_assert(map->hdr_copy_buf->used == map->hdr.header_size);
2276N/A
49N/A if (mail_index_map_parse_keywords(map) < 0)
49N/A i_panic("Keyword update corrupted keywords header");
49N/A
2276N/A *keyword_idx_r = keywords_count - 1;
49N/A i_assert(*keyword_idx_r / CHAR_BIT < ext->record_size);
49N/A}
2276N/A
49N/Astatic int
2304N/Akeywords_update_records(struct mail_index_sync_map_ctx *ctx,
2304N/A const struct mail_index_ext *ext,
3158N/A unsigned int keyword_idx, enum modify_type type,
2456N/A uint32_t uid1, uint32_t uid2)
3234N/A{
2456N/A struct mail_index_view *view = ctx->view;
2456N/A struct mail_index_record *rec;
1765N/A unsigned char *data, data_mask;
2276N/A unsigned int data_offset;
2276N/A uint32_t seq1, seq2;
2276N/A
2276N/A i_assert(keyword_idx != UINT_MAX);
2276N/A
2276N/A if (!mail_index_lookup_seq_range(view, uid1, uid2, &seq1, &seq2))
2276N/A return 1;
2276N/A
2276N/A mail_index_modseq_update_keyword(ctx->modseq_ctx, keyword_idx,
2276N/A seq1, seq2);
2276N/A
2276N/A data_offset = keyword_idx / CHAR_BIT;
2276N/A data_mask = 1 << (keyword_idx % CHAR_BIT);
2276N/A
1765N/A i_assert(data_offset < ext->record_size);
49N/A data_offset += ext->record_offset;
2276N/A
2276N/A i_assert(data_offset >= sizeof(struct mail_index_record));
2276N/A
2276N/A switch (type) {
2276N/A case MODIFY_ADD:
2276N/A for (; seq1 <= seq2; seq1++) {
2276N/A rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq1);
2276N/A data = PTR_OFFSET(rec, data_offset);
3356N/A *data |= data_mask;
2276N/A }
2276N/A break;
2276N/A case MODIFY_REMOVE:
2276N/A data_mask = ~data_mask;
2276N/A for (; seq1 <= seq2; seq1++) {
2276N/A rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq1);
2276N/A data = PTR_OFFSET(rec, data_offset);
2276N/A *data &= data_mask;
2276N/A }
2276N/A break;
2276N/A default:
2276N/A i_unreached();
2276N/A }
2276N/A return 1;
2276N/A}
2276N/A
2276N/Aint mail_index_sync_keywords(struct mail_index_sync_map_ctx *ctx,
2276N/A const struct mail_transaction_header *hdr,
2276N/A const struct mail_transaction_keyword_update *rec)
2276N/A{
2276N/A struct mail_index_view *view = ctx->view;
2276N/A const char *keyword_name;
2276N/A const struct mail_index_ext *ext;
2276N/A const uint32_t *uid, *end;
2276N/A uint32_t seqset_offset, ext_map_idx;
2276N/A unsigned int keyword_idx;
2276N/A int ret;
2276N/A
2276N/A i_assert(rec->name_size > 0);
2276N/A
2276N/A seqset_offset = sizeof(*rec) + rec->name_size;
2276N/A if ((seqset_offset % 4) != 0)
2276N/A seqset_offset += 4 - (seqset_offset % 4);
2276N/A i_assert(seqset_offset < hdr->size);
2276N/A
2276N/A uid = CONST_PTR_OFFSET(rec, seqset_offset);
2276N/A end = CONST_PTR_OFFSET(rec, hdr->size);
2276N/A
2276N/A keyword_name = t_strndup(rec + 1, rec->name_size);
2276N/A if (!keyword_lookup(ctx, keyword_name, &keyword_idx))
3158N/A keywords_header_add(ctx, keyword_name, &keyword_idx);
2276N/A
2276N/A /* if the keyword wasn't found, the "keywords" extension was created.
2276N/A if it was found, the record size should already be correct, but
2276N/A in case it isn't just fix it ourself. */
2276N/A if (!mail_index_map_lookup_ext(view->map, MAIL_INDEX_EXT_KEYWORDS,
2276N/A &ext_map_idx))
2276N/A i_unreached();
2276N/A
2276N/A ext = array_idx(&view->map->extensions, ext_map_idx);
2276N/A if (keyword_idx / CHAR_BIT >= ext->record_size) {
2276N/A if (rec->modify_type == MODIFY_REMOVE) {
2639N/A /* nothing to do */
2276N/A return 1;
2276N/A }
2276N/A
2276N/A /* grow the record size */
2276N/A keywords_ext_register(ctx, ext_map_idx, ext->reset_id,
2276N/A ext->hdr_size,
2276N/A array_count(&view->map->keyword_idx_map));
2276N/A if (!mail_index_map_lookup_ext(view->map,
2276N/A MAIL_INDEX_EXT_KEYWORDS,
2276N/A &ext_map_idx))
2276N/A i_unreached();
2276N/A ext = array_idx(&view->map->extensions, ext_map_idx);
38N/A }
38N/A
38N/A while (uid+2 <= end) {
42N/A ret = keywords_update_records(ctx, ext, keyword_idx,
38N/A rec->modify_type,
38N/A uid[0], uid[1]);
38N/A 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;
}