diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-index/mail-index.c | 1110 |
1 files changed, 1110 insertions, 0 deletions
diff --git a/src/lib-index/mail-index.c b/src/lib-index/mail-index.c new file mode 100644 index 0000000..8f89309 --- /dev/null +++ b/src/lib-index/mail-index.c @@ -0,0 +1,1110 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "buffer.h" +#include "eacces-error.h" +#include "hash.h" +#include "str-sanitize.h" +#include "mmap-util.h" +#include "nfs-workarounds.h" +#include "read-full.h" +#include "write-full.h" +#include "mail-index-alloc-cache.h" +#include "mail-index-private.h" +#include "mail-index-view-private.h" +#include "mail-index-sync-private.h" +#include "mail-index-modseq.h" +#include "mail-transaction-log-private.h" +#include "mail-transaction-log-view-private.h" +#include "mail-cache.h" + +#include <stdio.h> +#include <stddef.h> +#include <time.h> +#include <sys/stat.h> +#include <ctype.h> + +struct mail_index_module_register mail_index_module_register = { 0 }; + +struct event_category event_category_mail_index = { + .name = "mail-index", +}; + +static void mail_index_close_nonopened(struct mail_index *index); + +static const struct mail_index_optimization_settings default_optimization_set = { + .index = { + .rewrite_min_log_bytes = 8 * 1024, + .rewrite_max_log_bytes = 128 * 1024, + }, + .log = { + .min_size = 32 * 1024, + .max_size = 1024 * 1024, + .min_age_secs = 5 * 60, + .log2_max_age_secs = 3600 * 24 * 2, + }, + .cache = { + .unaccessed_field_drop_secs = 3600 * 24 * 30, + .record_max_size = 64 * 1024, + .max_size = 1024 * 1024 * 1024, + .purge_min_size = 32 * 1024, + .purge_delete_percentage = 20, + .purge_continued_percentage = 200, + .purge_header_continue_count = 4, + }, +}; + +struct mail_index *mail_index_alloc(struct event *parent_event, + const char *dir, const char *prefix) +{ + struct mail_index *index; + + index = i_new(struct mail_index, 1); + index->dir = i_strdup(dir); + index->prefix = i_strdup(prefix); + index->fd = -1; + index->event = event_create(parent_event); + event_add_category(index->event, &event_category_mail_index); + + index->extension_pool = + pool_alloconly_create(MEMPOOL_GROWING"index extension", 1024); + p_array_init(&index->extensions, index->extension_pool, 5); + i_array_init(&index->module_contexts, + I_MIN(5, mail_index_module_register.id)); + + index->set.mode = 0600; + index->set.gid = (gid_t)-1; + index->set.lock_method = FILE_LOCK_METHOD_FCNTL; + index->set.max_lock_timeout_secs = UINT_MAX; + index->optimization_set = default_optimization_set; + + index->keywords_ext_id = + mail_index_ext_register(index, MAIL_INDEX_EXT_KEYWORDS, + 128, 2, 1); + index->keywords_pool = pool_alloconly_create("keywords", 512); + i_array_init(&index->keywords, 16); + hash_table_create(&index->keywords_hash, index->keywords_pool, 0, + strcase_hash, strcasecmp); + index->log = mail_transaction_log_alloc(index); + mail_index_modseq_init(index); + return index; +} + +void mail_index_free(struct mail_index **_index) +{ + struct mail_index *index = *_index; + + *_index = NULL; + + i_assert(index->open_count == 0); + + mail_transaction_log_free(&index->log); + hash_table_destroy(&index->keywords_hash); + pool_unref(&index->extension_pool); + pool_unref(&index->keywords_pool); + + array_free(&index->keywords); + array_free(&index->module_contexts); + + event_unref(&index->event); + i_free(index->set.cache_dir); + i_free(index->set.ext_hdr_init_data); + i_free(index->set.gid_origin); + i_free(index->last_error.text); + i_free(index->dir); + i_free(index->prefix); + i_free(index->need_recreate); + i_free(index); +} + +void mail_index_set_cache_dir(struct mail_index *index, const char *dir) +{ + i_free(index->set.cache_dir); + index->set.cache_dir = i_strdup(dir); +} + +void mail_index_set_fsync_mode(struct mail_index *index, + enum fsync_mode mode, + enum mail_index_fsync_mask mask) +{ + index->set.fsync_mode = mode; + index->set.fsync_mask = mask; +} + +bool mail_index_use_existing_permissions(struct mail_index *index) +{ + struct stat st; + + if (MAIL_INDEX_IS_IN_MEMORY(index)) + return FALSE; + + if (stat(index->dir, &st) < 0) { + if (errno != ENOENT) + e_error(index->event, "stat(%s) failed: %m", index->dir); + return FALSE; + } + + index->set.mode = st.st_mode & 0666; + if (S_ISDIR(st.st_mode) && (st.st_mode & S_ISGID) != 0) { + /* directory's GID is used automatically for new files */ + index->set.gid = (gid_t)-1; + } else if ((st.st_mode & 0070) >> 3 == (st.st_mode & 0007)) { + /* group has same permissions as world, so don't bother + changing it */ + index->set.gid = (gid_t)-1; + } else if (getegid() == st.st_gid) { + /* using our own gid, no need to change it */ + index->set.gid = (gid_t)-1; + } else { + index->set.gid = st.st_gid; + } + + i_free(index->set.gid_origin); + if (index->set.gid != (gid_t)-1) + index->set.gid_origin = i_strdup("preserved existing GID"); + return TRUE; +} + +void mail_index_set_permissions(struct mail_index *index, + mode_t mode, gid_t gid, const char *gid_origin) +{ + index->set.mode = mode & 0666; + index->set.gid = gid; + + i_free(index->set.gid_origin); + index->set.gid_origin = i_strdup(gid_origin); +} + +void mail_index_set_lock_method(struct mail_index *index, + enum file_lock_method lock_method, + unsigned int max_timeout_secs) +{ + index->set.lock_method = lock_method; + index->set.max_lock_timeout_secs = max_timeout_secs; +} + +void mail_index_set_optimization_settings(struct mail_index *index, + const struct mail_index_optimization_settings *set) +{ + struct mail_index_optimization_settings *dest = + &index->optimization_set; + + /* index */ + if (set->index.rewrite_min_log_bytes != 0) + dest->index.rewrite_min_log_bytes = set->index.rewrite_min_log_bytes; + if (set->index.rewrite_max_log_bytes != 0) + dest->index.rewrite_max_log_bytes = set->index.rewrite_max_log_bytes; + + /* log */ + if (set->log.min_size != 0) + dest->log.min_size = set->log.min_size; + if (set->log.max_size != 0) + dest->log.max_size = set->log.max_size; + if (set->log.min_age_secs != 0) + dest->log.min_age_secs = set->log.min_age_secs; + if (set->log.log2_max_age_secs != 0) + dest->log.log2_max_age_secs = set->log.log2_max_age_secs; + + /* cache */ + if (set->cache.unaccessed_field_drop_secs != 0) + dest->cache.unaccessed_field_drop_secs = + set->cache.unaccessed_field_drop_secs; + if (set->cache.max_size != 0) + dest->cache.max_size = set->cache.max_size; + if (set->cache.purge_min_size != 0) + dest->cache.purge_min_size = set->cache.purge_min_size; + if (set->cache.purge_delete_percentage != 0) + dest->cache.purge_delete_percentage = + set->cache.purge_delete_percentage; + if (set->cache.purge_continued_percentage != 0) + dest->cache.purge_continued_percentage = + set->cache.purge_continued_percentage; + if (set->cache.purge_header_continue_count != 0) + dest->cache.purge_header_continue_count = + set->cache.purge_header_continue_count; + if (set->cache.record_max_size != 0) + dest->cache.record_max_size = set->cache.record_max_size; +} + +void mail_index_set_ext_init_data(struct mail_index *index, uint32_t ext_id, + const void *data, size_t size) +{ + const struct mail_index_registered_ext *rext; + + i_assert(index->set.ext_hdr_init_data == NULL || + index->set.ext_hdr_init_id == ext_id); + + rext = array_idx(&index->extensions, ext_id); + i_assert(rext->hdr_size == size); + + index->set.ext_hdr_init_id = ext_id; + i_free(index->set.ext_hdr_init_data); + index->set.ext_hdr_init_data = i_malloc(size); + memcpy(index->set.ext_hdr_init_data, data, size); +} + +bool mail_index_ext_name_is_valid(const char *name) +{ + size_t i; + + for (i = 0; name[i] != '\0'; i++) { + if (!i_isalnum(name[i]) && name[i] != '-' && name[i] != '_' && + name[i] != ' ') + return FALSE; + + } + return i == 0 || i < MAIL_INDEX_EXT_NAME_MAX_LENGTH; +} + +uint32_t mail_index_ext_register(struct mail_index *index, const char *name, + uint32_t default_hdr_size, + uint16_t default_record_size, + uint16_t default_record_align) +{ + struct mail_index_registered_ext rext; + uint32_t ext_id; + + if (!mail_index_ext_name_is_valid(name)) + i_panic("mail_index_ext_register(%s): Invalid name", name); + + if (default_record_size != 0 && default_record_align == 0) { + i_panic("mail_index_ext_register(%s): " + "Invalid record alignment", name); + } + + if (mail_index_ext_lookup(index, name, &ext_id)) + return ext_id; + + i_zero(&rext); + rext.name = p_strdup(index->extension_pool, name); + rext.index_idx = array_count(&index->extensions); + rext.hdr_size = default_hdr_size; + rext.record_size = default_record_size; + rext.record_align = default_record_align; + + array_push_back(&index->extensions, &rext); + return rext.index_idx; +} + +void mail_index_ext_register_resize_defaults(struct mail_index *index, + uint32_t ext_id, + uint32_t default_hdr_size, + uint16_t default_record_size, + uint16_t default_record_align) +{ + struct mail_index_registered_ext *rext; + + rext = array_idx_modifiable(&index->extensions, ext_id); + rext->hdr_size = default_hdr_size; + rext->record_size = default_record_size; + rext->record_align = default_record_align; +} + +bool mail_index_ext_lookup(struct mail_index *index, const char *name, + uint32_t *ext_id_r) +{ + const struct mail_index_registered_ext *extensions; + unsigned int i, count; + + extensions = array_get(&index->extensions, &count); + for (i = 0; i < count; i++) { + if (strcmp(extensions[i].name, name) == 0) { + *ext_id_r = i; + return TRUE; + } + } + + *ext_id_r = (uint32_t)-1; + return FALSE; +} + +void mail_index_register_expunge_handler(struct mail_index *index, + uint32_t ext_id, + mail_index_expunge_handler_t *cb) +{ + struct mail_index_registered_ext *rext; + + rext = array_idx_modifiable(&index->extensions, ext_id); + i_assert(rext->expunge_handler == NULL || rext->expunge_handler == cb); + + rext->expunge_handler = cb; +} + +void mail_index_unregister_expunge_handler(struct mail_index *index, + uint32_t ext_id) +{ + struct mail_index_registered_ext *rext; + + rext = array_idx_modifiable(&index->extensions, ext_id); + i_assert(rext->expunge_handler != NULL); + + rext->expunge_handler = NULL; +} + +bool mail_index_keyword_lookup(struct mail_index *index, + const char *keyword, unsigned int *idx_r) +{ + char *key; + void *value; + + /* keywords_hash keeps a name => index mapping of keywords. + Keywords are never removed from it, so the index values are valid + for the lifetime of the mail_index. */ + if (hash_table_lookup_full(index->keywords_hash, keyword, + &key, &value)) { + *idx_r = POINTER_CAST_TO(value, unsigned int); + return TRUE; + } + + *idx_r = UINT_MAX; + return FALSE; +} + +void mail_index_keyword_lookup_or_create(struct mail_index *index, + const char *keyword, + unsigned int *idx_r) +{ + char *keyword_dup; + + i_assert(*keyword != '\0'); + + if (mail_index_keyword_lookup(index, keyword, idx_r)) + return; + + keyword = keyword_dup = p_strdup(index->keywords_pool, keyword); + *idx_r = array_count(&index->keywords); + + hash_table_insert(index->keywords_hash, keyword_dup, + POINTER_CAST(*idx_r)); + array_push_back(&index->keywords, &keyword); + + /* keep the array NULL-terminated, but the NULL itself invisible */ + array_append_zero(&index->keywords); + array_pop_back(&index->keywords); +} + +const ARRAY_TYPE(keywords) *mail_index_get_keywords(struct mail_index *index) +{ + return &index->keywords; +} + +struct mail_keywords * +mail_index_keywords_create(struct mail_index *index, + const char *const keywords[]) +{ + struct mail_keywords *k; + unsigned int src, dest, i, count; + + count = str_array_length(keywords); + if (count == 0) { + k = i_new(struct mail_keywords, 1); + k->index = index; + k->refcount = 1; + return k; + } + + /* @UNSAFE */ + k = i_malloc(MALLOC_ADD(sizeof(struct mail_keywords), + MALLOC_MULTIPLY(sizeof(k->idx[0]), count))); + k->index = index; + k->refcount = 1; + + /* look up the keywords from index. they're never removed from there + so we can permanently store indexes to them. */ + for (src = dest = 0; src < count; src++) { + mail_index_keyword_lookup_or_create(index, keywords[src], + &k->idx[dest]); + /* ignore if this is a duplicate */ + for (i = 0; i < src; i++) { + if (k->idx[i] == k->idx[dest]) + break; + } + if (i == src) + dest++; + } + k->count = dest; + return k; +} + +struct mail_keywords * +mail_index_keywords_create_from_indexes(struct mail_index *index, + const ARRAY_TYPE(keyword_indexes) + *keyword_indexes) +{ + struct mail_keywords *k; + const unsigned int *indexes; + unsigned int src, dest, i, count; + + indexes = array_get(keyword_indexes, &count); + if (count == 0) { + k = i_new(struct mail_keywords, 1); + k->index = index; + k->refcount = 1; + return k; + } + + /* @UNSAFE */ + k = i_malloc(MALLOC_ADD(sizeof(struct mail_keywords), + MALLOC_MULTIPLY(sizeof(k->idx[0]), count))); + k->index = index; + k->refcount = 1; + + /* copy but skip duplicates */ + for (src = dest = 0; src < count; src++) { + for (i = 0; i < src; i++) { + if (k->idx[i] == indexes[src]) + break; + } + if (i == src) + k->idx[dest++] = indexes[src]; + } + k->count = dest; + return k; +} + +void mail_index_keywords_ref(struct mail_keywords *keywords) +{ + keywords->refcount++; +} + +void mail_index_keywords_unref(struct mail_keywords **_keywords) +{ + struct mail_keywords *keywords = *_keywords; + + i_assert(keywords->refcount > 0); + + *_keywords = NULL; + if (--keywords->refcount == 0) + i_free(keywords); +} + +int mail_index_try_open_only(struct mail_index *index) +{ + i_assert(index->fd == -1); + i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); + + /* Note that our caller must close index->fd by itself. */ + if (index->readonly) + errno = EACCES; + else { + index->fd = nfs_safe_open(index->filepath, O_RDWR); + index->readonly = FALSE; + } + + if (index->fd == -1 && errno == EACCES) { + index->fd = open(index->filepath, O_RDONLY); + index->readonly = TRUE; + } + + if (index->fd == -1) { + if (errno != ENOENT) { + mail_index_set_syscall_error(index, "open()"); + return -1; + } + + /* have to create it */ + return 0; + } + return 1; +} + +static int +mail_index_try_open(struct mail_index *index) +{ + int ret; + + i_assert(index->fd == -1); + + if (MAIL_INDEX_IS_IN_MEMORY(index)) + return 0; + + ret = mail_index_map(index, MAIL_INDEX_SYNC_HANDLER_HEAD); + if (ret == 0 && !index->readonly) { + /* it's corrupted - recreate it */ + if (index->fd != -1) { + if (close(index->fd) < 0) + mail_index_set_syscall_error(index, "close()"); + index->fd = -1; + } + } + return ret; +} + +int mail_index_create_tmp_file(struct mail_index *index, + const char *path_prefix, const char **path_r) +{ + mode_t old_mask; + const char *path; + int fd; + + i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); + + path = *path_r = t_strconcat(path_prefix, ".tmp", NULL); + old_mask = umask(0); + fd = open(path, O_RDWR|O_CREAT|O_EXCL, index->set.mode); + umask(old_mask); + if (fd == -1 && errno == EEXIST) { + /* stale temp file. unlink and recreate rather than overwriting, + just to make sure locking problems won't cause corruption */ + if (i_unlink(path) < 0) + return -1; + old_mask = umask(0); + fd = open(path, O_RDWR|O_CREAT|O_EXCL, index->set.mode); + umask(old_mask); + } + if (fd == -1) { + mail_index_file_set_syscall_error(index, path, "creat()"); + return -1; + } + + mail_index_fchown(index, fd, path); + return fd; +} + +static const char *mail_index_get_cache_path(struct mail_index *index) +{ + const char *dir; + + if (index->set.cache_dir != NULL) + dir = index->set.cache_dir; + else if (index->dir != NULL) + dir = index->dir; + else + return NULL; + return t_strconcat(dir, "/", index->prefix, + MAIL_CACHE_FILE_SUFFIX, NULL); +} + +static int mail_index_open_files(struct mail_index *index, + enum mail_index_open_flags flags) +{ + int ret; + + ret = mail_transaction_log_open(index->log); + if (ret == 0) { + if ((flags & MAIL_INDEX_OPEN_FLAG_CREATE) == 0) + return 0; + + /* if dovecot.index exists, read it first so that we can get + the correct indexid and log sequence */ + (void)mail_index_try_open(index); + + if (index->indexid == 0) { + /* Create a new indexid for us. If we're opening index + into memory, index->map doesn't exist yet. */ + index->indexid = ioloop_time; + index->initial_create = TRUE; + if (index->map != NULL) + index->map->hdr.indexid = index->indexid; + } + + ret = mail_transaction_log_create(index->log, FALSE); + if (index->map != NULL) { + /* log creation could have changed it if someone else + just created it. */ + index->map->hdr.indexid = index->indexid; + } + index->initial_create = FALSE; + } + if (ret >= 0) { + ret = index->map != NULL ? 1 : mail_index_try_open(index); + if (ret == 0 && !index->readonly) { + /* corrupted */ + mail_transaction_log_close(index->log); + ret = mail_transaction_log_create(index->log, TRUE); + if (ret == 0) { + if (index->map != NULL) + mail_index_unmap(&index->map); + index->map = mail_index_map_alloc(index); + } + } + } + if (ret < 0) { + /* open/create failed, fallback to in-memory indexes */ + if ((flags & MAIL_INDEX_OPEN_FLAG_CREATE) == 0) + return -1; + + if (mail_index_move_to_memory(index) < 0) + return -1; + } + + if (index->cache == NULL) { + const char *path = mail_index_get_cache_path(index); + index->cache = mail_cache_open_or_create_path(index, path); + } + return 1; +} + +static int +mail_index_open_opened(struct mail_index *index, + enum mail_index_open_flags flags) +{ + int ret; + + i_assert(index->map != NULL); + + if ((index->map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) != 0) { + /* index was marked corrupted. we'll probably need to + recreate the files. */ + mail_index_unmap(&index->map); + mail_index_close_file(index); + mail_transaction_log_close(index->log); + if ((ret = mail_index_open_files(index, flags)) <= 0) + return ret; + } + + index->open_count++; + return 1; +} + +int mail_index_open(struct mail_index *index, enum mail_index_open_flags flags) +{ + int ret; + + if (index->open_count > 0) { + if ((ret = mail_index_open_opened(index, flags)) <= 0) { + /* doesn't exist and create flag not used */ + } + return ret; + } + + index->filepath = MAIL_INDEX_IS_IN_MEMORY(index) ? + i_strdup("(in-memory index)") : + i_strconcat(index->dir, "/", index->prefix, NULL); + + mail_index_reset_error(index); + index->readonly = FALSE; + index->log_sync_locked = FALSE; + index->flags = flags; + index->readonly = (flags & MAIL_INDEX_OPEN_FLAG_READONLY) != 0; + if ((flags & MAIL_INDEX_OPEN_FLAG_DEBUG) != 0) + event_set_forced_debug(index->event, TRUE); + else + event_unset_forced_debug(index->event); + + if ((flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0 && + index->set.fsync_mode != FSYNC_MODE_ALWAYS) + i_fatal("nfs flush requires mail_fsync=always"); + if ((flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0 && + (flags & MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE) == 0) + i_fatal("nfs flush requires mmap_disable=yes"); + + /* NOTE: increase open_count only after mail_index_open_files(). + it's used elsewhere to check if we're doing an initial opening + of the index files */ + if ((ret = mail_index_open_files(index, flags)) <= 0) { + /* doesn't exist and create flag not used */ + mail_index_close_nonopened(index); + return ret; + } + + index->open_count++; + + if (index->log->head == NULL) { + mail_index_close(index); + mail_index_set_error(index, "Index is corrupted " + "(log->view->head == NULL)"); + return -1; + } + + i_assert(index->map != NULL); + mail_index_alloc_cache_index_opened(index); + return 1; +} + +int mail_index_open_or_create(struct mail_index *index, + enum mail_index_open_flags flags) +{ + int ret; + + flags |= MAIL_INDEX_OPEN_FLAG_CREATE; + ret = mail_index_open(index, flags); + i_assert(ret != 0); + return ret < 0 ? -1 : 0; +} + +void mail_index_close_file(struct mail_index *index) +{ + if (index->fd != -1) { + if (close(index->fd) < 0) + mail_index_set_syscall_error(index, "close()"); + index->fd = -1; + } +} + +static void mail_index_close_nonopened(struct mail_index *index) +{ + i_assert(!index->syncing); + + if (index->views != NULL) { + i_panic("Leaked view for index %s: Opened in %s:%u", + index->filepath, index->views->source_filename, + index->views->source_linenum); + } + i_assert(index->views == NULL); + + if (index->map != NULL) + mail_index_unmap(&index->map); + + mail_index_close_file(index); + mail_transaction_log_close(index->log); + if (index->cache != NULL) + mail_cache_free(&index->cache); + + i_free_and_null(index->filepath); + + index->indexid = 0; +} + +void mail_index_close(struct mail_index *index) +{ + i_assert(index->open_count > 0); + + mail_index_alloc_cache_index_closing(index); + if (--index->open_count == 0) + mail_index_close_nonopened(index); +} + +int mail_index_unlink(struct mail_index *index) +{ + const char *path; + int last_errno = 0; + + if (MAIL_INDEX_IS_IN_MEMORY(index) || index->readonly) + return 0; + + /* main index */ + if (unlink(index->filepath) < 0 && errno != ENOENT) + last_errno = errno; + + /* logs */ + path = t_strconcat(index->filepath, MAIL_TRANSACTION_LOG_SUFFIX, NULL); + if (unlink(path) < 0 && errno != ENOENT) + last_errno = errno; + + path = t_strconcat(index->filepath, + MAIL_TRANSACTION_LOG_SUFFIX".2", NULL); + if (unlink(path) < 0 && errno != ENOENT) + last_errno = errno; + + /* cache */ + path = t_strconcat(index->filepath, MAIL_CACHE_FILE_SUFFIX, NULL); + if (unlink(path) < 0 && errno != ENOENT) + last_errno = errno; + + if (last_errno == 0) + return 0; + else { + errno = last_errno; + return -1; + } +} + +int mail_index_reopen_if_changed(struct mail_index *index, bool *reopened_r, + const char **reason_r) +{ + struct stat st1, st2; + int ret; + + *reopened_r = FALSE; + + if (MAIL_INDEX_IS_IN_MEMORY(index)) { + *reason_r = "in-memory index"; + return 0; + } + + if (index->fd == -1) + goto final; + + if ((index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0) + nfs_flush_file_handle_cache(index->filepath); + if (nfs_safe_stat(index->filepath, &st2) < 0) { + if (errno == ENOENT) { + *reason_r = "index not found via stat()"; + return 0; + } + mail_index_set_syscall_error(index, "stat()"); + return -1; + } + + if (fstat(index->fd, &st1) < 0) { + if (!ESTALE_FSTAT(errno)) { + mail_index_set_syscall_error(index, "fstat()"); + return -1; + } + /* deleted/recreated, reopen */ + *reason_r = "index is stale"; + } else if (st1.st_ino == st2.st_ino && + CMP_DEV_T(st1.st_dev, st2.st_dev)) { + /* the same file */ + *reason_r = "index unchanged"; + return 1; + } else { + *reason_r = "index inode changed"; + } + + /* new file, new locks. the old fd can keep its locks, they don't + matter anymore as no-one's going to modify the file. */ + mail_index_close_file(index); + +final: + if ((ret = mail_index_try_open_only(index)) == 0) + *reason_r = "index not found via open()"; + else if (ret > 0) { + *reason_r = "index opened"; + *reopened_r = TRUE; + } + return ret; +} + +int mail_index_refresh(struct mail_index *index) +{ + int ret; + + ret = mail_index_map(index, MAIL_INDEX_SYNC_HANDLER_HEAD); + return ret <= 0 ? -1 : 0; +} + +struct mail_cache *mail_index_get_cache(struct mail_index *index) +{ + return index->cache; +} + +void mail_index_set_error(struct mail_index *index, const char *fmt, ...) +{ + va_list va; + + i_free(index->last_error.text); + + if (fmt == NULL) + index->last_error.text = NULL; + else { + va_start(va, fmt); + index->last_error.text = i_strdup_vprintf(fmt, va); + va_end(va); + + e_error(index->event, "%s", index->last_error.text); + } +} + +void mail_index_set_error_nolog(struct mail_index *index, const char *str) +{ + i_assert(str != NULL); + + char *old_error = index->last_error.text; + index->last_error.text = i_strdup(str); + i_free(old_error); +} + +bool mail_index_is_in_memory(struct mail_index *index) +{ + return MAIL_INDEX_IS_IN_MEMORY(index); +} + +static void mail_index_set_as_in_memory(struct mail_index *index) +{ + i_free_and_null(index->dir); + + i_free(index->filepath); + index->filepath = i_strdup("(in-memory index)"); +} + +int mail_index_move_to_memory(struct mail_index *index) +{ + struct mail_index_map *map; + + if (MAIL_INDEX_IS_IN_MEMORY(index)) + return index->map == NULL ? -1 : 0; + + if ((index->flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) + return -1; + + if (index->map == NULL) { + /* index was never even opened. just mark it as being in + memory and let the caller re-open the index. */ + i_assert(index->fd == -1); + mail_index_set_as_in_memory(index); + return -1; + } + + /* move index map to memory */ + if (!MAIL_INDEX_MAP_IS_IN_MEMORY(index->map)) { + map = mail_index_map_clone(index->map); + mail_index_unmap(&index->map); + index->map = map; + } + + if (index->log != NULL) { + /* move transaction log to memory */ + if (mail_transaction_log_move_to_memory(index->log) < 0) + return -1; + } + + if (index->fd != -1) { + if (close(index->fd) < 0) + mail_index_set_syscall_error(index, "close()"); + index->fd = -1; + } + mail_index_set_as_in_memory(index); + return 0; +} + +void mail_index_mark_corrupted(struct mail_index *index) +{ + index->indexid = 0; + + index->map->hdr.flags |= MAIL_INDEX_HDR_FLAG_CORRUPTED; + if (!index->readonly) { + if (unlink(index->filepath) < 0 && + errno != ENOENT && errno != ESTALE) + mail_index_set_syscall_error(index, "unlink()"); + (void)mail_transaction_log_unlink(index->log); + } +} + +bool mail_index_is_deleted(struct mail_index *index) +{ + return index->index_delete_requested || index->index_deleted; +} + +int mail_index_get_modification_time(struct mail_index *index, time_t *mtime_r) +{ + struct stat st; + const char *path; + + *mtime_r = 0; + if (MAIL_INDEX_IS_IN_MEMORY(index)) { + /* this function doesn't make sense for in-memory indexes */ + return 0; + } + + /* index may not be open, so index->filepath may be NULL */ + path = t_strconcat(index->dir, "/", index->prefix, + MAIL_TRANSACTION_LOG_SUFFIX, NULL); + if (stat(path, &st) < 0) { + if (errno == ENOENT) { + /* .log is always supposed to exist - don't bother + trying to stat(dovecot.index) */ + return 0; + } + mail_index_file_set_syscall_error(index, path, "stat()"); + return -1; + } + *mtime_r = st.st_mtime; + return 0; +} + +void mail_index_fchown(struct mail_index *index, int fd, const char *path) +{ + mode_t mode; + + if (index->set.gid == (gid_t)-1) { + /* no gid changing */ + return; + } else if (fchown(fd, (uid_t)-1, index->set.gid) == 0) { + /* success */ + return; + } if ((index->set.mode & 0060) >> 3 == (index->set.mode & 0006)) { + /* group and world permissions are the same, so group doesn't + really matter. ignore silently. */ + return; + } + if (errno != EPERM) + mail_index_file_set_syscall_error(index, path, "fchown()"); + else { + mail_index_set_error(index, "%s", + eperm_error_get_chgrp("fchown", path, index->set.gid, + index->set.gid_origin)); + } + + /* continue, but change permissions so that only the common + subset of group and world is used. this makes sure no one + gets any extra permissions. */ + mode = ((index->set.mode & 0060) >> 3) & (index->set.mode & 0006); + mode |= (mode << 3) | (index->set.mode & 0600); + if (fchmod(fd, mode) < 0) + mail_index_file_set_syscall_error(index, path, "fchmod()"); +} + +int mail_index_lock_sync(struct mail_index *index, const char *lock_reason) +{ + uint32_t file_seq; + uoff_t file_offset; + + return mail_transaction_log_sync_lock(index->log, lock_reason, + &file_seq, &file_offset); +} + +void mail_index_unlock(struct mail_index *index, const char *long_lock_reason) +{ + mail_transaction_log_sync_unlock(index->log, long_lock_reason); +} + +bool mail_index_is_locked(struct mail_index *index) +{ + return index->log_sync_locked; +} + +void mail_index_set_syscall_error(struct mail_index *index, + const char *function) +{ + mail_index_file_set_syscall_error(index, index->filepath, function); +} + +void mail_index_file_set_syscall_error(struct mail_index *index, + const char *filepath, + const char *function) +{ + const char *errstr; + + i_assert(filepath != NULL); + i_assert(function != NULL); + + if (errno == ENOENT) { + struct stat st; + int old_errno = errno; + i_assert(index->log->filepath != NULL); + if (nfs_safe_stat(index->log->filepath, &st) < 0 && + errno == ENOENT) { + /* the index log has gone away */ + index->index_deleted = TRUE; + errno = old_errno; + return; + } + errno = old_errno; + } + + if (ENOSPACE(errno)) { + index->last_error.nodiskspace = TRUE; + if ((index->flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) == 0) + return; + } + + if (errno == EACCES) { + function = t_strcut(function, '('); + if (strcmp(function, "creat") == 0 || + str_begins(function, "file_dotlock_")) + errstr = eacces_error_get_creating(function, filepath); + else + errstr = eacces_error_get(function, filepath); + mail_index_set_error(index, "%s", errstr); + } else { + const char *suffix = errno != EFBIG ? "" : + " (process was started with ulimit -f limit)"; + mail_index_set_error(index, "%s failed with file %s: " + "%m%s", function, filepath, suffix); + } +} + +const char *mail_index_get_error_message(struct mail_index *index) +{ + return index->last_error.text; +} + +void mail_index_reset_error(struct mail_index *index) +{ + i_free(index->last_error.text); + i_zero(&index->last_error); +} |