diff options
Diffstat (limited to 'src/lib-storage/list/mailbox-list-index.c')
-rw-r--r-- | src/lib-storage/list/mailbox-list-index.c | 1234 |
1 files changed, 1234 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-index.c b/src/lib-storage/list/mailbox-list-index.c new file mode 100644 index 0000000..9c8a718 --- /dev/null +++ b/src/lib-storage/list/mailbox-list-index.c @@ -0,0 +1,1234 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "hash.h" +#include "str.h" +#include "mail-index-view-private.h" +#include "mail-storage-hooks.h" +#include "mail-storage-private.h" +#include "mailbox-list-index-storage.h" +#include "mailbox-list-index-sync.h" + +#define MAILBOX_LIST_INDEX_REFRESH_DELAY_MSECS 1000 + +/* dovecot.list.index.log doesn't have to be kept for that long. */ +#define MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_SIZE (8*1024) +#define MAILBOX_LIST_INDEX_LOG_ROTATE_MAX_SIZE (64*1024) +#define MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_AGE_SECS (5*60) +#define MAILBOX_LIST_INDEX_LOG2_MAX_AGE_SECS (10*60) + +static void mailbox_list_index_init_finish(struct mailbox_list *list); + +struct mailbox_list_index_module mailbox_list_index_module = + MODULE_CONTEXT_INIT(&mailbox_list_module_register); + +void mailbox_list_index_set_index_error(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + mailbox_list_set_internal_error(list); + mail_index_reset_error(ilist->index); +} + +static void mailbox_list_index_init_pool(struct mailbox_list_index *ilist) +{ + ilist->mailbox_pool = pool_alloconly_create("mailbox list index", 4096); + hash_table_create_direct(&ilist->mailbox_names, ilist->mailbox_pool, 0); + hash_table_create_direct(&ilist->mailbox_hash, ilist->mailbox_pool, 0); +} + +void mailbox_list_index_reset(struct mailbox_list_index *ilist) +{ + hash_table_destroy(&ilist->mailbox_names); + hash_table_destroy(&ilist->mailbox_hash); + pool_unref(&ilist->mailbox_pool); + + ilist->mailbox_tree = NULL; + ilist->highest_name_id = 0; + ilist->sync_log_file_seq = 0; + ilist->sync_log_file_offset = 0; + + mailbox_list_index_init_pool(ilist); +} + +int mailbox_list_index_index_open(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + const struct mail_storage_settings *set = list->mail_set; + enum mail_index_open_flags index_flags; + unsigned int lock_timeout; + + if (ilist->opened) + return 0; + + if (mailbox_list_mkdir_missing_list_index_root(list) < 0) + return -1; + + i_assert(ilist->index != NULL); + + index_flags = mail_storage_settings_to_index_flags(set); + if (strcmp(list->name, MAILBOX_LIST_NAME_INDEX) == 0) { + /* LAYOUT=index. this is the only location for the mailbox + data, so we must never move it into memory. */ + index_flags |= MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY; + } + lock_timeout = set->mail_max_lock_timeout == 0 ? UINT_MAX : + set->mail_max_lock_timeout; + + if (!mail_index_use_existing_permissions(ilist->index)) { + struct mailbox_permissions perm; + + mailbox_list_get_root_permissions(list, &perm); + mail_index_set_permissions(ilist->index, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + } + const struct mail_index_optimization_settings optimize_set = { + .log = { + .min_size = MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_SIZE, + .max_size = MAILBOX_LIST_INDEX_LOG_ROTATE_MAX_SIZE, + .min_age_secs = MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_AGE_SECS, + .log2_max_age_secs = MAILBOX_LIST_INDEX_LOG2_MAX_AGE_SECS, + }, + }; + mail_index_set_optimization_settings(ilist->index, &optimize_set); + + mail_index_set_fsync_mode(ilist->index, set->parsed_fsync_mode, 0); + mail_index_set_lock_method(ilist->index, set->parsed_lock_method, + lock_timeout); + if (mail_index_open_or_create(ilist->index, index_flags) < 0) { + if (mail_index_move_to_memory(ilist->index) < 0) { + /* try opening once more. it should be created + directly into memory now, except if it fails with + LAYOUT=index backend. */ + if (mail_index_open_or_create(ilist->index, + index_flags) < 0) { + mailbox_list_set_internal_error(list); + return -1; + } + } + } + ilist->opened = TRUE; + return 0; +} + +struct mailbox_list_index_node * +mailbox_list_index_node_find_sibling(const struct mailbox_list *list, + struct mailbox_list_index_node *node, + const char *name) +{ + mailbox_list_name_unescape(&name, list->set.storage_name_escape_char); + + while (node != NULL) { + if (strcmp(node->raw_name, name) == 0) + return node; + node = node->next; + } + return NULL; +} + +static struct mailbox_list_index_node * +mailbox_list_index_lookup_real(struct mailbox_list *list, const char *name) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_index_node *node = ilist->mailbox_tree; + const char *const *path; + unsigned int i; + char sep[2]; + + if (*name == '\0') + return mailbox_list_index_node_find_sibling(list, node, ""); + + sep[0] = mailbox_list_get_hierarchy_sep(list); sep[1] = '\0'; + path = t_strsplit(name, sep); + for (i = 0;; i++) { + node = mailbox_list_index_node_find_sibling(list, node, path[i]); + if (node == NULL || path[i+1] == NULL) + break; + node = node->children; + } + return node; +} + +struct mailbox_list_index_node * +mailbox_list_index_lookup(struct mailbox_list *list, const char *name) +{ + struct mailbox_list_index_node *node; + + T_BEGIN { + node = mailbox_list_index_lookup_real(list, name); + } T_END; + return node; +} + +struct mailbox_list_index_node * +mailbox_list_index_lookup_uid(struct mailbox_list_index *ilist, uint32_t uid) +{ + return hash_table_lookup(ilist->mailbox_hash, POINTER_CAST(uid)); +} + +void mailbox_list_index_node_get_path(const struct mailbox_list_index_node *node, + char sep, string_t *str) +{ + if (node->parent != NULL) { + mailbox_list_index_node_get_path(node->parent, sep, str); + str_append_c(str, sep); + } + str_append(str, node->raw_name); +} + +void mailbox_list_index_node_unlink(struct mailbox_list_index *ilist, + struct mailbox_list_index_node *node) +{ + struct mailbox_list_index_node **prev; + + prev = node->parent == NULL ? + &ilist->mailbox_tree : &node->parent->children; + + while (*prev != node) + prev = &(*prev)->next; + *prev = node->next; +} + +static int mailbox_list_index_parse_header(struct mailbox_list_index *ilist, + struct mail_index_view *view) +{ + const void *data, *name_start, *p; + size_t i, len, size; + uint32_t id, prev_id = 0; + string_t *str; + char *name; + int ret = 0; + + mail_index_map_get_header_ext(view, view->map, ilist->ext_id, &data, &size); + if (size == 0) + return 0; + + str = t_str_new(128); + for (i = sizeof(struct mailbox_list_index_header); i < size; ) { + /* get id */ + if (i + sizeof(id) > size) + return -1; + memcpy(&id, CONST_PTR_OFFSET(data, i), sizeof(id)); + i += sizeof(id); + + if (id <= prev_id) { + /* allow extra space in the end as long as last id=0 */ + return id == 0 ? 0 : -1; + } + prev_id = id; + + /* get name */ + p = memchr(CONST_PTR_OFFSET(data, i), '\0', size-i); + if (p == NULL) + return -1; + name_start = CONST_PTR_OFFSET(data, i); + len = (const char *)p - (const char *)name_start; + + if (uni_utf8_get_valid_data(name_start, len, str)) { + name = p_strndup(ilist->mailbox_pool, name_start, len); + } else { + /* corrupted index. fix the name. */ + name = p_strdup(ilist->mailbox_pool, str_c(str)); + str_truncate(str, 0); + ret = -1; + } + + i += len + 1; + + /* add id => name to hash table */ + hash_table_insert(ilist->mailbox_names, POINTER_CAST(id), name); + ilist->highest_name_id = id; + } + i_assert(i == size); + return ret; +} + +static void +mailbox_list_index_generate_name(struct mailbox_list_index *ilist, + struct mailbox_list_index_node *node, + const char *prefix) +{ + guid_128_t guid; + char *name; + + i_assert(node->name_id != 0); + + guid_128_generate(guid); + name = p_strdup_printf(ilist->mailbox_pool, "%s%s", prefix, + guid_128_to_string(guid)); + node->raw_name = name; + node->flags |= MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME; + + hash_table_insert(ilist->mailbox_names, + POINTER_CAST(node->name_id), name); + if (ilist->highest_name_id < node->name_id) + ilist->highest_name_id = node->name_id; +} + +static int mailbox_list_index_node_cmp(const struct mailbox_list_index_node *n1, + const struct mailbox_list_index_node *n2) +{ + return n1->parent == n2->parent && + strcmp(n1->raw_name, n2->raw_name) == 0 ? 0 : -1; +} + +static unsigned int +mailbox_list_index_node_hash(const struct mailbox_list_index_node *node) +{ + return str_hash(node->raw_name) ^ + POINTER_CAST_TO(node->parent, unsigned int); +} + +static bool node_has_parent(const struct mailbox_list_index_node *parent, + const struct mailbox_list_index_node *node) +{ + const struct mailbox_list_index_node *n; + + for (n = parent; n != NULL; n = n->parent) { + if (n == node) + return TRUE; + } + return FALSE; +} + +static int mailbox_list_index_parse_records(struct mailbox_list_index *ilist, + struct mail_index_view *view, + const char **error_r) +{ + struct mailbox_list_index_node *node, *parent; + HASH_TABLE(struct mailbox_list_index_node *, + struct mailbox_list_index_node *) duplicate_hash; + const struct mail_index_record *rec; + const struct mailbox_list_index_record *irec; + const void *data; + bool expunged; + uint32_t seq, uid, count; + HASH_TABLE(uint8_t *, struct mailbox_list_index_node *) duplicate_guid; + + *error_r = NULL; + + pool_t dup_pool = + pool_alloconly_create(MEMPOOL_GROWING"duplicate pool", 2048); + hash_table_create(&duplicate_hash, dup_pool, 0, + mailbox_list_index_node_hash, + mailbox_list_index_node_cmp); + count = mail_index_view_get_messages_count(view); + if (!ilist->has_backing_store) + hash_table_create(&duplicate_guid, dup_pool, 0, guid_128_hash, + guid_128_cmp); + + for (seq = 1; seq <= count; seq++) { + node = p_new(ilist->mailbox_pool, + struct mailbox_list_index_node, 1); + rec = mail_index_lookup(view, seq); + node->uid = rec->uid; + node->flags = rec->flags; + + mail_index_lookup_ext(view, seq, ilist->ext_id, + &data, &expunged); + if (data == NULL) { + *error_r = "Missing list extension data"; + /* list index is missing, no point trying + to do second scan either */ + count = 0; + break; + } + irec = data; + + node->name_id = irec->name_id; + if (node->name_id == 0) { + /* invalid name_id - assign a new one */ + node->name_id = ++ilist->highest_name_id; + node->corrupted_ext = TRUE; + } + node->raw_name = hash_table_lookup(ilist->mailbox_names, + POINTER_CAST(irec->name_id)); + if (node->raw_name == NULL) { + *error_r = t_strdup_printf( + "name_id=%u not in index header", irec->name_id); + if (ilist->has_backing_store) + break; + /* generate a new name and use it */ + mailbox_list_index_generate_name(ilist, node, "unknown-"); + } + + if (!ilist->has_backing_store && guid_128_is_empty(irec->guid) && + (rec->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT | + MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) { + /* no backing store and mailbox has no GUID. + it can't be selectable, but the flag is missing. */ + node->flags |= MAILBOX_LIST_INDEX_FLAG_NOSELECT; + *error_r = t_strdup_printf( + "mailbox '%s' (uid=%u) is missing GUID - " + "marking it non-selectable", node->raw_name, node->uid); + node->corrupted_flags = TRUE; + } + if (!ilist->has_backing_store && !guid_128_is_empty(irec->guid) && + (rec->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT | + MAILBOX_LIST_INDEX_FLAG_NOSELECT)) != 0) { + node->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_NONEXISTENT | MAILBOX_LIST_INDEX_FLAG_NOSELECT); + *error_r = t_strdup_printf( + "non-selectable mailbox '%s' (uid=%u) already has GUID - " + "marking it selectable", node->raw_name, node->uid); + node->corrupted_flags = TRUE; + } + + if (!ilist->has_backing_store && !guid_128_is_empty(irec->guid)) { + struct mailbox_list_index_node *dup_node; + uint8_t *guid_p = p_memdup(dup_pool, irec->guid, + sizeof(guid_128_t)); + if ((dup_node = hash_table_lookup(duplicate_guid, guid_p)) != NULL) { + *error_r = t_strdup_printf( + "duplicate GUID %s for mailbox '%s' and '%s'", + guid_128_to_string(guid_p), + node->raw_name, + dup_node->raw_name); + node->corrupted_ext = TRUE; + ilist->corrupted_names_or_parents = TRUE; + ilist->call_corruption_callback = TRUE; + } else { + hash_table_insert(duplicate_guid, guid_p, node); + } + } + + hash_table_insert(ilist->mailbox_hash, + POINTER_CAST(node->uid), node); + } + + /* do a second scan to create the actual mailbox tree hierarchy. + this is needed because the parent_uid may be smaller or higher than + the current node's uid */ + if (*error_r != NULL && ilist->has_backing_store) + count = 0; + for (seq = 1; seq <= count; seq++) { + mail_index_lookup_uid(view, seq, &uid); + mail_index_lookup_ext(view, seq, ilist->ext_id, + &data, &expunged); + irec = data; + + node = mailbox_list_index_lookup_uid(ilist, uid); + i_assert(node != NULL); + + if (irec->parent_uid != 0) { + /* node should have a parent */ + parent = mailbox_list_index_lookup_uid(ilist, + irec->parent_uid); + if (parent == NULL) { + *error_r = t_strdup_printf( + "parent_uid=%u points to nonexistent record", + irec->parent_uid); + if (ilist->has_backing_store) + break; + /* just place it under the root */ + node->corrupted_ext = TRUE; + } else if (node_has_parent(parent, node)) { + *error_r = t_strdup_printf( + "parent_uid=%u loops to node itself (%s)", + uid, node->raw_name); + if (ilist->has_backing_store) + break; + /* just place it under the root */ + node->corrupted_ext = TRUE; + } else { + node->parent = parent; + node->next = parent->children; + parent->children = node; + } + } else if (strcasecmp(node->raw_name, "INBOX") == 0) { + ilist->rebuild_on_missing_inbox = FALSE; + } + if (hash_table_lookup(duplicate_hash, node) == NULL) + hash_table_insert(duplicate_hash, node, node); + else { + const char *old_name = node->raw_name; + + if (ilist->has_backing_store) { + *error_r = t_strdup_printf( + "Duplicate mailbox '%s' in index", + node->raw_name); + break; + } + + /* we have only the mailbox list index and this node + may have a different GUID, so rename it. */ + node->corrupted_ext = TRUE; + node->name_id = ++ilist->highest_name_id; + mailbox_list_index_generate_name(ilist, node, + t_strconcat(node->raw_name, "-duplicate-", NULL)); + *error_r = t_strdup_printf( + "Duplicate mailbox '%s' in index, renaming to %s", + old_name, node->raw_name); + } + if (node->parent == NULL) { + node->next = ilist->mailbox_tree; + ilist->mailbox_tree = node; + } + } + hash_table_destroy(&duplicate_hash); + if (!ilist->has_backing_store) + hash_table_destroy(&duplicate_guid); + pool_unref(&dup_pool); + return *error_r == NULL ? 0 : -1; +} + +int mailbox_list_index_parse(struct mailbox_list *list, + struct mail_index_view *view, bool force) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + const struct mail_index_header *hdr; + const char *error; + + hdr = mail_index_get_header(view); + if (!force && + hdr->log_file_seq == ilist->sync_log_file_seq && + hdr->log_file_head_offset == ilist->sync_log_file_offset) { + /* nothing changed */ + return 0; + } + if ((hdr->flags & MAIL_INDEX_HDR_FLAG_FSCKD) != 0) { + mailbox_list_set_critical(list, + "Mailbox list index was marked as fsck'd %s", ilist->path); + ilist->call_corruption_callback = TRUE; + } + + mailbox_list_index_reset(ilist); + ilist->sync_log_file_seq = hdr->log_file_seq; + ilist->sync_log_file_offset = hdr->log_file_head_offset; + + if (mailbox_list_index_parse_header(ilist, view) < 0) { + mailbox_list_set_critical(list, + "Corrupted mailbox list index header %s", ilist->path); + if (ilist->has_backing_store) { + mail_index_mark_corrupted(ilist->index); + return -1; + } + ilist->call_corruption_callback = TRUE; + ilist->corrupted_names_or_parents = TRUE; + } + if (mailbox_list_index_parse_records(ilist, view, &error) < 0) { + mailbox_list_set_critical(list, + "Corrupted mailbox list index %s: %s", + ilist->path, error); + if (ilist->has_backing_store) { + mail_index_mark_corrupted(ilist->index); + return -1; + } + ilist->call_corruption_callback = TRUE; + ilist->corrupted_names_or_parents = TRUE; + } + return 0; +} + +const unsigned char * +mailbox_name_hdr_encode(struct mailbox_list *list, const char *storage_name, + size_t *name_len_r) +{ + const char sep[] = { + mailbox_list_get_hierarchy_sep(list), + '\0' + }; + const char **name_parts = + (const char **)p_strsplit(unsafe_data_stack_pool, storage_name, sep); + if (list->set.storage_name_escape_char != '\0') { + for (unsigned int i = 0; name_parts[i] != NULL; i++) { + mailbox_list_name_unescape(&name_parts[i], + list->set.storage_name_escape_char); + } + } + + string_t *str = t_str_new(64); + str_append(str, name_parts[0]); + for (unsigned int i = 1; name_parts[i] != NULL; i++) { + str_append_c(str, '\0'); + str_append(str, name_parts[i]); + } + *name_len_r = str_len(str); + return str_data(str); +} + +const char * +mailbox_name_hdr_decode_storage_name(struct mailbox_list *list, + const unsigned char *name_hdr, + size_t name_hdr_size) +{ + const char list_sep = mailbox_list_get_hierarchy_sep(list); + const char escape_char = list->set.storage_name_escape_char; + string_t *storage_name = t_str_new(name_hdr_size); + while (name_hdr_size > 0) { + const unsigned char *p = memchr(name_hdr, '\0', name_hdr_size); + size_t name_part_len; + if (p == NULL) { + name_part_len = name_hdr_size; + name_hdr_size = 0; + } else { + name_part_len = p - name_hdr; + i_assert(name_hdr_size > name_part_len); + name_hdr_size -= name_part_len + 1; + } + + if (escape_char == '\0') + str_append_data(storage_name, name_hdr, name_part_len); + else { + const char *name_part = + t_strndup(name_hdr, name_part_len); + str_append(storage_name, + mailbox_list_escape_name_params(name_part, + "", '\0', list_sep, escape_char, + list->set.maildir_name)); + } + + if (p != NULL) { + name_hdr += name_part_len + 1; + str_append_c(storage_name, list_sep); + } + } + return str_c(storage_name); +} + +bool mailbox_list_index_need_refresh(struct mailbox_list_index *ilist, + struct mail_index_view *view) +{ + const struct mailbox_list_index_header *hdr; + const void *data; + size_t size; + + if (!ilist->has_backing_store) + return FALSE; + + mail_index_get_header_ext(view, ilist->ext_id, &data, &size); + hdr = data; + return hdr != NULL && hdr->refresh_flag != 0; +} + +int mailbox_list_index_refresh(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + if (ilist->syncing) + return 0; + if (ilist->last_refresh_timeval.tv_usec == ioloop_timeval.tv_usec && + ilist->last_refresh_timeval.tv_sec == ioloop_timeval.tv_sec) { + /* we haven't been to ioloop since last refresh, skip checking + it. when we're accessing many mailboxes at once (e.g. + opening a virtual mailbox) we don't want to stat/read the + index every single time. */ + return 0; + } + + return mailbox_list_index_refresh_force(list); +} + +int mailbox_list_index_refresh_force(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + struct mail_index_view *view; + int ret; + bool refresh; + + i_assert(!ilist->syncing); + + ilist->last_refresh_timeval = ioloop_timeval; + if (mailbox_list_index_index_open(list) < 0) + return -1; + if (mail_index_refresh(ilist->index) < 0) { + mailbox_list_index_set_index_error(list); + return -1; + } + + view = mail_index_view_open(ilist->index); + if ((refresh = mailbox_list_index_need_refresh(ilist, view)) || + ilist->mailbox_tree == NULL) { + /* refresh list of mailboxes */ + ret = mailbox_list_index_sync(list, refresh); + } else { + ret = mailbox_list_index_parse(list, view, FALSE); + } + mail_index_view_close(&view); + + if (mailbox_list_index_handle_corruption(list) < 0) { + const char *errstr; + enum mail_error error; + + errstr = mailbox_list_get_last_internal_error(list, &error); + mailbox_list_set_error(list, error, t_strdup_printf( + "Failed to rebuild mailbox list index: %s", errstr)); + ret = -1; + } + return ret; +} + +static void mailbox_list_index_refresh_timeout(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + timeout_remove(&ilist->to_refresh); + (void)mailbox_list_index_refresh(list); +} + +void mailbox_list_index_refresh_later(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_index_header new_hdr; + struct mail_index_view *view; + struct mail_index_transaction *trans; + + memset(&ilist->last_refresh_timeval, 0, + sizeof(ilist->last_refresh_timeval)); + + if (!ilist->has_backing_store) + return; + + (void)mailbox_list_index_index_open(list); + + view = mail_index_view_open(ilist->index); + if (!mailbox_list_index_need_refresh(ilist, view)) { + new_hdr.refresh_flag = 1; + + trans = mail_index_transaction_begin(view, + MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); + mail_index_update_header_ext(trans, ilist->ext_id, + offsetof(struct mailbox_list_index_header, refresh_flag), + &new_hdr.refresh_flag, sizeof(new_hdr.refresh_flag)); + if (mail_index_transaction_commit(&trans) < 0) + mail_index_mark_corrupted(ilist->index); + + } + mail_index_view_close(&view); + + if (ilist->to_refresh == NULL) { + ilist->to_refresh = + timeout_add(MAILBOX_LIST_INDEX_REFRESH_DELAY_MSECS, + mailbox_list_index_refresh_timeout, list); + } +} + +static int +list_handle_corruption_locked(struct mailbox_list *list, + enum mail_storage_list_index_rebuild_reason reason) +{ + struct mail_storage *storage; + const char *errstr; + enum mail_error error; + + array_foreach_elem(&list->ns->all_storages, storage) { + if (storage->v.list_index_rebuild == NULL) + continue; + + if (storage->v.list_index_rebuild(storage, reason) < 0) { + errstr = mail_storage_get_last_internal_error(storage, &error); + mailbox_list_set_error(list, error, errstr); + return -1; + } else { + /* FIXME: implement a generic handler that + just lists mailbox directories in filesystem + and adds the missing ones to the index. */ + } + } + return mailbox_list_index_set_uncorrupted(list); +} + +int mailbox_list_index_handle_corruption(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + enum mail_storage_list_index_rebuild_reason reason; + int ret; + + if (ilist->call_corruption_callback) + reason = MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_CORRUPTED; + else if (ilist->rebuild_on_missing_inbox) + reason = MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_NO_INBOX; + else + return 0; + + if (list->disable_rebuild_on_corruption) + return 0; + + /* make sure we don't recurse */ + if (ilist->handling_corruption) + return 0; + ilist->handling_corruption = TRUE; + + /* Perform the rebuilding locked. Note that if we're here because + INBOX wasn't found, this may be because another process is in the + middle of creating it. Waiting for the lock here makes sure that + we don't start rebuilding before it's finished. In that case the + rebuild is a bit unnecessary, but harmless (and avoiding the rebuild + just adds extra code complexity). */ + if (mailbox_list_lock(list) < 0) + ret = -1; + else { + ret = list_handle_corruption_locked(list, reason); + mailbox_list_unlock(list); + } + ilist->handling_corruption = FALSE; + return ret; +} + +int mailbox_list_index_set_uncorrupted(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_index_sync_context *sync_ctx; + + ilist->call_corruption_callback = FALSE; + ilist->rebuild_on_missing_inbox = FALSE; + + if (mailbox_list_index_sync_begin(list, &sync_ctx) < 0) + return -1; + + mail_index_unset_fscked(sync_ctx->trans); + return mailbox_list_index_sync_end(&sync_ctx, TRUE); +} + +bool mailbox_list_index_get_index(struct mailbox_list *list, + struct mail_index **index_r) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list); + + if (ilist == NULL) + return FALSE; + *index_r = ilist->index; + return TRUE; +} + +int mailbox_list_index_view_open(struct mailbox *box, bool require_refreshed, + struct mail_index_view **view_r, + uint32_t *seq_r) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list); + struct mailbox_list_index_node *node; + struct mail_index_view *view; + const char *reason = NULL; + uint32_t seq; + int ret; + + if (ilist == NULL) { + /* mailbox list indexes aren't enabled */ + return 0; + } + if (MAILBOX_IS_NEVER_IN_INDEX(box) && require_refreshed) { + /* Optimization: Caller wants the list index to be up-to-date + for this mailbox, but this mailbox isn't updated to the list + index at all. */ + return 0; + } + if (mailbox_list_index_refresh(box->list) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + + node = mailbox_list_index_lookup(box->list, box->name); + if (node == NULL) { + /* mailbox not found */ + e_debug(box->event, "Couldn't open mailbox in list index: " + "Mailbox not found"); + return 0; + } + + view = mail_index_view_open(ilist->index); + if (mailbox_list_index_need_refresh(ilist, view)) { + /* mailbox_list_index_refresh_later() was called. + Can't trust the index's contents. */ + reason = "Refresh-flag set"; + ret = 1; + } else if (!mail_index_lookup_seq(view, node->uid, &seq)) { + /* our in-memory tree is out of sync */ + ret = 1; + reason = "Mailbox no longer exists in index"; + } else if (!require_refreshed) { + /* this operation doesn't need the index to be up-to-date */ + ret = 0; + } else { + ret = box->v.list_index_has_changed == NULL ? 0 : + box->v.list_index_has_changed(box, view, seq, FALSE, + &reason); + i_assert(ret <= 0 || reason != NULL); + } + + if (ret != 0) { + /* error / mailbox has changed. we'll need to sync it. */ + if (ret < 0) + mailbox_list_index_refresh_later(box->list); + else { + i_assert(reason != NULL); + e_debug(box->event, + "Couldn't open mailbox in list index: %s", + reason); + ilist->index_last_check_changed = TRUE; + } + mail_index_view_close(&view); + return ret < 0 ? -1 : 0; + } + + *view_r = view; + *seq_r = seq; + return 1; +} + +static void mailbox_list_index_deinit(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + timeout_remove(&ilist->to_refresh); + if (ilist->index != NULL) { + hash_table_destroy(&ilist->mailbox_hash); + hash_table_destroy(&ilist->mailbox_names); + pool_unref(&ilist->mailbox_pool); + if (ilist->opened) + mail_index_close(ilist->index); + mail_index_free(&ilist->index); + } + ilist->module_ctx.super.deinit(list); +} + +static void +mailbox_list_index_refresh_if_found(struct mailbox_list *list, + const char *name, bool selectable) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + struct mailbox_list_index_node *node; + + if (ilist->syncing) + return; + + mailbox_list_last_error_push(list); + (void)mailbox_list_index_refresh_force(list); + node = mailbox_list_index_lookup(list, name); + if (node != NULL && + (!selectable || + (node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT | + MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0)) { + /* index is out of sync - refresh */ + mailbox_list_index_refresh_later(list); + } + mailbox_list_last_error_pop(list); +} + +static void mailbox_list_index_refresh_if_not_found(struct mailbox_list *list, + const char *name) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + if (ilist->syncing) + return; + + mailbox_list_last_error_push(list); + (void)mailbox_list_index_refresh_force(list); + if (mailbox_list_index_lookup(list, name) == NULL) { + /* index is out of sync - refresh */ + mailbox_list_index_refresh_later(list); + } + mailbox_list_last_error_pop(list); +} + +static int mailbox_list_index_open_mailbox(struct mailbox *box) +{ + struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box); + + if (ibox->module_ctx.super.open(box) < 0) { + if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) + mailbox_list_index_refresh_if_found(box->list, box->name, TRUE); + return -1; + } + return 0; +} + +static int +mailbox_list_index_create_mailbox(struct mailbox *box, + const struct mailbox_update *update, + bool directory) +{ + struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box); + + if (ibox->module_ctx.super.create_box(box, update, directory) < 0) { + if (mailbox_get_last_mail_error(box) == MAIL_ERROR_EXISTS) + mailbox_list_index_refresh_if_not_found(box->list, box->name); + return -1; + } + mailbox_list_index_refresh_later(box->list); + return 0; +} + +static int +mailbox_list_index_update_mailbox(struct mailbox *box, + const struct mailbox_update *update) +{ + struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box); + + if (ibox->module_ctx.super.update_box(box, update) < 0) { + if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) + mailbox_list_index_refresh_if_found(box->list, box->name, TRUE); + return -1; + } + + mailbox_list_index_update_mailbox_index(box, update); + return 0; +} + +static int +mailbox_list_index_delete_mailbox(struct mailbox_list *list, const char *name) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + if (ilist->module_ctx.super.delete_mailbox(list, name) < 0) { + if (mailbox_list_get_last_mail_error(list) == MAIL_ERROR_NOTFOUND) + mailbox_list_index_refresh_if_found(list, name, FALSE); + return -1; + } + mailbox_list_index_refresh_later(list); + return 0; +} + +static int +mailbox_list_index_delete_dir(struct mailbox_list *list, const char *name) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list); + + if (ilist->module_ctx.super.delete_dir(list, name) < 0) { + if (mailbox_list_get_last_mail_error(list) == MAIL_ERROR_NOTFOUND) + mailbox_list_index_refresh_if_found(list, name, FALSE); + return -1; + } + mailbox_list_index_refresh_later(list); + return 0; +} + +static int +mailbox_list_index_rename_mailbox(struct mailbox_list *oldlist, + const char *oldname, + struct mailbox_list *newlist, + const char *newname) +{ + struct mailbox_list_index *oldilist = INDEX_LIST_CONTEXT_REQUIRE(oldlist); + + if (oldilist->module_ctx.super.rename_mailbox(oldlist, oldname, + newlist, newname) < 0) { + if (mailbox_list_get_last_mail_error(oldlist) == MAIL_ERROR_NOTFOUND) + mailbox_list_index_refresh_if_found(oldlist, oldname, FALSE); + if (mailbox_list_get_last_mail_error(newlist) == MAIL_ERROR_EXISTS) + mailbox_list_index_refresh_if_not_found(newlist, newname); + return -1; + } + mailbox_list_index_refresh_later(oldlist); + if (oldlist != newlist) + mailbox_list_index_refresh_later(newlist); + return 0; +} + +static int +mailbox_list_index_set_subscribed(struct mailbox_list *_list, + const char *name, bool set) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_list); + struct mail_index_view *view; + struct mail_index_transaction *trans; + const void *data; + size_t size; + uint32_t counter; + + if (ilist->module_ctx.super.set_subscribed(_list, name, set) < 0) + return -1; + + /* update the "subscriptions changed" counter/timestamp. its purpose + is to trigger NOTIFY watcher to handle SubscriptionChange events */ + if (mailbox_list_index_index_open(_list) < 0) + return -1; + view = mail_index_view_open(ilist->index); + mail_index_get_header_ext(view, ilist->subs_hdr_ext_id, &data, &size); + if (size != sizeof(counter)) + counter = ioloop_time; + else { + memcpy(&counter, data, size); + if (++counter < (uint32_t)ioloop_time) + counter = ioloop_time; + } + + trans = mail_index_transaction_begin(view, + MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); + mail_index_update_header_ext(trans, ilist->subs_hdr_ext_id, + 0, &counter, sizeof(counter)); + (void)mail_index_transaction_commit(&trans); + mail_index_view_close(&view); + return 0; +} + +static bool mailbox_list_index_is_enabled(struct mailbox_list *list) +{ + if (!list->mail_set->mailbox_list_index || + (list->props & MAILBOX_LIST_PROP_NO_LIST_INDEX) != 0) + return FALSE; + + i_assert(list->set.list_index_fname != NULL); + if (list->set.list_index_fname[0] == '\0') + return FALSE; + return TRUE; +} + +static void mailbox_list_index_created(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct mailbox_list_index *ilist; + bool has_backing_store; + + /* layout=index doesn't have any backing store */ + has_backing_store = strcmp(list->name, MAILBOX_LIST_NAME_INDEX) != 0; + + if (!mailbox_list_index_is_enabled(list)) { + /* reserve the module context anyway, so syncing code knows + that the index is disabled */ + i_assert(has_backing_store); + ilist = NULL; + MODULE_CONTEXT_SET(list, mailbox_list_index_module, ilist); + return; + } + + ilist = p_new(list->pool, struct mailbox_list_index, 1); + ilist->module_ctx.super = *v; + list->vlast = &ilist->module_ctx.super; + ilist->has_backing_store = has_backing_store; + ilist->pending_init = TRUE; + + v->deinit = mailbox_list_index_deinit; + v->iter_init = mailbox_list_index_iter_init; + v->iter_deinit = mailbox_list_index_iter_deinit; + v->iter_next = mailbox_list_index_iter_next; + + v->delete_mailbox = mailbox_list_index_delete_mailbox; + v->delete_dir = mailbox_list_index_delete_dir; + v->rename_mailbox = mailbox_list_index_rename_mailbox; + v->set_subscribed = mailbox_list_index_set_subscribed; + + v->notify_init = mailbox_list_index_notify_init; + v->notify_next = mailbox_list_index_notify_next; + v->notify_deinit = mailbox_list_index_notify_deinit; + v->notify_wait = mailbox_list_index_notify_wait; + v->notify_flush = mailbox_list_index_notify_flush; + + MODULE_CONTEXT_SET(list, mailbox_list_index_module, ilist); + + if ((list->flags & MAILBOX_LIST_FLAG_SECONDARY) != 0) { + /* secondary lists aren't accessible via namespaces, so we + need to finish them now. */ + mailbox_list_index_init_finish(list); + } +} + +static void mailbox_list_index_init_finish(struct mailbox_list *list) +{ + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list); + const char *dir; + + if (ilist == NULL || !ilist->pending_init) + return; + ilist->pending_init = FALSE; + + /* we've delayed this part of the initialization so that mbox format + can override the index root directory path */ + if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_LIST_INDEX, + &dir)) { + /* in-memory indexes */ + dir = NULL; + } + i_assert(ilist->has_backing_store || dir != NULL); + + i_assert(list->set.list_index_fname != NULL); + ilist->path = dir == NULL ? "(in-memory mailbox list index)" : + p_strdup_printf(list->pool, "%s/%s", dir, list->set.list_index_fname); + ilist->index = mail_index_alloc(list->ns->user->event, + dir, list->set.list_index_fname); + ilist->rebuild_on_missing_inbox = !ilist->has_backing_store && + (list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0; + + ilist->ext_id = mail_index_ext_register(ilist->index, "list", + sizeof(struct mailbox_list_index_header), + sizeof(struct mailbox_list_index_record), + sizeof(uint32_t)); + ilist->subs_hdr_ext_id = mail_index_ext_register(ilist->index, "subs", + sizeof(uint32_t), 0, + sizeof(uint32_t)); + mailbox_list_index_init_pool(ilist); + + mailbox_list_index_status_init_finish(list); +} + +static void +mailbox_list_index_namespaces_added(struct mail_namespace *namespaces) +{ + struct mail_namespace *ns; + + for (ns = namespaces; ns != NULL; ns = ns->next) + mailbox_list_index_init_finish(ns->list); +} + +static struct mailbox_sync_context * +mailbox_list_index_sync_init(struct mailbox *box, + enum mailbox_sync_flags flags) +{ + struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box); + + mailbox_list_index_status_sync_init(box); + if (!ibox->have_backend) + mailbox_list_index_backend_sync_init(box, flags); + return ibox->module_ctx.super.sync_init(box, flags); +} + +static int +mailbox_list_index_sync_deinit(struct mailbox_sync_context *ctx, + struct mailbox_sync_status *status_r) +{ + struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(ctx->box); + struct mailbox *box = ctx->box; + + if (ibox->module_ctx.super.sync_deinit(ctx, status_r) < 0) + return -1; + ctx = NULL; + + mailbox_list_index_status_sync_deinit(box); + if (ibox->have_backend) + return mailbox_list_index_backend_sync_deinit(box); + else + return 0; +} + +static void mailbox_list_index_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list); + struct index_list_mailbox *ibox; + + if (ilist == NULL) + return; + + ibox = p_new(box->pool, struct index_list_mailbox, 1); + ibox->module_ctx.super = *v; + box->vlast = &ibox->module_ctx.super; + MODULE_CONTEXT_SET(box, index_list_storage_module, ibox); + + /* for layout=index these get overridden */ + v->open = mailbox_list_index_open_mailbox; + v->create_box = mailbox_list_index_create_mailbox; + v->update_box = mailbox_list_index_update_mailbox; + + /* These are used by both status and backend code, but they can't both + be overriding the same function pointer since they share the + super pointer. */ + v->sync_init = mailbox_list_index_sync_init; + v->sync_deinit = mailbox_list_index_sync_deinit; + + mailbox_list_index_status_init_mailbox(v); + ibox->have_backend = mailbox_list_index_backend_init_mailbox(box, v); +} + +static struct mail_storage_hooks mailbox_list_index_hooks = { + .mailbox_list_created = mailbox_list_index_created, + .mail_namespaces_added = mailbox_list_index_namespaces_added, + .mailbox_allocated = mailbox_list_index_mailbox_allocated +}; + +void mailbox_list_index_init(void); /* called in mailbox-list-register.c */ + +void mailbox_list_index_init(void) +{ + mail_storage_hooks_add_internal(&mailbox_list_index_hooks); +} |