summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/index-storage.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/index-storage.c')
-rw-r--r--src/lib-storage/index/index-storage.c1296
1 files changed, 1296 insertions, 0 deletions
diff --git a/src/lib-storage/index/index-storage.c b/src/lib-storage/index/index-storage.c
new file mode 100644
index 0000000..f699dfb
--- /dev/null
+++ b/src/lib-storage/index/index-storage.c
@@ -0,0 +1,1296 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "ostream.h"
+#include "ioloop.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mkdir-parents.h"
+#include "dict.h"
+#include "fs-api.h"
+#include "message-header-parser.h"
+#include "mail-index-alloc-cache.h"
+#include "mail-index-private.h"
+#include "mail-index-modseq.h"
+#include "mailbox-log.h"
+#include "mailbox-list-private.h"
+#include "mail-search-build.h"
+#include "index-storage.h"
+#include "index-mail.h"
+#include "index-attachment.h"
+#include "index-thread-private.h"
+#include "index-mailbox-size.h"
+
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define LOCK_NOTIFY_INTERVAL 30
+
+struct index_storage_module index_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+
+static void set_cache_decisions(struct mail_cache *cache,
+ const char *set, const char *fields,
+ enum mail_cache_decision_type dec)
+{
+ struct mail_cache_field field;
+ const char *const *arr;
+ unsigned int idx;
+
+ if (fields == NULL || *fields == '\0')
+ return;
+
+ for (arr = t_strsplit_spaces(fields, " ,"); *arr != NULL; arr++) {
+ const char *name = *arr;
+
+ idx = mail_cache_register_lookup(cache, name);
+ if (idx != UINT_MAX) {
+ field = *mail_cache_register_get_field(cache, idx);
+ } else if (strncasecmp(name, "hdr.", 4) == 0) {
+ /* Do some sanity checking for the header name. Mainly
+ to make sure there aren't UTF-8 characters that look
+ like their ASCII equivalents or are completely
+ invisible. */
+ if (message_header_name_is_valid(name+4)) {
+ i_zero(&field);
+ field.name = name;
+ field.type = MAIL_CACHE_FIELD_HEADER;
+ } else {
+ i_error("%s: Header name '%s' has invalid character, ignoring",
+ set, name);
+ continue;
+ }
+ } else {
+ i_error("%s: Unknown cache field name '%s', ignoring",
+ set, *arr);
+ continue;
+ }
+
+ field.decision = dec;
+ mail_cache_register_fields(cache, &field, 1);
+ }
+}
+
+static void index_cache_register_defaults(struct mailbox *box)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ const struct mail_storage_settings *set = box->storage->set;
+ struct mail_cache *cache = box->cache;
+
+ ibox->cache_fields = i_malloc(sizeof(global_cache_fields));
+ memcpy(ibox->cache_fields, global_cache_fields,
+ sizeof(global_cache_fields));
+ mail_cache_register_fields(cache, ibox->cache_fields,
+ MAIL_INDEX_CACHE_FIELD_COUNT);
+
+ if (strcmp(set->mail_never_cache_fields, "*") == 0) {
+ /* all caching disabled for now */
+ box->mail_cache_disabled = TRUE;
+ return;
+ }
+
+ set_cache_decisions(cache, "mail_cache_fields",
+ set->mail_cache_fields,
+ MAIL_CACHE_DECISION_TEMP);
+ set_cache_decisions(cache, "mail_always_cache_fields",
+ set->mail_always_cache_fields,
+ MAIL_CACHE_DECISION_YES |
+ MAIL_CACHE_DECISION_FORCED);
+ set_cache_decisions(cache, "mail_never_cache_fields",
+ set->mail_never_cache_fields,
+ MAIL_CACHE_DECISION_NO |
+ MAIL_CACHE_DECISION_FORCED);
+}
+
+void index_storage_lock_notify(struct mailbox *box,
+ enum mailbox_lock_notify_type notify_type,
+ unsigned int secs_left)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ struct mail_storage *storage = box->storage;
+ const char *str;
+ time_t now;
+
+ /* if notify type changes, print the message immediately */
+ now = time(NULL);
+ if (ibox->last_notify_type == MAILBOX_LOCK_NOTIFY_NONE ||
+ ibox->last_notify_type == notify_type) {
+ if (ibox->last_notify_type == MAILBOX_LOCK_NOTIFY_NONE &&
+ notify_type == MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE) {
+ /* first override notification, show it */
+ } else {
+ if (now < ibox->next_lock_notify || secs_left < 15)
+ return;
+ }
+ }
+
+ ibox->next_lock_notify = now + LOCK_NOTIFY_INTERVAL;
+ ibox->last_notify_type = notify_type;
+
+ switch (notify_type) {
+ case MAILBOX_LOCK_NOTIFY_NONE:
+ break;
+ case MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT:
+ if (storage->callbacks.notify_no == NULL)
+ break;
+
+ str = t_strdup_printf("Mailbox is locked, will abort in "
+ "%u seconds", secs_left);
+ storage->callbacks.
+ notify_no(box, str, storage->callback_context);
+ break;
+ case MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE:
+ if (storage->callbacks.notify_ok == NULL)
+ break;
+
+ str = t_strdup_printf("Stale mailbox lock file detected, "
+ "will override in %u seconds", secs_left);
+ storage->callbacks.
+ notify_ok(box, str, storage->callback_context);
+ break;
+ }
+}
+
+void index_storage_lock_notify_reset(struct mailbox *box)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+
+ ibox->next_lock_notify = time(NULL) + LOCK_NOTIFY_INTERVAL;
+ ibox->last_notify_type = MAILBOX_LOCK_NOTIFY_NONE;
+}
+
+static int
+index_mailbox_alloc_index(struct mailbox *box, struct mail_index **index_r)
+{
+ const char *index_dir, *mailbox_path;
+
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &mailbox_path) < 0)
+ return -1;
+ if ((box->flags & MAILBOX_FLAG_NO_INDEX_FILES) != 0 ||
+ mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX,
+ &index_dir) <= 0)
+ index_dir = NULL;
+ /* Note that this may cause box->event to live longer than box */
+ *index_r = mail_index_alloc_cache_get(box->event,
+ mailbox_path, index_dir,
+ box->index_prefix);
+ return 0;
+}
+
+int index_storage_mailbox_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ if (auto_boxes && mailbox_is_autocreated(box)) {
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+ }
+
+ return index_storage_mailbox_exists_full(box, NULL, existence_r);
+}
+
+int index_storage_mailbox_exists_full(struct mailbox *box, const char *subdir,
+ enum mailbox_existence *existence_r)
+{
+ struct stat st;
+ const char *path, *path2, *index_path;
+ int ret;
+
+ /* see if it's selectable */
+ ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path);
+ if (ret < 0) {
+ if (mailbox_get_last_mail_error(box) != MAIL_ERROR_NOTFOUND)
+ return -1;
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ return 0;
+ }
+ if (ret == 0) {
+ /* no mailboxes in this storage? */
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ return 0;
+ }
+
+ ret = (subdir != NULL || !box->list->set.iter_from_index_dir) ? 0 :
+ mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &index_path);
+ if (ret > 0 && strcmp(path, index_path) != 0) {
+ /* index directory is different - prefer looking it up first
+ since it might be on a faster storage. since the directory
+ itself exists also for \NoSelect mailboxes, we'll need to
+ check the dovecot.index.log existence. */
+ index_path = t_strconcat(index_path, "/", box->index_prefix,
+ ".log", NULL);
+ if (stat(index_path, &st) == 0) {
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+ }
+ }
+
+ if (subdir != NULL)
+ path = t_strconcat(path, "/", subdir, NULL);
+ if (stat(path, &st) == 0) {
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+ }
+ if (!ENOTFOUND(errno) && errno != EACCES) {
+ mailbox_set_critical(box, "stat(%s) failed: %m", path);
+ return -1;
+ }
+
+ /* see if it's non-selectable */
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_DIR, &path2) <= 0 ||
+ (strcmp(path, path2) != 0 && stat(path2, &st) == 0)) {
+ *existence_r = MAILBOX_EXISTENCE_NOSELECT;
+ return 0;
+ }
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ return 0;
+}
+
+int index_storage_mailbox_alloc_index(struct mailbox *box)
+{
+ const char *cache_dir;
+
+ if (box->index != NULL)
+ return 0;
+
+ if (mailbox_create_missing_dir(box, MAILBOX_LIST_PATH_TYPE_INDEX) < 0)
+ return -1;
+ if (index_mailbox_alloc_index(box, &box->index) < 0)
+ return -1;
+
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX_CACHE,
+ &cache_dir) > 0) {
+ if (mailbox_create_missing_dir(box, MAILBOX_LIST_PATH_TYPE_INDEX_CACHE) < 0)
+ return -1;
+ mail_index_set_cache_dir(box->index, cache_dir);
+ }
+ mail_index_set_fsync_mode(box->index,
+ box->storage->set->parsed_fsync_mode, 0);
+ mail_index_set_lock_method(box->index,
+ box->storage->set->parsed_lock_method,
+ mail_storage_get_lock_timeout(box->storage, UINT_MAX));
+
+ const struct mail_storage_settings *set = box->storage->set;
+ struct mail_index_optimization_settings optimization_set = {
+ .index = {
+ .rewrite_min_log_bytes = set->mail_index_rewrite_min_log_bytes,
+ .rewrite_max_log_bytes = set->mail_index_rewrite_max_log_bytes,
+ },
+ .log = {
+ .min_size = set->mail_index_log_rotate_min_size,
+ .max_size = set->mail_index_log_rotate_max_size,
+ .min_age_secs = set->mail_index_log_rotate_min_age,
+ .log2_max_age_secs = set->mail_index_log2_max_age,
+ },
+ .cache = {
+ .unaccessed_field_drop_secs = set->mail_cache_unaccessed_field_drop,
+ .record_max_size = set->mail_cache_record_max_size,
+ .max_size = set->mail_cache_max_size,
+ .purge_min_size = set->mail_cache_purge_min_size,
+ .purge_delete_percentage = set->mail_cache_purge_delete_percentage,
+ .purge_continued_percentage = set->mail_cache_purge_continued_percentage,
+ .purge_header_continue_count = set->mail_cache_purge_header_continue_count,
+ },
+ };
+ mail_index_set_optimization_settings(box->index, &optimization_set);
+ return 0;
+}
+
+int index_storage_mailbox_open(struct mailbox *box, bool move_to_memory)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ enum mail_index_open_flags index_flags;
+ int ret;
+
+ i_assert(!box->opened);
+
+ index_flags = ibox->index_flags;
+ if (move_to_memory)
+ index_flags &= ENUM_NEGATE(MAIL_INDEX_OPEN_FLAG_CREATE);
+
+ if (index_storage_mailbox_alloc_index(box) < 0)
+ return -1;
+
+ /* make sure mail_index_set_permissions() has been called */
+ (void)mailbox_get_permissions(box);
+
+ ret = mail_index_open(box->index, index_flags);
+ if (ret <= 0 || move_to_memory) {
+ if ((index_flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) {
+ i_assert(ret <= 0);
+ mailbox_set_index_error(box);
+ return -1;
+ }
+
+ if (mail_index_move_to_memory(box->index) < 0) {
+ /* try opening once more. it should be created
+ directly into memory now. */
+ if (mail_index_open_or_create(box->index,
+ index_flags) < 0)
+ i_panic("in-memory index creation failed");
+ }
+ }
+ if ((index_flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) {
+ if (mail_index_is_in_memory(box->index)) {
+ mailbox_set_critical(box,
+ "Couldn't create index file");
+ mail_index_close(box->index);
+ return -1;
+ }
+ }
+
+ if ((box->flags & MAILBOX_FLAG_OPEN_DELETED) == 0) {
+ if (mail_index_is_deleted(box->index)) {
+ mailbox_set_deleted(box);
+ mail_index_close(box->index);
+ return -1;
+ }
+ }
+ if ((box->flags & MAILBOX_FLAG_FSCK) != 0) {
+ if (mail_index_fsck(box->index) < 0) {
+ mailbox_set_index_error(box);
+ return -1;
+ }
+ }
+
+ box->cache = mail_index_get_cache(box->index);
+ index_cache_register_defaults(box);
+ box->view = mail_index_view_open(box->index);
+ ibox->keyword_names = mail_index_get_keywords(box->index);
+ box->vsize_hdr_ext_id =
+ mail_index_ext_register(box->index, "hdr-vsize",
+ sizeof(struct mailbox_index_vsize), 0,
+ sizeof(uint64_t));
+ box->pop3_uidl_hdr_ext_id =
+ mail_index_ext_register(box->index, "hdr-pop3-uidl",
+ sizeof(struct mailbox_index_pop3_uidl), 0, 0);
+ box->box_name_hdr_ext_id =
+ mail_index_ext_register(box->index, "box-name", 0, 0, 0);
+
+ box->box_last_rename_stamp_ext_id =
+ mail_index_ext_register(box->index, "last-rename-stamp",
+ sizeof(uint32_t), 0, sizeof(uint32_t));
+ box->mail_vsize_ext_id = mail_index_ext_register(box->index, "vsize", 0,
+ sizeof(uint32_t),
+ sizeof(uint32_t));
+
+ box->opened = TRUE;
+
+ if ((box->enabled_features & MAILBOX_FEATURE_CONDSTORE) != 0)
+ mail_index_modseq_enable(box->index);
+
+ index_thread_mailbox_opened(box);
+ hook_mailbox_opened(box);
+ return 0;
+}
+
+void index_storage_mailbox_alloc(struct mailbox *box, const char *vname,
+ enum mailbox_flags flags,
+ const char *index_prefix)
+{
+ static unsigned int mailbox_generation_sequence = 0;
+ struct index_mailbox_context *ibox;
+
+ i_assert(vname != NULL);
+
+ box->generation_sequence = ++mailbox_generation_sequence;
+ box->vname = p_strdup(box->pool, vname);
+ box->name = p_strdup(box->pool,
+ mailbox_list_get_storage_name(box->list, vname));
+ box->flags = flags;
+ box->index_prefix = p_strdup(box->pool, index_prefix);
+ box->event = event_create(box->storage->event);
+ event_add_category(box->event, &event_category_mailbox);
+ event_add_str(box->event, "mailbox", box->vname);
+ event_set_append_log_prefix(box->event,
+ t_strdup_printf("Mailbox %s: ", str_sanitize(box->vname, 128)));
+
+ p_array_init(&box->search_results, box->pool, 16);
+ array_create(&box->module_contexts,
+ box->pool, sizeof(void *), 5);
+
+ ibox = p_new(box->pool, struct index_mailbox_context, 1);
+ ibox->list_index_sync_ext_id = (uint32_t)-1;
+ ibox->index_flags = MAIL_INDEX_OPEN_FLAG_CREATE |
+ mail_storage_settings_to_index_flags(box->storage->set);
+ if ((box->flags & MAILBOX_FLAG_SAVEONLY) != 0)
+ ibox->index_flags |= MAIL_INDEX_OPEN_FLAG_SAVEONLY;
+ if (event_want_debug(box->event))
+ ibox->index_flags |= MAIL_INDEX_OPEN_FLAG_DEBUG;
+ ibox->next_lock_notify = time(NULL) + LOCK_NOTIFY_INTERVAL;
+ MODULE_CONTEXT_SET(box, index_storage_module, ibox);
+
+ box->inbox_user = strcmp(box->name, "INBOX") == 0 &&
+ (box->list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0;
+ box->inbox_any = strcmp(box->name, "INBOX") == 0 &&
+ (box->list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0;
+}
+
+int index_storage_mailbox_enable(struct mailbox *box,
+ enum mailbox_feature feature)
+{
+ if ((feature & MAILBOX_FEATURE_CONDSTORE) != 0) {
+ box->enabled_features |= MAILBOX_FEATURE_CONDSTORE;
+ if (box->opened)
+ mail_index_modseq_enable(box->index);
+ }
+ return 0;
+}
+
+void index_storage_mailbox_close(struct mailbox *box)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+
+ mailbox_watch_remove_all(box);
+ i_stream_unref(&box->input);
+
+ if (box->view_pvt != NULL)
+ mail_index_view_close(&box->view_pvt);
+ if (box->index_pvt != NULL)
+ mail_index_close(box->index_pvt);
+ if (box->view != NULL) {
+ mail_index_view_close(&box->view);
+ mail_index_close(box->index);
+ }
+ box->cache = NULL;
+
+ ibox->keyword_names = NULL;
+ i_free_and_null(ibox->cache_fields);
+
+ ibox->sync_last_check = 0;
+}
+
+static void index_storage_mailbox_unref_indexes(struct mailbox *box)
+{
+ if (box->index_pvt != NULL)
+ mail_index_alloc_cache_unref(&box->index_pvt);
+ if (box->index != NULL)
+ mail_index_alloc_cache_unref(&box->index);
+}
+
+void index_storage_mailbox_free(struct mailbox *box)
+{
+ index_storage_mailbox_unref_indexes(box);
+ event_unref(&box->event);
+}
+
+static void
+index_storage_mailbox_update_cache(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ const struct mailbox_cache_field *updates = update->cache_updates;
+ ARRAY(struct mail_cache_field) new_fields;
+ const struct mail_cache_field *old_fields;
+ struct mail_cache_field field;
+ unsigned int i, j, old_count;
+
+ old_fields = mail_cache_register_get_list(box->cache,
+ pool_datastack_create(),
+ &old_count);
+
+ /* There shouldn't be many fields, so don't worry about O(n^2). */
+ t_array_init(&new_fields, 32);
+ for (i = 0; updates[i].name != NULL; i++) {
+ /* see if it's an existing field */
+ for (j = 0; j < old_count; j++) {
+ if (strcmp(updates[i].name, old_fields[j].name) == 0)
+ break;
+ }
+ if (j != old_count) {
+ field = old_fields[j];
+ } else if (str_begins(updates[i].name, "hdr.")) {
+ /* new header */
+ i_zero(&field);
+ field.name = updates[i].name;
+ field.type = MAIL_CACHE_FIELD_HEADER;
+ } else {
+ /* new unknown field. we can't do anything about
+ this since we don't know its type */
+ continue;
+ }
+ field.decision = updates[i].decision;
+ if (updates[i].last_used != (time_t)-1)
+ field.last_used = updates[i].last_used;
+ array_push_back(&new_fields, &field);
+ }
+ if (array_count(&new_fields) > 0) {
+ mail_cache_register_fields(box->cache,
+ array_front_modifiable(&new_fields),
+ array_count(&new_fields));
+ }
+}
+
+static int
+index_storage_mailbox_update_pvt(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ struct mail_index_transaction *trans;
+ struct mail_index_view *view;
+ int ret;
+
+ if ((ret = mailbox_open_index_pvt(box)) <= 0)
+ return ret;
+
+ mail_index_refresh(box->index_pvt);
+ view = mail_index_view_open(box->index_pvt);
+ trans = mail_index_transaction_begin(view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ if (update->min_highest_modseq != 0 &&
+ mail_index_modseq_get_highest(view) < update->min_highest_pvt_modseq) {
+ mail_index_modseq_enable(box->index_pvt);
+ mail_index_update_highest_modseq(trans,
+ update->min_highest_pvt_modseq);
+ }
+
+ if ((ret = mail_index_transaction_commit(&trans)) < 0)
+ mailbox_set_index_error(box);
+ mail_index_view_close(&view);
+ return ret;
+}
+
+int index_storage_mailbox_update_common(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ int ret = 0;
+
+ if (update->cache_updates != NULL)
+ index_storage_mailbox_update_cache(box, update);
+
+ if (update->min_highest_pvt_modseq != 0) {
+ if (index_storage_mailbox_update_pvt(box, update) < 0)
+ ret = -1;
+ }
+ return ret;
+}
+
+int index_storage_mailbox_update(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ const struct mail_index_header *hdr;
+ struct mail_index_view *view;
+ struct mail_index_transaction *trans;
+ int ret;
+
+ if (mailbox_open(box) < 0)
+ return -1;
+
+ /* make sure we get the latest index info */
+ mail_index_refresh(box->index);
+ view = mail_index_view_open(box->index);
+ hdr = mail_index_get_header(view);
+
+ trans = mail_index_transaction_begin(view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ if (update->uid_validity != 0 &&
+ hdr->uid_validity != update->uid_validity) {
+ uint32_t uid_validity = update->uid_validity;
+
+ if (hdr->uid_validity != 0) {
+ /* UIDVALIDITY change requires index to be reset */
+ mail_index_reset(trans);
+ }
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, uid_validity),
+ &uid_validity, sizeof(uid_validity), TRUE);
+ }
+ if (update->min_next_uid != 0 &&
+ hdr->next_uid < update->min_next_uid) {
+ uint32_t next_uid = update->min_next_uid;
+
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, next_uid),
+ &next_uid, sizeof(next_uid), FALSE);
+ }
+ if (update->min_first_recent_uid != 0 &&
+ hdr->first_recent_uid < update->min_first_recent_uid) {
+ uint32_t first_recent_uid = update->min_first_recent_uid;
+
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, first_recent_uid),
+ &first_recent_uid, sizeof(first_recent_uid), FALSE);
+ }
+ if (update->min_highest_modseq != 0 &&
+ mail_index_modseq_get_highest(view) < update->min_highest_modseq) {
+ mail_index_modseq_enable(box->index);
+ mail_index_update_highest_modseq(trans,
+ update->min_highest_modseq);
+ }
+
+ if ((ret = mail_index_transaction_commit(&trans)) < 0)
+ mailbox_set_index_error(box);
+ mail_index_view_close(&view);
+ return ret < 0 ? -1 :
+ index_storage_mailbox_update_common(box, update);
+}
+
+int index_storage_mailbox_create(struct mailbox *box, bool directory)
+{
+ const char *path, *p;
+ enum mailbox_list_path_type type;
+ enum mailbox_existence existence;
+ bool create_parent_dir;
+ int ret;
+
+ if ((box->list->props & MAILBOX_LIST_PROP_NO_NOSELECT) != 0) {
+ /* Layout doesn't support creating \NoSelect mailboxes.
+ Switch to creating a selectable mailbox. */
+ directory = FALSE;
+ }
+
+ type = directory ? MAILBOX_LIST_PATH_TYPE_DIR :
+ MAILBOX_LIST_PATH_TYPE_MAILBOX;
+ if ((ret = mailbox_get_path_to(box, type, &path)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* layout=none */
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Mailbox creation not supported");
+ return -1;
+ }
+ create_parent_dir = !directory &&
+ (box->list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0;
+ if (create_parent_dir) {
+ /* we only need to make sure that the parent directory exists */
+ p = strrchr(path, '/');
+ if (p == NULL)
+ return 1;
+ path = t_strdup_until(path, p);
+ }
+
+ if ((ret = mailbox_mkdir(box, path, type)) < 0)
+ return -1;
+ if (box->list->set.iter_from_index_dir) {
+ /* need to also create the directory to index path or
+ iteration won't find it. */
+ int ret2;
+
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &path) <= 0)
+ i_unreached();
+ if ((ret2 = mailbox_mkdir(box, path, type)) < 0)
+ return -1;
+ if (ret == 0 && ret2 > 0) {
+ /* finish partial creation: existed in mail directory,
+ but not in index directory. */
+ ret = 1;
+ }
+ }
+ mailbox_refresh_permissions(box);
+ if (ret == 0) {
+ /* directory already exists */
+ if (create_parent_dir)
+ return 1;
+ if (!directory && *box->list->set.mailbox_dir_name == '\0') {
+ /* For example: layout=fs, path=~/Maildir/foo
+ might itself exist, but does it have the
+ cur|new|tmp subdirs? */
+ if (mailbox_exists(box, FALSE, &existence) < 0)
+ return -1;
+ if (existence != MAILBOX_EXISTENCE_SELECT)
+ return 1;
+ } else if (!box->storage->rebuilding_list_index) {
+ /* ignore existing location if we are recovering list index */
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "Mailbox already exists");
+ return -1;
+ }
+ }
+
+ if (directory) {
+ /* we only wanted to create the directory and it's done now */
+ return 0;
+ }
+ /* the caller should still create the mailbox */
+ return 1;
+}
+
+int index_storage_mailbox_delete_dir(struct mailbox *box, bool mailbox_deleted)
+{
+ guid_128_t dir_sha128;
+ enum mail_error error;
+
+ if (mailbox_list_delete_dir(box->list, box->name) == 0)
+ return 0;
+
+ mailbox_list_get_last_error(box->list, &error);
+ if (error != MAIL_ERROR_NOTFOUND || !mailbox_deleted) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ /* failed directory deletion, but mailbox deletion succeeded.
+ this was probably maildir++, which internally deleted the
+ directory as well. add changelog record about that too. */
+ mailbox_name_get_sha128(box->vname, dir_sha128);
+ mailbox_list_add_change(box->list, MAILBOX_LOG_RECORD_DELETE_DIR,
+ dir_sha128);
+ return 0;
+}
+
+static int
+mailbox_delete_all_attributes(struct mailbox_transaction_context *t,
+ enum mail_attribute_type type)
+{
+ struct mailbox_attribute_iter *iter;
+ const char *key;
+ int ret = 0;
+ bool inbox = t->box->inbox_any;
+
+ iter = mailbox_attribute_iter_init(t->box, type, "");
+ while ((key = mailbox_attribute_iter_next(iter)) != NULL) {
+ if (inbox &&
+ str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER))
+ continue;
+
+ if (mailbox_attribute_unset(t, type, key) < 0) {
+ if (mailbox_get_last_mail_error(t->box) != MAIL_ERROR_NOTPOSSIBLE) {
+ ret = -1;
+ break;
+ }
+ }
+ }
+ if (mailbox_attribute_iter_deinit(&iter) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int mailbox_expunge_all_data(struct mailbox *box)
+{
+ struct mail_search_context *ctx;
+ struct mailbox_transaction_context *t;
+ struct mail *mail;
+ struct mail_search_args *search_args;
+
+ (void)mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ);
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+ ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(ctx, &mail))
+ mail_expunge(mail);
+
+ if (mailbox_search_deinit(&ctx) < 0) {
+ mailbox_transaction_rollback(&t);
+ return -1;
+ }
+
+ if (mailbox_delete_all_attributes(t, MAIL_ATTRIBUTE_TYPE_PRIVATE) < 0 ||
+ mailbox_delete_all_attributes(t, MAIL_ATTRIBUTE_TYPE_SHARED) < 0) {
+ mailbox_transaction_rollback(&t);
+ return -1;
+ }
+ if (mailbox_transaction_commit(&t) < 0)
+ return -1;
+ /* sync to actually perform the expunges */
+ return mailbox_sync(box, 0);
+}
+
+int index_storage_mailbox_delete_pre(struct mailbox *box)
+{
+ struct mailbox_status status;
+
+ if (!box->opened) {
+ /* \noselect mailbox, try deleting only the directory */
+ if (index_storage_mailbox_delete_dir(box, FALSE) == 0)
+ return 0;
+ if (mailbox_is_autocreated(box)) {
+ /* Return success when trying to delete autocreated
+ mailbox. The client sees it as existing, so we
+ shouldn't be returning an error. */
+ return 0;
+ }
+ return -1;
+ }
+
+ if ((box->list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ /* specifically support symlinked shared mailboxes. a deletion
+ will simply remove the symlink, not actually expunge any
+ mails */
+ if (mailbox_list_delete_symlink(box->list, box->name) == 0)
+ return 0;
+ }
+
+ /* we can't easily atomically delete all mails and the mailbox. so:
+ 1) expunge all mails
+ 2) mark the mailbox deleted (modifications after this will fail)
+ 3) check if a race condition between 1) and 2) added any mails:
+ yes) abort and undelete mailbox
+ no) finish deleting the mailbox
+ */
+
+ if (!box->deleting_must_be_empty) {
+ if (mailbox_expunge_all_data(box) < 0)
+ return -1;
+ }
+ if (mailbox_mark_index_deleted(box, TRUE) < 0)
+ return -1;
+
+ if (!box->delete_skip_empty_check || box->deleting_must_be_empty) {
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0)
+ return -1;
+ mailbox_get_open_status(box, STATUS_MESSAGES, &status);
+ if (status.messages == 0)
+ ;
+ else if (box->deleting_must_be_empty) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "Mailbox isn't empty");
+ return -1;
+ } else {
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "New mails were added to mailbox during deletion");
+ return -1;
+ }
+ }
+ return 1;
+}
+
+int index_storage_mailbox_delete_post(struct mailbox *box)
+{
+ struct mailbox_metadata metadata;
+ int ret_guid;
+
+ ret_guid = mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata);
+
+ /* Make sure the indexes are closed before trying to delete the
+ directory that contains them. It can still fail with some NFS
+ implementations if indexes are opened by another session, but
+ that can't really be helped. */
+ mailbox_close(box);
+ index_storage_mailbox_unref_indexes(box);
+ mail_index_alloc_cache_destroy_unrefed();
+
+ if (box->list->v.delete_mailbox(box->list, box->name) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+
+ if (ret_guid == 0) {
+ mailbox_list_add_change(box->list,
+ MAILBOX_LOG_RECORD_DELETE_MAILBOX,
+ metadata.guid);
+ }
+ if (index_storage_mailbox_delete_dir(box, TRUE) < 0) {
+ if (mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS)
+ return -1;
+ /* we deleted the mailbox, but couldn't delete the directory
+ because it has children. that's not an error. */
+ }
+ return 0;
+}
+
+int index_storage_mailbox_delete(struct mailbox *box)
+{
+ int ret;
+
+ if ((ret = index_storage_mailbox_delete_pre(box)) <= 0)
+ return ret;
+ /* mails have been now successfully deleted. some mailbox formats may
+ at this point do some other deletion that is required for it.
+ the _post() deletion will close the index and delete the
+ directory. */
+ return index_storage_mailbox_delete_post(box);
+}
+
+int index_storage_mailbox_rename(struct mailbox *src, struct mailbox *dest)
+{
+ guid_128_t guid;
+
+ if (src->list->v.rename_mailbox(src->list, src->name,
+ dest->list, dest->name) < 0) {
+ mail_storage_copy_list_error(src->storage, src->list);
+ return -1;
+ }
+
+ if (mailbox_open(dest) == 0) {
+ struct mail_index_transaction *t =
+ mail_index_transaction_begin(dest->view, 0);
+
+ uint32_t stamp = ioloop_time;
+
+ mail_index_update_header_ext(t, dest->box_last_rename_stamp_ext_id,
+ 0, &stamp, sizeof(stamp));
+
+ /* can't do much if this fails anyways */
+ (void)mail_index_transaction_commit(&t);
+ }
+
+ /* we'll track mailbox names, instead of GUIDs. We may be renaming a
+ non-selectable mailbox (directory), which doesn't even have a GUID */
+ mailbox_name_get_sha128(dest->vname, guid);
+ mailbox_list_add_change(src->list, MAILBOX_LOG_RECORD_RENAME, guid);
+ return 0;
+}
+
+int index_mailbox_view_update_last_temp_file_scan(struct mail_index_view *view)
+{
+ uint32_t last_temp_file_scan = ioloop_time;
+ struct mail_index_transaction *trans =
+ mail_index_transaction_begin(view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, last_temp_file_scan),
+ &last_temp_file_scan, sizeof(last_temp_file_scan), TRUE);
+ return mail_index_transaction_commit(&trans);
+}
+
+int index_mailbox_update_last_temp_file_scan(struct mailbox *box)
+{
+ if (index_mailbox_view_update_last_temp_file_scan(box->view) < 0) {
+ mailbox_set_index_error(box);
+ return -1;
+ }
+ return 0;
+}
+
+bool index_storage_is_readonly(struct mailbox *box)
+{
+ return (box->flags & MAILBOX_FLAG_READONLY) != 0;
+}
+
+bool index_storage_is_inconsistent(struct mailbox *box)
+{
+ return box->view != NULL &&
+ mail_index_view_is_inconsistent(box->view);
+}
+
+void index_save_context_free(struct mail_save_context *ctx)
+{
+ i_assert(ctx->dest_mail != NULL);
+
+ index_mail_save_finish(ctx);
+ if (ctx->data.keywords != NULL)
+ mailbox_keywords_unref(&ctx->data.keywords);
+ i_free_and_null(ctx->data.from_envelope);
+ i_free_and_null(ctx->data.guid);
+ i_free_and_null(ctx->data.pop3_uidl);
+ index_attachment_save_free(ctx);
+ i_zero(&ctx->data);
+
+ ctx->unfinished = FALSE;
+}
+
+static void
+mail_copy_cache_field(struct mail_save_context *ctx, struct mail *src_mail,
+ uint32_t dest_seq, const char *name, buffer_t *buf)
+{
+ struct mailbox_transaction_context *dest_trans = ctx->transaction;
+ const struct mail_cache_field *dest_field;
+ unsigned int src_field_idx, dest_field_idx;
+ uint32_t t;
+ bool add = FALSE;
+
+ src_field_idx = mail_cache_register_lookup(src_mail->box->cache, name);
+ i_assert(src_field_idx != UINT_MAX);
+
+ dest_field_idx = mail_cache_register_lookup(dest_trans->box->cache, name);
+ if (dest_field_idx == UINT_MAX) {
+ /* unknown field */
+ return;
+ }
+ dest_field = mail_cache_register_get_field(dest_trans->box->cache,
+ dest_field_idx);
+ if ((dest_field->decision &
+ ENUM_NEGATE(MAIL_CACHE_DECISION_FORCED)) == MAIL_CACHE_DECISION_NO) {
+ /* field not wanted in destination mailbox */
+ return;
+ }
+
+ buffer_set_used_size(buf, 0);
+ if (strcmp(name, "date.save") == 0) {
+ /* save date must update when mail is copied */
+ t = ioloop_time;
+ buffer_append(buf, &t, sizeof(t));
+ add = TRUE;
+ } else if (mail_cache_lookup_field(src_mail->transaction->cache_view, buf,
+ src_mail->seq, src_field_idx) <= 0) {
+ /* error / not found */
+ buffer_set_used_size(buf, 0);
+ } else {
+ if (strcmp(name, "size.physical") == 0 ||
+ strcmp(name, "size.virtual") == 0) {
+ /* FIXME: until mail_cache_lookup() can read unwritten
+ cached data from buffer, we'll do this optimization
+ to make quota plugin's work faster */
+ struct index_mail *imail =
+ INDEX_MAIL(ctx->dest_mail);
+ uoff_t size;
+
+ i_assert(buf->used == sizeof(size));
+ memcpy(&size, buf->data, sizeof(size));
+ if (strcmp(name, "size.physical") == 0)
+ imail->data.physical_size = size;
+ else
+ imail->data.virtual_size = size;
+ }
+ /* NOTE: we'll want to add also nonexistent headers, which
+ will keep the buf empty */
+ add = TRUE;
+ }
+ if (add) {
+ mail_cache_add(dest_trans->cache_trans, dest_seq,
+ dest_field_idx, buf->data, buf->used);
+ }
+}
+
+static void
+index_copy_vsize_extension(struct mail_save_context *ctx,
+ struct mail *src_mail, uint32_t dest_seq)
+{
+ const uint32_t *vsizep;
+ bool expunged ATTR_UNUSED;
+
+ vsizep = index_mail_get_vsize_extension(src_mail);
+ if (vsizep == NULL || *vsizep == 0)
+ return;
+ uint32_t vsize = *vsizep;
+
+ if (vsize < (uint32_t)-1) {
+ /* copy the vsize record to the destination index */
+ mail_index_update_ext(ctx->transaction->itrans, dest_seq,
+ ctx->transaction->box->mail_vsize_ext_id,
+ &vsize, NULL);
+ }
+}
+
+void index_copy_cache_fields(struct mail_save_context *ctx,
+ struct mail *src_mail, uint32_t dest_seq)
+{
+ T_BEGIN {
+ struct mailbox_metadata src_metadata, dest_metadata;
+ const struct mailbox_cache_field *field;
+ buffer_t *buf;
+
+ if (mailbox_get_metadata(src_mail->box,
+ MAILBOX_METADATA_CACHE_FIELDS,
+ &src_metadata) < 0)
+ i_unreached();
+ /* the only reason we're doing the destination lookup is to
+ make sure that the cache file is opened and the cache
+ decisions are up to date */
+ if (mailbox_get_metadata(ctx->transaction->box,
+ MAILBOX_METADATA_CACHE_FIELDS,
+ &dest_metadata) < 0)
+ i_unreached();
+
+ buf = t_buffer_create(1024);
+ array_foreach(src_metadata.cache_fields, field) {
+ mail_copy_cache_field(ctx, src_mail, dest_seq,
+ field->name, buf);
+ }
+ index_copy_vsize_extension(ctx, src_mail, dest_seq);
+ } T_END;
+}
+
+int index_storage_set_subscribed(struct mailbox *box, bool set)
+{
+ struct mail_namespace *ns;
+ struct mailbox_list *list = box->list;
+ const char *subs_name;
+ guid_128_t guid;
+
+ if ((list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0)
+ subs_name = box->name;
+ else {
+ /* subscriptions=no namespace, find another one where we can
+ add the subscription to */
+ ns = mail_namespace_find_subscribable(list->ns->user->namespaces,
+ box->vname);
+ if (ns == NULL) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "This namespace has no subscriptions");
+ return -1;
+ }
+ /* use <orig ns prefix><orig storage name> as the
+ subscription name */
+ subs_name = t_strconcat(list->ns->prefix, box->name, NULL);
+ /* drop the common prefix (typically there isn't one) */
+ i_assert(str_begins(subs_name, ns->prefix));
+ subs_name += strlen(ns->prefix);
+
+ list = ns->list;
+ }
+ if (mailbox_list_set_subscribed(list, subs_name, set) < 0) {
+ mail_storage_copy_list_error(box->storage, list);
+ return -1;
+ }
+
+ /* subscriptions are about names, not about mailboxes. it's possible
+ to have a subscription to nonexistent mailbox. renames also don't
+ change subscriptions. so instead of using actual GUIDs, we'll use
+ hash of the name. */
+ mailbox_name_get_sha128(box->vname, guid);
+ mailbox_list_add_change(list, set ? MAILBOX_LOG_RECORD_SUBSCRIBE :
+ MAILBOX_LOG_RECORD_UNSUBSCRIBE, guid);
+ return 0;
+}
+
+void index_storage_destroy(struct mail_storage *storage)
+{
+ if (storage->_shared_attr_dict != NULL) {
+ dict_wait(storage->_shared_attr_dict);
+ dict_deinit(&storage->_shared_attr_dict);
+ }
+ fs_unref(&storage->mailboxes_fs);
+}
+
+static void index_storage_expunging_init(struct mailbox *box)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+
+ if (ibox->vsize_update != NULL)
+ return;
+
+ ibox->vsize_update = index_mailbox_vsize_update_init(box);
+ if (!index_mailbox_vsize_want_updates(ibox->vsize_update) ||
+ !index_mailbox_vsize_update_wait_lock(ibox->vsize_update))
+ index_mailbox_vsize_update_deinit(&ibox->vsize_update);
+}
+
+void index_storage_expunging_deinit(struct mailbox *box)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+
+ if (ibox->vsize_update != NULL)
+ index_mailbox_vsize_update_deinit(&ibox->vsize_update);
+}
+
+static bool index_storage_expunging_want_updates(struct mailbox *box)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ bool ret;
+
+ i_assert(ibox->vsize_update == NULL);
+
+ ibox->vsize_update = index_mailbox_vsize_update_init(box);
+ ret = index_mailbox_vsize_want_updates(ibox->vsize_update);
+ index_mailbox_vsize_update_deinit(&ibox->vsize_update);
+ return ret;
+}
+
+int index_storage_expunged_sync_begin(struct mailbox *box,
+ struct mail_index_sync_ctx **ctx_r,
+ struct mail_index_view **view_r,
+ struct mail_index_transaction **trans_r,
+ enum mail_index_sync_flags flags)
+{
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ int ret;
+
+ /* try to avoid locking vsize updates by checking if we see any
+ expunges */
+ if (mail_index_sync_have_any_expunges(box->index))
+ index_storage_expunging_init(box);
+
+ ret = mail_index_sync_begin(box->index, ctx_r, view_r,
+ trans_r, flags);
+ if (ret <= 0) {
+ if (ret < 0)
+ mailbox_set_index_error(box);
+ index_storage_expunging_deinit(box);
+ return ret;
+ }
+ if (ibox->vsize_update == NULL &&
+ mail_index_sync_has_expunges(*ctx_r) &&
+ index_storage_expunging_want_updates(box)) {
+ /* race condition - need to abort the sync and retry with
+ the vsize locked */
+ mail_index_sync_rollback(ctx_r);
+ index_storage_expunging_deinit(box);
+ return index_storage_expunged_sync_begin(box, ctx_r, view_r,
+ trans_r, flags);
+ }
+ return 1;
+}
+
+int index_storage_save_continue(struct mail_save_context *ctx,
+ struct istream *input,
+ struct mail *cache_dest_mail)
+{
+ struct mail_storage *storage = ctx->transaction->box->storage;
+
+ do {
+ switch (o_stream_send_istream(ctx->data.output, input)) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ /* handle below */
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ if (!mail_storage_set_error_from_errno(storage)) {
+ mail_set_critical(ctx->dest_mail,
+ "save: write(%s) failed: %s",
+ o_stream_get_name(ctx->data.output),
+ o_stream_get_error(ctx->data.output));
+ }
+ return -1;
+ }
+ if (cache_dest_mail != NULL)
+ index_mail_cache_parse_continue(cache_dest_mail);
+
+ /* both tee input readers may consume data from our primary
+ input stream. we'll have to make sure we don't return with
+ one of the streams still having data in them. */
+ } while (i_stream_read(input) > 0);
+
+ if (input->stream_errno != 0) {
+ mail_set_critical(ctx->dest_mail, "save: read(%s) failed: %s",
+ i_stream_get_name(input), i_stream_get_error(input));
+ return -1;
+ }
+ return 0;
+}
+
+void index_storage_save_abort_last(struct mail_save_context *ctx, uint32_t seq)
+{
+ struct index_mail *imail = INDEX_MAIL(ctx->dest_mail);
+
+ /* Close the mail before it's expunged. This allows it to be
+ reset cleanly. */
+ imail->data.no_caching = TRUE;
+ imail->mail.v.close(&imail->mail.mail);
+
+ mail_index_expunge(ctx->transaction->itrans, seq);
+ /* currently we can't just drop pending cache updates for this one
+ specific record, so we'll reset the whole cache transaction. */
+ mail_cache_transaction_reset(ctx->transaction->cache_trans);
+}
+
+int index_mailbox_fix_inconsistent_existence(struct mailbox *box,
+ const char *path)
+{
+ const char *index_path;
+ struct stat st;
+
+ /* Could be a race condition or could be because ITERINDEX is used
+ and the index directory exists, but the storage directory doesn't.
+ Handle the existence inconsistency by creating this directory if
+ the index directory exists (don't bother checking if ITERINDEX is
+ set or not - it doesn't matter since either both dirs should exist
+ or not). */
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX,
+ &index_path) < 0)
+ return -1;
+
+ if (strcmp(index_path, path) == 0) {
+ /* there's no separate index path - mailbox was just deleted */
+ } else if (stat(index_path, &st) == 0) {
+ /* inconsistency - create also the mail directory */
+ return mailbox_mkdir(box, path, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ } else if (errno == ENOENT) {
+ /* race condition - mailbox was just deleted */
+ } else {
+ mailbox_set_critical(box, "stat(%s) failed: %m", index_path);
+ return -1;
+ }
+ mailbox_set_deleted(box);
+ return -1;
+}