Mercurial > dovecot > original-hg > dovecot-1.2
changeset 5686:0d2a6a7f2a1b HEAD
Moved mail index map related code to its own file.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 11 Jun 2007 06:07:40 +0300 |
parents | d36a14c37c22 |
children | d28185a3131a |
files | src/lib-index/Makefile.am src/lib-index/mail-index-map.c src/lib-index/mail-index-private.h src/lib-index/mail-index.c |
diffstat | 4 files changed, 936 insertions(+), 925 deletions(-) [+] |
line wrap: on
line diff
--- a/src/lib-index/Makefile.am Mon Jun 11 05:06:46 2007 +0300 +++ b/src/lib-index/Makefile.am Mon Jun 11 06:07:40 2007 +0300 @@ -17,6 +17,7 @@ mail-index-dummy-view.c \ mail-index-fsck.c \ mail-index-lock.c \ + mail-index-map.c \ mail-index-transaction.c \ mail-index-transaction-view.c \ mail-index-sync.c \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-map.c Mon Jun 11 06:07:40 2007 +0300 @@ -0,0 +1,916 @@ +/* Copyright (C) 2003-2007 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "nfs-workarounds.h" +#include "mmap-util.h" +#include "read-full.h" +#include "mail-index-private.h" +#include "mail-index-sync-private.h" + +static void mail_index_map_init_extbufs(struct mail_index_map *map, + unsigned int initial_count) +{ +#define EXTENSION_NAME_APPROX_LEN 20 +#define EXT_GLOBAL_ALLOC_SIZE \ + ((sizeof(map->extensions) + BUFFER_APPROX_SIZE) * 2) +#define EXT_PER_ALLOC_SIZE \ + (EXTENSION_NAME_APPROX_LEN + \ + sizeof(struct mail_index_ext) + sizeof(uint32_t)) + size_t size; + + if (map->extension_pool == NULL) { + size = EXT_GLOBAL_ALLOC_SIZE + + initial_count * EXT_PER_ALLOC_SIZE; + map->extension_pool = + pool_alloconly_create("map extensions", + nearest_power(size)); + } else { + p_clear(map->extension_pool); + + /* try to use the existing pool's size for initial_count so + we don't grow it unneededly */ + size = p_get_max_easy_alloc_size(map->extension_pool); + if (size > EXT_GLOBAL_ALLOC_SIZE + EXT_PER_ALLOC_SIZE) { + initial_count = (size - EXT_GLOBAL_ALLOC_SIZE) / + EXT_PER_ALLOC_SIZE; + } + } + + p_array_init(&map->extensions, map->extension_pool, initial_count); + p_array_init(&map->ext_id_map, map->extension_pool, initial_count); +} + +uint32_t mail_index_map_lookup_ext(struct mail_index_map *map, const char *name) +{ + const struct mail_index_ext *extensions; + unsigned int i, size; + + if (!array_is_created(&map->extensions)) + return (uint32_t)-1; + + extensions = array_get(&map->extensions, &size); + for (i = 0; i < size; i++) { + if (strcmp(extensions[i].name, name) == 0) + return i; + } + return (uint32_t)-1; +} + +uint32_t +mail_index_map_register_ext(struct mail_index *index, + struct mail_index_map *map, const char *name, + uint32_t hdr_offset, uint32_t hdr_size, + uint32_t record_offset, uint32_t record_size, + uint32_t record_align, uint32_t reset_id) +{ + struct mail_index_ext *ext; + uint32_t idx, empty_idx = (uint32_t)-1; + + if (!array_is_created(&map->extensions)) { + mail_index_map_init_extbufs(map, 5); + idx = 0; + } else { + idx = array_count(&map->extensions); + } + i_assert(mail_index_map_lookup_ext(map, name) == (uint32_t)-1); + + ext = array_append_space(&map->extensions); + ext->name = p_strdup(map->extension_pool, name); + ext->hdr_offset = hdr_offset; + ext->hdr_size = hdr_size; + ext->record_offset = record_offset; + ext->record_size = record_size; + ext->record_align = record_align; + ext->reset_id = reset_id; + + ext->index_idx = mail_index_ext_register(index, name, hdr_size, + record_size, record_align); + + /* Update index ext_id -> map ext_id mapping. Fill non-used + ext_ids with (uint32_t)-1 */ + while (array_count(&map->ext_id_map) < ext->index_idx) + array_append(&map->ext_id_map, &empty_idx, 1); + array_idx_set(&map->ext_id_map, ext->index_idx, &idx); + return idx; +} + +static bool size_check(size_t *size_left, size_t size) +{ + if (size > *size_left) + return FALSE; + *size_left -= size; + return TRUE; +} + +static size_t get_align(size_t name_len) +{ + size_t size = sizeof(struct mail_index_ext_header) + name_len; + return MAIL_INDEX_HEADER_SIZE_ALIGN(size) - size; +} + +static int mail_index_parse_extensions(struct mail_index *index, + struct mail_index_map *map) +{ + const struct mail_index_ext_header *ext_hdr; + unsigned int i, old_count; + const char *name; + uint32_t ext_id, offset, name_offset; + size_t size_left; + + /* extension headers always start from 64bit offsets, so if base header + doesn't happen to be 64bit aligned we'll skip some bytes */ + offset = MAIL_INDEX_HEADER_SIZE_ALIGN(map->hdr.base_header_size); + if (offset >= map->hdr.header_size && map->extension_pool == NULL) { + /* nothing to do, skip allocatations and all */ + return 1; + } + + old_count = array_count(&index->extensions); + mail_index_map_init_extbufs(map, old_count + 5); + + ext_id = (uint32_t)-1; + for (i = 0; i < old_count; i++) + array_append(&map->ext_id_map, &ext_id, 1); + + while (offset < map->hdr.header_size) { + ext_hdr = CONST_PTR_OFFSET(map->hdr_base, offset); + + /* Extension header contains: + - struct mail_index_ext_header + - name (not 0-terminated) + - 64bit alignment padding + - extension header contents + - 64bit alignment padding + */ + size_left = map->hdr.header_size - offset; + if (!size_check(&size_left, sizeof(*ext_hdr)) || + !size_check(&size_left, ext_hdr->name_size) || + !size_check(&size_left, get_align(ext_hdr->name_size)) || + !size_check(&size_left, ext_hdr->hdr_size)) { + mail_index_set_error(index, "Corrupted index file %s: " + "Header extension goes outside header", + index->filepath); + return -1; + } + + offset += sizeof(*ext_hdr); + name_offset = offset; + offset += ext_hdr->name_size + get_align(ext_hdr->name_size); + + t_push(); + name = t_strndup(CONST_PTR_OFFSET(map->hdr_base, name_offset), + ext_hdr->name_size); + + if (mail_index_map_lookup_ext(map, name) != (uint32_t)-1) { + mail_index_set_error(index, "Corrupted index file %s: " + "Duplicate header extension %s", + index->filepath, name); + t_pop(); + return -1; + } + + if (map->hdr.record_size < + ext_hdr->record_offset + ext_hdr->record_size) { + mail_index_set_error(index, "Corrupted index file %s: " + "Record field %s points outside record size " + "(%u < %u+%u)", index->filepath, name, + map->hdr.record_size, + ext_hdr->record_offset, ext_hdr->record_size); + t_pop(); + return -1; + } + + if ((ext_hdr->record_offset % ext_hdr->record_align) != 0 || + (map->hdr.record_size % ext_hdr->record_align) != 0) { + mail_index_set_error(index, "Corrupted index file %s: " + "Record field %s alignmentation %u not used", + index->filepath, name, ext_hdr->record_align); + t_pop(); + return -1; + } + mail_index_map_register_ext(index, map, name, + offset, ext_hdr->hdr_size, + ext_hdr->record_offset, + ext_hdr->record_size, + ext_hdr->record_align, + ext_hdr->reset_id); + t_pop(); + + offset += MAIL_INDEX_HEADER_SIZE_ALIGN(ext_hdr->hdr_size); + } + return 1; +} + +static bool mail_index_check_header_compat(const struct mail_index_header *hdr) +{ + enum mail_index_header_compat_flags compat_flags = 0; + +#ifndef WORDS_BIGENDIAN + compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN; +#endif + + if (hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { + /* major version change - handle silently(?) */ + return FALSE; + } + if (hdr->compat_flags != compat_flags) { + /* architecture change - handle silently(?) */ + return FALSE; + } + + if ((hdr->flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) { + /* we've already complained about it */ + return FALSE; + } + + return TRUE; +} + +static int mail_index_check_header(struct mail_index *index, + struct mail_index_map *map) +{ + const struct mail_index_header *hdr = &map->hdr; + + if (!mail_index_check_header_compat(hdr)) + return -1; + + /* following some extra checks that only take a bit of CPU */ + if (hdr->uid_validity == 0 && hdr->next_uid != 1) { + mail_index_set_error(index, "Corrupted index file %s: " + "uid_validity = 0, next_uid = %u", + index->filepath, hdr->next_uid); + return -1; + } + + if (hdr->record_size < sizeof(struct mail_index_record)) { + mail_index_set_error(index, "Corrupted index file %s: " + "record_size too small: %u < %"PRIuSIZE_T, + index->filepath, hdr->record_size, + sizeof(struct mail_index_record)); + return -1; + } + + if ((hdr->flags & MAIL_INDEX_HDR_FLAG_FSCK) != 0) + return 0; + + if (hdr->next_uid == 0) + return 0; + + if (hdr->recent_messages_count > hdr->messages_count || + hdr->seen_messages_count > hdr->messages_count || + hdr->deleted_messages_count > hdr->messages_count) + return 0; + if (hdr->first_recent_uid_lowwater > hdr->next_uid || + hdr->first_unseen_uid_lowwater > hdr->next_uid || + hdr->first_deleted_uid_lowwater > hdr->next_uid) + return 0; + + if (map->records_count > 0) { + /* last message's UID must be smaller than next_uid. + also make sure it's not zero. */ + const struct mail_index_record *rec; + + rec = MAIL_INDEX_MAP_IDX(map, map->records_count-1); + if (rec->uid == 0 || rec->uid >= hdr->next_uid) + return 0; + } + + return mail_index_parse_extensions(index, map); +} + +static void mail_index_map_clear(struct mail_index *index, + struct mail_index_map *map) +{ + if (map->buffer != NULL) { + i_assert(map->mmap_base == NULL); + buffer_free(map->buffer); + map->buffer = NULL; + } else if (map->mmap_base != NULL) { + i_assert(map->buffer == NULL); + if (munmap(map->mmap_base, map->mmap_size) < 0) + mail_index_set_syscall_error(index, "munmap()"); + map->mmap_base = NULL; + } + + if (map->refcount > 0) { + memset(&map->hdr, 0, sizeof(map->hdr)); + map->mmap_size = 0; + map->mmap_used_size = 0; + map->records = NULL; + map->records_count = 0; + } +} + +void mail_index_unmap(struct mail_index *index, struct mail_index_map **_map) +{ + struct mail_index_map *map = *_map; + + *_map = NULL; + if (--map->refcount > 0) + return; + + i_assert(map->refcount == 0); + mail_index_map_clear(index, map); + if (map->extension_pool != NULL) + pool_unref(map->extension_pool); + if (array_is_created(&map->keyword_idx_map)) + array_free(&map->keyword_idx_map); + buffer_free(map->hdr_copy_buf); + i_free(map); +} + +static void mail_index_map_copy_hdr(struct mail_index_map *map, + const struct mail_index_header *hdr) +{ + if (hdr->base_header_size < sizeof(map->hdr)) { + /* header smaller than ours, make a copy so our newer headers + won't have garbage in them */ + memset(&map->hdr, 0, sizeof(map->hdr)); + memcpy(&map->hdr, hdr, hdr->base_header_size); + } else { + map->hdr = *hdr; + } +} + +static int mail_index_mmap(struct mail_index *index, struct mail_index_map *map) +{ + const struct mail_index_header *hdr; + unsigned int records_count; + + i_assert(!map->write_to_disk); + + if (map->buffer != NULL) { + /* we had temporarily used a buffer, eg. for updating index */ + buffer_free(map->buffer); + map->buffer = NULL; + } + + map->mmap_base = index->readonly ? + mmap_ro_file(index->fd, &map->mmap_size) : + mmap_rw_file(index->fd, &map->mmap_size); + if (map->mmap_base == MAP_FAILED) { + map->mmap_base = NULL; + mail_index_set_syscall_error(index, "mmap()"); + return -1; + } + + hdr = map->mmap_base; + if (map->mmap_size > + offsetof(struct mail_index_header, major_version) && + hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { + /* major version change - handle silently */ + return 0; + } + + if (map->mmap_size < MAIL_INDEX_HEADER_MIN_SIZE) { + mail_index_set_error(index, "Corrupted index file %s: " + "File too small (%"PRIuSIZE_T")", + index->filepath, map->mmap_size); + return 0; + } + + if (!mail_index_check_header_compat(hdr)) { + /* Can't use this file */ + return 0; + } + + map->mmap_used_size = hdr->header_size + + hdr->messages_count * hdr->record_size; + + if (map->mmap_used_size > map->mmap_size) { + records_count = (map->mmap_size - hdr->header_size) / + hdr->record_size; + mail_index_set_error(index, "Corrupted index file %s: " + "messages_count too large (%u > %u)", + index->filepath, hdr->messages_count, + records_count); + return 0; + } + + mail_index_map_copy_hdr(map, hdr); + + map->hdr_base = map->mmap_base; + map->records = PTR_OFFSET(map->mmap_base, map->hdr.header_size); + map->records_count = map->hdr.messages_count; + return 1; +} + +static int +mail_index_read_map(struct mail_index *index, struct mail_index_map *map, + bool *retry_r, bool try_retry) +{ + const struct mail_index_header *hdr; + struct stat st; + unsigned char buf[512]; + void *data = NULL; + ssize_t ret; + size_t pos, records_size; + unsigned int records_count; + + i_assert(map->mmap_base == NULL); + + *retry_r = FALSE; + ret = mail_index_read_header(index, buf, sizeof(buf), &pos); + hdr = (const struct mail_index_header *)buf; + + if (pos > (ssize_t)offsetof(struct mail_index_header, major_version) && + hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { + /* major version change - handle silently */ + return 0; + } + + if (fstat(index->fd, &st) < 0) { + mail_index_set_syscall_error(index, "fstat()"); + return -1; + } + + if (ret >= 0 && pos >= MAIL_INDEX_HEADER_MIN_SIZE && + (ret > 0 || pos >= hdr->base_header_size)) { + if (!mail_index_check_header_compat(hdr)) { + /* Can't use this file */ + return 0; + } + + if (hdr->base_header_size < MAIL_INDEX_HEADER_MIN_SIZE || + hdr->header_size < hdr->base_header_size) { + mail_index_set_error(index, "Corrupted index file %s: " + "Corrupted header sizes (base %u, full %u)", + index->filepath, hdr->base_header_size, + hdr->header_size); + return 0; + } + if (hdr->header_size > (uoff_t)st.st_size) { + mail_index_set_error(index, "Corrupted index file %s: " + "Corrupted header size (%u > %"PRIuUOFF_T")", + index->filepath, hdr->header_size, + st.st_size); + return 0; + } + + if (pos > hdr->header_size) + pos = hdr->header_size; + + /* place the base header into memory. */ + buffer_reset(map->hdr_copy_buf); + buffer_append(map->hdr_copy_buf, buf, pos); + + if (pos != hdr->header_size) { + /* @UNSAFE: read the rest of the header into memory */ + data = buffer_append_space_unsafe(map->hdr_copy_buf, + hdr->header_size - + pos); + ret = pread_full(index->fd, data, + hdr->header_size - pos, pos); + } + } + + if (ret > 0) { + /* header read, read the records now. */ + records_size = (size_t)hdr->messages_count * hdr->record_size; + + if ((uoff_t)st.st_size - hdr->header_size < records_size || + (hdr->record_size != 0 && + records_size / hdr->record_size != hdr->messages_count)) { + records_count = (st.st_size - hdr->header_size) / + hdr->record_size; + mail_index_set_error(index, "Corrupted index file %s: " + "messages_count too large (%u > %u)", + index->filepath, hdr->messages_count, + records_count); + return 0; + } + + if (map->buffer == NULL) { + map->buffer = buffer_create_dynamic(default_pool, + records_size); + } + + /* @UNSAFE */ + buffer_set_used_size(map->buffer, 0); + data = buffer_append_space_unsafe(map->buffer, records_size); + + ret = pread_full(index->fd, data, records_size, + hdr->header_size); + } + + if (ret < 0) { + if (errno == ESTALE && try_retry) { + /* a new index file was renamed over this one. */ + *retry_r = TRUE; + return 0; + } + mail_index_set_syscall_error(index, "pread_full()"); + return -1; + } + if (ret == 0) { + mail_index_set_error(index, + "Corrupted index file %s: File too small", + index->filepath); + return 0; + } + + map->records = data; + map->records_count = hdr->messages_count; + + mail_index_map_copy_hdr(map, hdr); + map->hdr_base = map->hdr_copy_buf->data; + + index->sync_log_file_seq = hdr->log_file_seq; + index->sync_log_file_offset = hdr->log_file_int_offset; + return 1; +} + +static int mail_index_sync_from_transactions(struct mail_index *index, + struct mail_index_map **map, + bool sync_to_index) +{ + const struct mail_index_header *map_hdr = &(*map)->hdr; + struct mail_index_view *view; + struct mail_index_sync_map_ctx sync_map_ctx; + struct mail_index_header hdr; + const struct mail_transaction_header *thdr; + const void *tdata; + uint32_t prev_seq, max_seq; + uoff_t prev_offset, max_offset; + size_t pos; + int ret; + bool skipped, check_ext_offsets, broken; + + if (sync_to_index) { + /* read the real log position where we are supposed to be + synced */ + ret = mail_index_read_header(index, &hdr, sizeof(hdr), &pos); + if (ret < 0 && errno != ESTALE) { + mail_index_set_syscall_error(index, "pread()"); + return -1; + } + if (pos < MAIL_INDEX_HEADER_MIN_SIZE) + return 0; + + if (map_hdr->log_file_seq == hdr.log_file_seq && + map_hdr->log_file_int_offset == hdr.log_file_int_offset) { + /* nothing to do */ + return 1; + } + + if (map_hdr->log_file_seq > hdr.log_file_seq || + (map_hdr->log_file_seq == hdr.log_file_seq && + map_hdr->log_file_int_offset > hdr.log_file_int_offset)) { + /* we went too far, have to re-read the file */ + return 0; + } + if (map_hdr->log_file_ext_offset != + map_hdr->log_file_int_offset || + hdr.log_file_ext_offset != hdr.log_file_int_offset) { + /* too much trouble to get this right. */ + return 0; + } + max_seq = hdr.log_file_seq; + max_offset = hdr.log_file_int_offset; + } else { + /* sync everything there is */ + max_seq = (uint32_t)-1; + max_offset = (uoff_t)-1; + } + + index->map = *map; + + view = mail_index_view_open(index); + if (mail_transaction_log_view_set(view->log_view, + map_hdr->log_file_seq, + map_hdr->log_file_int_offset, + max_seq, max_offset, + MAIL_TRANSACTION_TYPE_MASK) <= 0) { + /* can't use it. sync by re-reading index. */ + mail_index_view_close(&view); + index->map = NULL; + return 0; + } + + mail_index_sync_map_init(&sync_map_ctx, view, + MAIL_INDEX_SYNC_HANDLER_HEAD); + + check_ext_offsets = TRUE; broken = FALSE; + while ((ret = mail_transaction_log_view_next(view->log_view, &thdr, + &tdata, &skipped)) > 0) { + if ((thdr->type & MAIL_TRANSACTION_EXTERNAL) != 0 && + check_ext_offsets) { + if (mail_index_is_ext_synced(view->log_view, + index->map)) + continue; + check_ext_offsets = FALSE; + } + + if (mail_index_sync_record(&sync_map_ctx, thdr, tdata) < 0) { + ret = 0; + broken = TRUE; + break; + } + } + if (ret == 0 && !broken) + ret = 1; + + mail_transaction_log_view_get_prev_pos(view->log_view, &prev_seq, + &prev_offset); + i_assert(prev_seq <= max_seq && + (prev_seq != max_seq || prev_offset <= max_offset)); + + index->map->hdr.log_file_seq = prev_seq; + index->map->hdr.log_file_int_offset = + index->map->hdr.log_file_ext_offset = prev_offset; + + mail_index_sync_map_deinit(&sync_map_ctx); + mail_index_view_close(&view); + + *map = index->map; + index->map = NULL; + + if (sync_to_index && ret > 0) { + /* make sure we did everything right. note that although the + message counts should be equal, the flag counters may not */ + i_assert(hdr.messages_count == (*map)->hdr.messages_count); + i_assert(hdr.log_file_seq == (*map)->hdr.log_file_seq); + i_assert(hdr.log_file_int_offset == (*map)->hdr.log_file_int_offset); + i_assert(hdr.log_file_ext_offset == (*map)->hdr.log_file_ext_offset); + } + + return ret; +} + +static int mail_index_read_map_with_retry(struct mail_index *index, + struct mail_index_map **map, + bool sync_to_index) +{ + mail_index_sync_lost_handler_t *const *handlers; + unsigned int i, count; + int ret; + bool retry; + + if (index->log_locked) { + /* we're most likely syncing the index and we really don't + want to read more than what was synced last time. */ + sync_to_index = TRUE; + } + + if ((*map)->hdr.indexid != 0 && index->log != NULL) { + /* we're not creating the index, or opening transaction log. + sync this as a view from transaction log. */ + index->sync_update = TRUE; + ret = mail_index_sync_from_transactions(index, map, + sync_to_index); + index->sync_update = FALSE; + if (ret != 0) + return ret; + + /* transaction log lost/broken, fallback to re-reading it */ + } + + /* notify all "sync lost" handlers */ + handlers = array_get(&index->sync_lost_handlers, &count); + for (i = 0; i < count; i++) + (*handlers[i])(index); + + for (i = 0;; i++) { + ret = mail_index_read_map(index, *map, &retry, + i < MAIL_INDEX_ESTALE_RETRY_COUNT); + if (ret != 0 || !retry) + return ret; + + /* ESTALE - reopen index file */ + if (close(index->fd) < 0) + mail_index_set_syscall_error(index, "close()"); + index->fd = -1; + + ret = mail_index_try_open_only(index); + if (ret <= 0) { + if (ret == 0) { + /* the file was lost */ + errno = ENOENT; + mail_index_set_syscall_error(index, "open()"); + } + return -1; + } + } +} + +static int mail_index_map_try_existing(struct mail_index *index) +{ + struct mail_index_map *map = index->map; + const struct mail_index_header *hdr; + size_t used_size; + int ret; + + if (MAIL_INDEX_MAP_IS_IN_MEMORY(map)) + return 0; + + hdr = map->mmap_base; + + /* always check corrupted-flag to avoid errors later */ + if ((hdr->flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) + return -1; + + used_size = hdr->header_size + hdr->messages_count * hdr->record_size; + if (map->mmap_size >= used_size && map->hdr_base == hdr) { + map->records_count = hdr->messages_count; + mail_index_map_copy_hdr(map, hdr); + + /* make sure the header is still valid. it also re-parses + extensions although they shouldn't change without the whole + index being recreated */ + ret = mail_index_check_header(index, map); + if (ret > 0) + return 1; + /* broken. fallback to re-mmaping which will catch it */ + } + return 0; +} + +int mail_index_map(struct mail_index *index, bool force) +{ + struct mail_index_map *map; + int ret; + + i_assert(!index->mapping); + i_assert(index->map == NULL || index->map->refcount > 0); + i_assert(index->lock_type != F_UNLCK); + + if (MAIL_INDEX_IS_IN_MEMORY(index)) { + if (index->map == NULL) + mail_index_create_in_memory(index, NULL); + return 1; + } + + index->mapping = TRUE; + + if (!force && index->map != NULL) { + i_assert(index->hdr != NULL); + ret = mail_index_map_try_existing(index); + if (ret != 0) { + index->mapping = FALSE; + return ret; + } + + if (index->lock_type == F_WRLCK) { + /* we're syncing, don't break the mapping */ + index->mapping = FALSE; + return 1; + } + } + + if (index->map != NULL && index->map->refcount > 1) { + /* this map is already used by some views and they may have + pointers into it. leave them and create a new mapping. */ + if (!index->mmap_disable) { + map = NULL; + } else { + /* create a copy of the mapping instead so we don't + have to re-read it */ + map = mail_index_map_clone(index->map, + index->map->hdr.record_size); + } + index->map->refcount--; + index->map = NULL; + } else { + map = index->map; + } + + if (map == NULL) { + map = i_new(struct mail_index_map, 1); + map->refcount = 1; + map->hdr_copy_buf = + buffer_create_dynamic(default_pool, sizeof(map->hdr)); + } else if (MAIL_INDEX_MAP_IS_IN_MEMORY(map)) { + i_assert(!map->write_to_disk); + } else if (map->mmap_base != NULL) { + i_assert(map->buffer == NULL); + if (munmap(map->mmap_base, map->mmap_size) < 0) + mail_index_set_syscall_error(index, "munmap()"); + map->mmap_base = NULL; + } + + index->hdr = NULL; + index->map = NULL; + + if (!index->mmap_disable) + ret = mail_index_mmap(index, map); + else + ret = mail_index_read_map_with_retry(index, &map, force); + i_assert(index->map == NULL); + + if (ret > 0) { + ret = mail_index_check_header(index, map); + if (ret < 0) + ret = 0; + else if (ret == 0) { + index->fsck = TRUE; + ret = 1; + } + } + + if (ret <= 0) { + mail_index_map_clear(index, map); + mail_index_unmap(index, &map); + index->mapping = FALSE; + return ret; + } + + index->hdr = &map->hdr; + index->map = map; + i_assert(map->hdr.messages_count == map->records_count); + index->mapping = FALSE; + return 1; +} + +struct mail_index_map * +mail_index_map_clone(const struct mail_index_map *map, uint32_t new_record_size) +{ + struct mail_index_map *mem_map; + struct mail_index_header *hdr; + struct mail_index_ext *extensions; + void *src, *dest; + size_t size, copy_size; + unsigned int i, count; + + size = map->records_count * new_record_size; + + mem_map = i_new(struct mail_index_map, 1); + mem_map->refcount = 1; + mem_map->buffer = buffer_create_dynamic(default_pool, size); + if (map->hdr.record_size == new_record_size) + buffer_append(mem_map->buffer, map->records, size); + else { + copy_size = I_MIN(map->hdr.record_size, new_record_size); + src = map->records; + for (i = 0; i < map->records_count; i++) { + dest = buffer_append_space_unsafe(mem_map->buffer, + new_record_size); + memcpy(dest, src, copy_size); + src = PTR_OFFSET(src, map->hdr.record_size); + } + } + + mem_map->records = buffer_get_modifiable_data(mem_map->buffer, NULL); + mem_map->records_count = map->records_count; + + mem_map->hdr_copy_buf = + buffer_create_dynamic(default_pool, map->hdr.header_size); + if (map->hdr.base_header_size < sizeof(*hdr)) + buffer_append_zero(mem_map->hdr_copy_buf, sizeof(*hdr)); + buffer_write(mem_map->hdr_copy_buf, 0, + &map->hdr, map->hdr.base_header_size); + buffer_append(mem_map->hdr_copy_buf, + CONST_PTR_OFFSET(map->hdr_base, + map->hdr.base_header_size), + map->hdr.header_size - map->hdr.base_header_size); + + hdr = buffer_get_modifiable_data(mem_map->hdr_copy_buf, NULL); + if (hdr->base_header_size < sizeof(*hdr)) + hdr->base_header_size = sizeof(*hdr); + hdr->record_size = new_record_size; + mem_map->hdr = *hdr; + mem_map->hdr_base = hdr; + + /* if we're syncing transaction log into memory and later use the + mapping for updating the index, we need to remember what has + changed */ + mem_map->write_atomic = map->write_atomic; + if (map->write_to_disk) { + mem_map->write_seq_first = map->write_seq_first; + mem_map->write_seq_last = map->write_seq_last; + } + + /* copy extensions */ + if (array_is_created(&map->ext_id_map)) { + count = array_count(&map->ext_id_map); + mail_index_map_init_extbufs(mem_map, count + 2); + + array_append_array(&mem_map->extensions, &map->extensions); + array_append_array(&mem_map->ext_id_map, &map->ext_id_map); + + /* fix the name pointers to use our own pool */ + extensions = array_get_modifiable(&mem_map->extensions, &count); + for (i = 0; i < count; i++) { + i_assert(extensions[i].record_offset + + extensions[i].record_size <= hdr->record_size); + extensions[i].name = p_strdup(mem_map->extension_pool, + extensions[i].name); + } + } + + return mem_map; +} + +int mail_index_map_get_ext_idx(struct mail_index_map *map, + uint32_t ext_id, uint32_t *idx_r) +{ + const uint32_t *id; + + if (!array_is_created(&map->ext_id_map) || + ext_id >= array_count(&map->ext_id_map)) + return 0; + + id = array_idx(&map->ext_id_map, ext_id); + *idx_r = *id; + return *idx_r != (uint32_t)-1; +}
--- a/src/lib-index/mail-index-private.h Mon Jun 11 05:06:46 2007 +0300 +++ b/src/lib-index/mail-index-private.h Mon Jun 11 06:07:40 2007 +0300 @@ -216,10 +216,15 @@ void mail_index_unregister_sync_lost_handler(struct mail_index *index, mail_index_sync_lost_handler_t *cb); +int mail_index_read_header(struct mail_index *index, + void *buf, size_t buf_size, size_t *pos_r); int mail_index_write_base_header(struct mail_index *index, const struct mail_index_header *hdr); +int mail_index_try_open_only(struct mail_index *index); int mail_index_reopen(struct mail_index *index, int fd); +void mail_index_create_in_memory(struct mail_index *index, + const struct mail_index_header *hdr); int mail_index_create_tmp_file(struct mail_index *index, const char **path_r); /* Returns 0 = ok, -1 = error. If update_index is TRUE, reopens the index
--- a/src/lib-index/mail-index.c Mon Jun 11 05:06:46 2007 +0300 +++ b/src/lib-index/mail-index.c Mon Jun 11 06:07:40 2007 +0300 @@ -21,10 +21,6 @@ struct mail_index_module_register mail_index_module_register = { 0 }; -static int mail_index_try_open_only(struct mail_index *index); -static void mail_index_create_in_memory(struct mail_index *index, - const struct mail_index_header *hdr); - struct mail_index *mail_index_alloc(const char *dir, const char *prefix) { struct mail_index *index; @@ -182,200 +178,6 @@ } } -static void mail_index_map_init_extbufs(struct mail_index_map *map, - unsigned int initial_count) -{ -#define EXTENSION_NAME_APPROX_LEN 20 -#define EXT_GLOBAL_ALLOC_SIZE \ - ((sizeof(map->extensions) + BUFFER_APPROX_SIZE) * 2) -#define EXT_PER_ALLOC_SIZE \ - (EXTENSION_NAME_APPROX_LEN + \ - sizeof(struct mail_index_ext) + sizeof(uint32_t)) - size_t size; - - if (map->extension_pool == NULL) { - size = EXT_GLOBAL_ALLOC_SIZE + - initial_count * EXT_PER_ALLOC_SIZE; - map->extension_pool = - pool_alloconly_create("map extensions", - nearest_power(size)); - } else { - p_clear(map->extension_pool); - - /* try to use the existing pool's size for initial_count so - we don't grow it unneededly */ - size = p_get_max_easy_alloc_size(map->extension_pool); - if (size > EXT_GLOBAL_ALLOC_SIZE + EXT_PER_ALLOC_SIZE) { - initial_count = (size - EXT_GLOBAL_ALLOC_SIZE) / - EXT_PER_ALLOC_SIZE; - } - } - - p_array_init(&map->extensions, map->extension_pool, initial_count); - p_array_init(&map->ext_id_map, map->extension_pool, initial_count); -} - -uint32_t mail_index_map_lookup_ext(struct mail_index_map *map, const char *name) -{ - const struct mail_index_ext *extensions; - unsigned int i, size; - - if (!array_is_created(&map->extensions)) - return (uint32_t)-1; - - extensions = array_get(&map->extensions, &size); - for (i = 0; i < size; i++) { - if (strcmp(extensions[i].name, name) == 0) - return i; - } - return (uint32_t)-1; -} - -uint32_t -mail_index_map_register_ext(struct mail_index *index, - struct mail_index_map *map, const char *name, - uint32_t hdr_offset, uint32_t hdr_size, - uint32_t record_offset, uint32_t record_size, - uint32_t record_align, uint32_t reset_id) -{ - struct mail_index_ext *ext; - uint32_t idx, empty_idx = (uint32_t)-1; - - if (!array_is_created(&map->extensions)) { - mail_index_map_init_extbufs(map, 5); - idx = 0; - } else { - idx = array_count(&map->extensions); - } - i_assert(mail_index_map_lookup_ext(map, name) == (uint32_t)-1); - - ext = array_append_space(&map->extensions); - ext->name = p_strdup(map->extension_pool, name); - ext->hdr_offset = hdr_offset; - ext->hdr_size = hdr_size; - ext->record_offset = record_offset; - ext->record_size = record_size; - ext->record_align = record_align; - ext->reset_id = reset_id; - - ext->index_idx = mail_index_ext_register(index, name, hdr_size, - record_size, record_align); - - /* Update index ext_id -> map ext_id mapping. Fill non-used - ext_ids with (uint32_t)-1 */ - while (array_count(&map->ext_id_map) < ext->index_idx) - array_append(&map->ext_id_map, &empty_idx, 1); - array_idx_set(&map->ext_id_map, ext->index_idx, &idx); - return idx; -} - -static bool size_check(size_t *size_left, size_t size) -{ - if (size > *size_left) - return FALSE; - *size_left -= size; - return TRUE; -} - -static size_t get_align(size_t name_len) -{ - size_t size = sizeof(struct mail_index_ext_header) + name_len; - return MAIL_INDEX_HEADER_SIZE_ALIGN(size) - size; -} - -static int mail_index_parse_extensions(struct mail_index *index, - struct mail_index_map *map) -{ - const struct mail_index_ext_header *ext_hdr; - unsigned int i, old_count; - const char *name; - uint32_t ext_id, offset, name_offset; - size_t size_left; - - /* extension headers always start from 64bit offsets, so if base header - doesn't happen to be 64bit aligned we'll skip some bytes */ - offset = MAIL_INDEX_HEADER_SIZE_ALIGN(map->hdr.base_header_size); - if (offset >= map->hdr.header_size && map->extension_pool == NULL) { - /* nothing to do, skip allocatations and all */ - return 1; - } - - old_count = array_count(&index->extensions); - mail_index_map_init_extbufs(map, old_count + 5); - - ext_id = (uint32_t)-1; - for (i = 0; i < old_count; i++) - array_append(&map->ext_id_map, &ext_id, 1); - - while (offset < map->hdr.header_size) { - ext_hdr = CONST_PTR_OFFSET(map->hdr_base, offset); - - /* Extension header contains: - - struct mail_index_ext_header - - name (not 0-terminated) - - 64bit alignment padding - - extension header contents - - 64bit alignment padding - */ - size_left = map->hdr.header_size - offset; - if (!size_check(&size_left, sizeof(*ext_hdr)) || - !size_check(&size_left, ext_hdr->name_size) || - !size_check(&size_left, get_align(ext_hdr->name_size)) || - !size_check(&size_left, ext_hdr->hdr_size)) { - mail_index_set_error(index, "Corrupted index file %s: " - "Header extension goes outside header", - index->filepath); - return -1; - } - - offset += sizeof(*ext_hdr); - name_offset = offset; - offset += ext_hdr->name_size + get_align(ext_hdr->name_size); - - t_push(); - name = t_strndup(CONST_PTR_OFFSET(map->hdr_base, name_offset), - ext_hdr->name_size); - - if (mail_index_map_lookup_ext(map, name) != (uint32_t)-1) { - mail_index_set_error(index, "Corrupted index file %s: " - "Duplicate header extension %s", - index->filepath, name); - t_pop(); - return -1; - } - - if (map->hdr.record_size < - ext_hdr->record_offset + ext_hdr->record_size) { - mail_index_set_error(index, "Corrupted index file %s: " - "Record field %s points outside record size " - "(%u < %u+%u)", index->filepath, name, - map->hdr.record_size, - ext_hdr->record_offset, ext_hdr->record_size); - t_pop(); - return -1; - } - - if ((ext_hdr->record_offset % ext_hdr->record_align) != 0 || - (map->hdr.record_size % ext_hdr->record_align) != 0) { - mail_index_set_error(index, "Corrupted index file %s: " - "Record field %s alignmentation %u not used", - index->filepath, name, ext_hdr->record_align); - t_pop(); - return -1; - } - mail_index_map_register_ext(index, map, name, - offset, ext_hdr->hdr_size, - ext_hdr->record_offset, - ext_hdr->record_size, - ext_hdr->record_align, - ext_hdr->reset_id); - t_pop(); - - offset += MAIL_INDEX_HEADER_SIZE_ALIGN(ext_hdr->hdr_size); - } - return 1; -} - bool mail_index_keyword_lookup(struct mail_index *index, const char *keyword, bool autocreate, unsigned int *idx_r) @@ -519,202 +321,21 @@ return &index->keywords; } -static bool mail_index_check_header_compat(const struct mail_index_header *hdr) +bool mail_index_is_ext_synced(struct mail_transaction_log_view *log_view, + struct mail_index_map *map) { - enum mail_index_header_compat_flags compat_flags = 0; - -#ifndef WORDS_BIGENDIAN - compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN; -#endif - - if (hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { - /* major version change - handle silently(?) */ - return FALSE; - } - if (hdr->compat_flags != compat_flags) { - /* architecture change - handle silently(?) */ - return FALSE; - } - - if ((hdr->flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) { - /* we've already complained about it */ - return FALSE; - } - - return TRUE; -} - -static int mail_index_check_header(struct mail_index *index, - struct mail_index_map *map) -{ - const struct mail_index_header *hdr = &map->hdr; - - if (!mail_index_check_header_compat(hdr)) - return -1; + uint32_t prev_seq; + uoff_t prev_offset; - /* following some extra checks that only take a bit of CPU */ - if (hdr->uid_validity == 0 && hdr->next_uid != 1) { - mail_index_set_error(index, "Corrupted index file %s: " - "uid_validity = 0, next_uid = %u", - index->filepath, hdr->next_uid); - return -1; - } - - if (hdr->record_size < sizeof(struct mail_index_record)) { - mail_index_set_error(index, "Corrupted index file %s: " - "record_size too small: %u < %"PRIuSIZE_T, - index->filepath, hdr->record_size, - sizeof(struct mail_index_record)); - return -1; - } - - if ((hdr->flags & MAIL_INDEX_HDR_FLAG_FSCK) != 0) - return 0; - - if (hdr->next_uid == 0) - return 0; - - if (hdr->recent_messages_count > hdr->messages_count || - hdr->seen_messages_count > hdr->messages_count || - hdr->deleted_messages_count > hdr->messages_count) - return 0; - if (hdr->first_recent_uid_lowwater > hdr->next_uid || - hdr->first_unseen_uid_lowwater > hdr->next_uid || - hdr->first_deleted_uid_lowwater > hdr->next_uid) - return 0; - - if (map->records_count > 0) { - /* last message's UID must be smaller than next_uid. - also make sure it's not zero. */ - const struct mail_index_record *rec; - - rec = MAIL_INDEX_MAP_IDX(map, map->records_count-1); - if (rec->uid == 0 || rec->uid >= hdr->next_uid) - return 0; - } - - return mail_index_parse_extensions(index, map); + mail_transaction_log_view_get_prev_pos(log_view, &prev_seq, + &prev_offset); + return prev_seq < map->hdr.log_file_seq || + (prev_seq == map->hdr.log_file_seq && + prev_offset < map->hdr.log_file_ext_offset); } -static void mail_index_map_clear(struct mail_index *index, - struct mail_index_map *map) -{ - if (map->buffer != NULL) { - i_assert(map->mmap_base == NULL); - buffer_free(map->buffer); - map->buffer = NULL; - } else if (map->mmap_base != NULL) { - i_assert(map->buffer == NULL); - if (munmap(map->mmap_base, map->mmap_size) < 0) - mail_index_set_syscall_error(index, "munmap()"); - map->mmap_base = NULL; - } - - if (map->refcount > 0) { - memset(&map->hdr, 0, sizeof(map->hdr)); - map->mmap_size = 0; - map->mmap_used_size = 0; - map->records = NULL; - map->records_count = 0; - } -} - -void mail_index_unmap(struct mail_index *index, struct mail_index_map **_map) -{ - struct mail_index_map *map = *_map; - - *_map = NULL; - if (--map->refcount > 0) - return; - - i_assert(map->refcount == 0); - mail_index_map_clear(index, map); - if (map->extension_pool != NULL) - pool_unref(map->extension_pool); - if (array_is_created(&map->keyword_idx_map)) - array_free(&map->keyword_idx_map); - buffer_free(map->hdr_copy_buf); - i_free(map); -} - -static void mail_index_map_copy_hdr(struct mail_index_map *map, - const struct mail_index_header *hdr) -{ - if (hdr->base_header_size < sizeof(map->hdr)) { - /* header smaller than ours, make a copy so our newer headers - won't have garbage in them */ - memset(&map->hdr, 0, sizeof(map->hdr)); - memcpy(&map->hdr, hdr, hdr->base_header_size); - } else { - map->hdr = *hdr; - } -} - -static int mail_index_mmap(struct mail_index *index, struct mail_index_map *map) -{ - const struct mail_index_header *hdr; - unsigned int records_count; - - i_assert(!map->write_to_disk); - - if (map->buffer != NULL) { - /* we had temporarily used a buffer, eg. for updating index */ - buffer_free(map->buffer); - map->buffer = NULL; - } - - map->mmap_base = index->readonly ? - mmap_ro_file(index->fd, &map->mmap_size) : - mmap_rw_file(index->fd, &map->mmap_size); - if (map->mmap_base == MAP_FAILED) { - map->mmap_base = NULL; - mail_index_set_syscall_error(index, "mmap()"); - return -1; - } - - hdr = map->mmap_base; - if (map->mmap_size > - offsetof(struct mail_index_header, major_version) && - hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { - /* major version change - handle silently */ - return 0; - } - - if (map->mmap_size < MAIL_INDEX_HEADER_MIN_SIZE) { - mail_index_set_error(index, "Corrupted index file %s: " - "File too small (%"PRIuSIZE_T")", - index->filepath, map->mmap_size); - return 0; - } - - if (!mail_index_check_header_compat(hdr)) { - /* Can't use this file */ - return 0; - } - - map->mmap_used_size = hdr->header_size + - hdr->messages_count * hdr->record_size; - - if (map->mmap_used_size > map->mmap_size) { - records_count = (map->mmap_size - hdr->header_size) / - hdr->record_size; - mail_index_set_error(index, "Corrupted index file %s: " - "messages_count too large (%u > %u)", - index->filepath, hdr->messages_count, - records_count); - return 0; - } - - mail_index_map_copy_hdr(map, hdr); - - map->hdr_base = map->mmap_base; - map->records = PTR_OFFSET(map->mmap_base, map->hdr.header_size); - map->records_count = map->hdr.messages_count; - return 1; -} - -static int mail_index_read_header(struct mail_index *index, - void *buf, size_t buf_size, size_t *pos_r) +int mail_index_read_header(struct mail_index *index, + void *buf, size_t buf_size, size_t *pos_r) { size_t pos; int ret; @@ -737,445 +358,6 @@ return ret; } -static int -mail_index_read_map(struct mail_index *index, struct mail_index_map *map, - bool *retry_r, bool try_retry) -{ - const struct mail_index_header *hdr; - struct stat st; - unsigned char buf[512]; - void *data = NULL; - ssize_t ret; - size_t pos, records_size; - unsigned int records_count; - - i_assert(map->mmap_base == NULL); - - *retry_r = FALSE; - ret = mail_index_read_header(index, buf, sizeof(buf), &pos); - hdr = (const struct mail_index_header *)buf; - - if (pos > (ssize_t)offsetof(struct mail_index_header, major_version) && - hdr->major_version != MAIL_INDEX_MAJOR_VERSION) { - /* major version change - handle silently */ - return 0; - } - - if (fstat(index->fd, &st) < 0) { - mail_index_set_syscall_error(index, "fstat()"); - return -1; - } - - if (ret >= 0 && pos >= MAIL_INDEX_HEADER_MIN_SIZE && - (ret > 0 || pos >= hdr->base_header_size)) { - if (!mail_index_check_header_compat(hdr)) { - /* Can't use this file */ - return 0; - } - - if (hdr->base_header_size < MAIL_INDEX_HEADER_MIN_SIZE || - hdr->header_size < hdr->base_header_size) { - mail_index_set_error(index, "Corrupted index file %s: " - "Corrupted header sizes (base %u, full %u)", - index->filepath, hdr->base_header_size, - hdr->header_size); - return 0; - } - if (hdr->header_size > (uoff_t)st.st_size) { - mail_index_set_error(index, "Corrupted index file %s: " - "Corrupted header size (%u > %"PRIuUOFF_T")", - index->filepath, hdr->header_size, - st.st_size); - return 0; - } - - if (pos > hdr->header_size) - pos = hdr->header_size; - - /* place the base header into memory. */ - buffer_reset(map->hdr_copy_buf); - buffer_append(map->hdr_copy_buf, buf, pos); - - if (pos != hdr->header_size) { - /* @UNSAFE: read the rest of the header into memory */ - data = buffer_append_space_unsafe(map->hdr_copy_buf, - hdr->header_size - - pos); - ret = pread_full(index->fd, data, - hdr->header_size - pos, pos); - } - } - - if (ret > 0) { - /* header read, read the records now. */ - records_size = (size_t)hdr->messages_count * hdr->record_size; - - if ((uoff_t)st.st_size - hdr->header_size < records_size || - (hdr->record_size != 0 && - records_size / hdr->record_size != hdr->messages_count)) { - records_count = (st.st_size - hdr->header_size) / - hdr->record_size; - mail_index_set_error(index, "Corrupted index file %s: " - "messages_count too large (%u > %u)", - index->filepath, hdr->messages_count, - records_count); - return 0; - } - - if (map->buffer == NULL) { - map->buffer = buffer_create_dynamic(default_pool, - records_size); - } - - /* @UNSAFE */ - buffer_set_used_size(map->buffer, 0); - data = buffer_append_space_unsafe(map->buffer, records_size); - - ret = pread_full(index->fd, data, records_size, - hdr->header_size); - } - - if (ret < 0) { - if (errno == ESTALE && try_retry) { - /* a new index file was renamed over this one. */ - *retry_r = TRUE; - return 0; - } - mail_index_set_syscall_error(index, "pread_full()"); - return -1; - } - if (ret == 0) { - mail_index_set_error(index, - "Corrupted index file %s: File too small", - index->filepath); - return 0; - } - - map->records = data; - map->records_count = hdr->messages_count; - - mail_index_map_copy_hdr(map, hdr); - map->hdr_base = map->hdr_copy_buf->data; - - index->sync_log_file_seq = hdr->log_file_seq; - index->sync_log_file_offset = hdr->log_file_int_offset; - return 1; -} - -bool mail_index_is_ext_synced(struct mail_transaction_log_view *log_view, - struct mail_index_map *map) -{ - uint32_t prev_seq; - uoff_t prev_offset; - - mail_transaction_log_view_get_prev_pos(log_view, &prev_seq, - &prev_offset); - return prev_seq < map->hdr.log_file_seq || - (prev_seq == map->hdr.log_file_seq && - prev_offset < map->hdr.log_file_ext_offset); -} - -static int mail_index_sync_from_transactions(struct mail_index *index, - struct mail_index_map **map, - bool sync_to_index) -{ - const struct mail_index_header *map_hdr = &(*map)->hdr; - struct mail_index_view *view; - struct mail_index_sync_map_ctx sync_map_ctx; - struct mail_index_header hdr; - const struct mail_transaction_header *thdr; - const void *tdata; - uint32_t prev_seq, max_seq; - uoff_t prev_offset, max_offset; - size_t pos; - int ret; - bool skipped, check_ext_offsets, broken; - - if (sync_to_index) { - /* read the real log position where we are supposed to be - synced */ - ret = mail_index_read_header(index, &hdr, sizeof(hdr), &pos); - if (ret < 0 && errno != ESTALE) { - mail_index_set_syscall_error(index, "pread()"); - return -1; - } - if (pos < MAIL_INDEX_HEADER_MIN_SIZE) - return 0; - - if (map_hdr->log_file_seq == hdr.log_file_seq && - map_hdr->log_file_int_offset == hdr.log_file_int_offset) { - /* nothing to do */ - return 1; - } - - if (map_hdr->log_file_seq > hdr.log_file_seq || - (map_hdr->log_file_seq == hdr.log_file_seq && - map_hdr->log_file_int_offset > hdr.log_file_int_offset)) { - /* we went too far, have to re-read the file */ - return 0; - } - if (map_hdr->log_file_ext_offset != - map_hdr->log_file_int_offset || - hdr.log_file_ext_offset != hdr.log_file_int_offset) { - /* too much trouble to get this right. */ - return 0; - } - max_seq = hdr.log_file_seq; - max_offset = hdr.log_file_int_offset; - } else { - /* sync everything there is */ - max_seq = (uint32_t)-1; - max_offset = (uoff_t)-1; - } - - index->map = *map; - - view = mail_index_view_open(index); - if (mail_transaction_log_view_set(view->log_view, - map_hdr->log_file_seq, - map_hdr->log_file_int_offset, - max_seq, max_offset, - MAIL_TRANSACTION_TYPE_MASK) <= 0) { - /* can't use it. sync by re-reading index. */ - mail_index_view_close(&view); - index->map = NULL; - return 0; - } - - mail_index_sync_map_init(&sync_map_ctx, view, - MAIL_INDEX_SYNC_HANDLER_HEAD); - - check_ext_offsets = TRUE; broken = FALSE; - while ((ret = mail_transaction_log_view_next(view->log_view, &thdr, - &tdata, &skipped)) > 0) { - if ((thdr->type & MAIL_TRANSACTION_EXTERNAL) != 0 && - check_ext_offsets) { - if (mail_index_is_ext_synced(view->log_view, - index->map)) - continue; - check_ext_offsets = FALSE; - } - - if (mail_index_sync_record(&sync_map_ctx, thdr, tdata) < 0) { - ret = 0; - broken = TRUE; - break; - } - } - if (ret == 0 && !broken) - ret = 1; - - mail_transaction_log_view_get_prev_pos(view->log_view, &prev_seq, - &prev_offset); - i_assert(prev_seq <= max_seq && - (prev_seq != max_seq || prev_offset <= max_offset)); - - index->map->hdr.log_file_seq = prev_seq; - index->map->hdr.log_file_int_offset = - index->map->hdr.log_file_ext_offset = prev_offset; - - mail_index_sync_map_deinit(&sync_map_ctx); - mail_index_view_close(&view); - - *map = index->map; - index->map = NULL; - - if (sync_to_index && ret > 0) { - /* make sure we did everything right. note that although the - message counts should be equal, the flag counters may not */ - i_assert(hdr.messages_count == (*map)->hdr.messages_count); - i_assert(hdr.log_file_seq == (*map)->hdr.log_file_seq); - i_assert(hdr.log_file_int_offset == (*map)->hdr.log_file_int_offset); - i_assert(hdr.log_file_ext_offset == (*map)->hdr.log_file_ext_offset); - } - - return ret; -} - -static int mail_index_read_map_with_retry(struct mail_index *index, - struct mail_index_map **map, - bool sync_to_index) -{ - mail_index_sync_lost_handler_t *const *handlers; - unsigned int i, count; - int ret; - bool retry; - - if (index->log_locked) { - /* we're most likely syncing the index and we really don't - want to read more than what was synced last time. */ - sync_to_index = TRUE; - } - - if ((*map)->hdr.indexid != 0 && index->log != NULL) { - /* we're not creating the index, or opening transaction log. - sync this as a view from transaction log. */ - index->sync_update = TRUE; - ret = mail_index_sync_from_transactions(index, map, - sync_to_index); - index->sync_update = FALSE; - if (ret != 0) - return ret; - - /* transaction log lost/broken, fallback to re-reading it */ - } - - /* notify all "sync lost" handlers */ - handlers = array_get(&index->sync_lost_handlers, &count); - for (i = 0; i < count; i++) - (*handlers[i])(index); - - for (i = 0;; i++) { - ret = mail_index_read_map(index, *map, &retry, - i < MAIL_INDEX_ESTALE_RETRY_COUNT); - if (ret != 0 || !retry) - return ret; - - /* ESTALE - reopen index file */ - if (close(index->fd) < 0) - mail_index_set_syscall_error(index, "close()"); - index->fd = -1; - - ret = mail_index_try_open_only(index); - if (ret <= 0) { - if (ret == 0) { - /* the file was lost */ - errno = ENOENT; - mail_index_set_syscall_error(index, "open()"); - } - return -1; - } - } -} - -static int mail_index_map_try_existing(struct mail_index *index) -{ - struct mail_index_map *map = index->map; - const struct mail_index_header *hdr; - size_t used_size; - int ret; - - if (MAIL_INDEX_MAP_IS_IN_MEMORY(map)) - return 0; - - hdr = map->mmap_base; - - /* always check corrupted-flag to avoid errors later */ - if ((hdr->flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) - return -1; - - used_size = hdr->header_size + hdr->messages_count * hdr->record_size; - if (map->mmap_size >= used_size && map->hdr_base == hdr) { - map->records_count = hdr->messages_count; - mail_index_map_copy_hdr(map, hdr); - - /* make sure the header is still valid. it also re-parses - extensions although they shouldn't change without the whole - index being recreated */ - ret = mail_index_check_header(index, map); - if (ret > 0) - return 1; - /* broken. fallback to re-mmaping which will catch it */ - } - return 0; -} - -int mail_index_map(struct mail_index *index, bool force) -{ - struct mail_index_map *map; - int ret; - - i_assert(!index->mapping); - i_assert(index->map == NULL || index->map->refcount > 0); - i_assert(index->lock_type != F_UNLCK); - - if (MAIL_INDEX_IS_IN_MEMORY(index)) { - if (index->map == NULL) - mail_index_create_in_memory(index, NULL); - return 1; - } - - index->mapping = TRUE; - - if (!force && index->map != NULL) { - i_assert(index->hdr != NULL); - ret = mail_index_map_try_existing(index); - if (ret != 0) { - index->mapping = FALSE; - return ret; - } - - if (index->lock_type == F_WRLCK) { - /* we're syncing, don't break the mapping */ - index->mapping = FALSE; - return 1; - } - } - - if (index->map != NULL && index->map->refcount > 1) { - /* this map is already used by some views and they may have - pointers into it. leave them and create a new mapping. */ - if (!index->mmap_disable) { - map = NULL; - } else { - /* create a copy of the mapping instead so we don't - have to re-read it */ - map = mail_index_map_clone(index->map, - index->map->hdr.record_size); - } - index->map->refcount--; - index->map = NULL; - } else { - map = index->map; - } - - if (map == NULL) { - map = i_new(struct mail_index_map, 1); - map->refcount = 1; - map->hdr_copy_buf = - buffer_create_dynamic(default_pool, sizeof(map->hdr)); - } else if (MAIL_INDEX_MAP_IS_IN_MEMORY(map)) { - i_assert(!map->write_to_disk); - } else if (map->mmap_base != NULL) { - i_assert(map->buffer == NULL); - if (munmap(map->mmap_base, map->mmap_size) < 0) - mail_index_set_syscall_error(index, "munmap()"); - map->mmap_base = NULL; - } - - index->hdr = NULL; - index->map = NULL; - - if (!index->mmap_disable) - ret = mail_index_mmap(index, map); - else - ret = mail_index_read_map_with_retry(index, &map, force); - i_assert(index->map == NULL); - - if (ret > 0) { - ret = mail_index_check_header(index, map); - if (ret < 0) - ret = 0; - else if (ret == 0) { - index->fsck = TRUE; - ret = 1; - } - } - - if (ret <= 0) { - mail_index_map_clear(index, map); - mail_index_unmap(index, &map); - index->mapping = FALSE; - return ret; - } - - index->hdr = &map->hdr; - index->map = map; - i_assert(map->hdr.messages_count == map->records_count); - index->mapping = FALSE; - return 1; -} - int mail_index_get_latest_header(struct mail_index *index, struct mail_index_header *hdr_r) { @@ -1225,100 +407,7 @@ return ret; } -struct mail_index_map * -mail_index_map_clone(const struct mail_index_map *map, uint32_t new_record_size) -{ - struct mail_index_map *mem_map; - struct mail_index_header *hdr; - struct mail_index_ext *extensions; - void *src, *dest; - size_t size, copy_size; - unsigned int i, count; - - size = map->records_count * new_record_size; - - mem_map = i_new(struct mail_index_map, 1); - mem_map->refcount = 1; - mem_map->buffer = buffer_create_dynamic(default_pool, size); - if (map->hdr.record_size == new_record_size) - buffer_append(mem_map->buffer, map->records, size); - else { - copy_size = I_MIN(map->hdr.record_size, new_record_size); - src = map->records; - for (i = 0; i < map->records_count; i++) { - dest = buffer_append_space_unsafe(mem_map->buffer, - new_record_size); - memcpy(dest, src, copy_size); - src = PTR_OFFSET(src, map->hdr.record_size); - } - } - - mem_map->records = buffer_get_modifiable_data(mem_map->buffer, NULL); - mem_map->records_count = map->records_count; - - mem_map->hdr_copy_buf = - buffer_create_dynamic(default_pool, map->hdr.header_size); - if (map->hdr.base_header_size < sizeof(*hdr)) - buffer_append_zero(mem_map->hdr_copy_buf, sizeof(*hdr)); - buffer_write(mem_map->hdr_copy_buf, 0, - &map->hdr, map->hdr.base_header_size); - buffer_append(mem_map->hdr_copy_buf, - CONST_PTR_OFFSET(map->hdr_base, - map->hdr.base_header_size), - map->hdr.header_size - map->hdr.base_header_size); - - hdr = buffer_get_modifiable_data(mem_map->hdr_copy_buf, NULL); - if (hdr->base_header_size < sizeof(*hdr)) - hdr->base_header_size = sizeof(*hdr); - hdr->record_size = new_record_size; - mem_map->hdr = *hdr; - mem_map->hdr_base = hdr; - - /* if we're syncing transaction log into memory and later use the - mapping for updating the index, we need to remember what has - changed */ - mem_map->write_atomic = map->write_atomic; - if (map->write_to_disk) { - mem_map->write_seq_first = map->write_seq_first; - mem_map->write_seq_last = map->write_seq_last; - } - - /* copy extensions */ - if (array_is_created(&map->ext_id_map)) { - count = array_count(&map->ext_id_map); - mail_index_map_init_extbufs(mem_map, count + 2); - - array_append_array(&mem_map->extensions, &map->extensions); - array_append_array(&mem_map->ext_id_map, &map->ext_id_map); - - /* fix the name pointers to use our own pool */ - extensions = array_get_modifiable(&mem_map->extensions, &count); - for (i = 0; i < count; i++) { - i_assert(extensions[i].record_offset + - extensions[i].record_size <= hdr->record_size); - extensions[i].name = p_strdup(mem_map->extension_pool, - extensions[i].name); - } - } - - return mem_map; -} - -int mail_index_map_get_ext_idx(struct mail_index_map *map, - uint32_t ext_id, uint32_t *idx_r) -{ - const uint32_t *id; - - if (!array_is_created(&map->ext_id_map) || - ext_id >= array_count(&map->ext_id_map)) - return 0; - - id = array_idx(&map->ext_id_map, ext_id); - *idx_r = *id; - return *idx_r != (uint32_t)-1; -} - -static int mail_index_try_open_only(struct mail_index *index) +int mail_index_try_open_only(struct mail_index *index) { i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); @@ -1525,8 +614,8 @@ hdr->next_uid = 1; } -static void mail_index_create_in_memory(struct mail_index *index, - const struct mail_index_header *hdr) +void mail_index_create_in_memory(struct mail_index *index, + const struct mail_index_header *hdr) { struct mail_index_header tmp_hdr; struct mail_index_map tmp_map;