mail-index-sync-keywords.c revision 7ded22760598b78ee29f9418eacc0abe3fb51055
/* Copyright (C) 2004 Timo Sirainen */
#include "lib.h"
#include "array.h"
#include "buffer.h"
#include "mail-index-view-private.h"
#include "mail-index-sync-private.h"
#include "mail-transaction-log.h"
static int
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 (!map->keywords_read) {
if (mail_index_map_parse_keywords(map) < 0)
return -1;
}
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 1;
}
}
}
*idx_r = (unsigned int)-1;
return 0;
}
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 = CONST_PTR_OFFSET(map->hdr_base, 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 = buffer_create_dynamic(pool_datastack_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 int 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;
ext_intro_buf =
buffer_create_static_hard(pool_datastack_create(),
sizeof(*u) + sizeof("keywords")-1);
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("keywords");
buffer_append(ext_intro_buf, "keywords", u->name_size);
}
return mail_index_sync_ext_intro(ctx, u);
}
static int
keywords_header_add(struct mail_index_sync_map_ctx *ctx,
const char *keyword_name, unsigned int *keyword_idx_r)
{
struct mail_index_map *map = ctx->view->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;
int ret;
/* 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, "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 */
buf = buffer_create_dynamic(pool_datastack_create(), 512);
kw_hdr = buffer_append_space_unsafe(buf, sizeof(*kw_hdr));
kw_hdr->keywords_count = 1;
keywords_count = kw_hdr->keywords_count;
rec_offset = buf->used;
name_offset_root = rec_offset +
kw_hdr->keywords_count * sizeof(kw_rec);
name_offset = 0;
}
/* add the keyword */
memset(&kw_rec, 0, sizeof(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);
ret = keywords_ext_register(ctx, ext_map_idx,
ext == NULL ? 0 : ext->reset_id,
buf->used, keywords_count);
if (ret <= 0)
return ret;
/* map may have changed */
map = ctx->view->map;
if (!mail_index_map_lookup_ext(map, "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);
map->hdr_base = map->hdr_copy_buf->data;
*keyword_idx_r = keywords_count - 1;
map->keywords_read = FALSE;
return 1;
}
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 != (unsigned int)-1);
mail_index_lookup_uid_range(view, uid1, uid2, &seq1, &seq2);
if (seq1 == 0)
return 1;
mail_index_sync_write_seq_update(ctx, 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 >= sizeof(struct mail_index_record));
switch (type) {
case MODIFY_ADD:
for (seq1--; seq1 < seq2; seq1++) {
rec = MAIL_INDEX_MAP_IDX(view->map, seq1);
data = PTR_OFFSET(rec, data_offset);
*data |= data_mask;
}
break;
case MODIFY_REMOVE:
data_mask = ~data_mask;
for (seq1--; seq1 < seq2; seq1++) {
rec = MAIL_INDEX_MAP_IDX(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)
{
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;
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_name == '\0') {
mail_index_sync_set_corrupted(ctx,
"Trying to use empty keyword");
return -1;
}
if (keyword_lookup(ctx, keyword_name, &keyword_idx) < 0)
return -1;
if (keyword_idx == (unsigned int)-1) {
ret = keywords_header_add(ctx, keyword_name, &keyword_idx);
if (ret <= 0)
return ret;
}
if (!mail_index_map_lookup_ext(ctx->view->map, "keywords",
&ext_map_idx))
ext = NULL;
else
ext = array_idx(&ctx->view->map->extensions, ext_map_idx);
if (ext == NULL || ext->record_size == 0) {
/* nothing to do */
if (rec->modify_type != MODIFY_REMOVE) {
mail_index_sync_set_corrupted(ctx,
"Keyword ext record missing for added keyword");
}
return 1;
}
if (!ctx->view->map->keywords_read) {
if (mail_index_map_parse_keywords(ctx->view->map) < 0)
return -1;
}
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, "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++) {
mail_index_lookup_uid_range(ctx->view, r->uid1, r->uid2,
&seq1, &seq2);
if (seq1 == 0)
continue;
mail_index_sync_write_seq_update(ctx, seq1, seq2);
for (seq1--; seq1 < seq2; seq1++) {
rec = MAIL_INDEX_MAP_IDX(map, seq1);
memset(PTR_OFFSET(rec, ext->record_offset),
0, ext->record_size);
}
}
return 1;
}