summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-index-backend.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-index-backend.c')
-rw-r--r--src/lib-storage/list/mailbox-list-index-backend.c970
1 files changed, 970 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-index-backend.c b/src/lib-storage/list/mailbox-list-index-backend.c
new file mode 100644
index 0000000..e088f3b
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-backend.c
@@ -0,0 +1,970 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "str.h"
+#include "mail-index.h"
+#include "subscription-file.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-index-storage.h"
+#include "mailbox-list-index-sync.h"
+
+#include <stdio.h>
+
+#define GLOBAL_TEMP_PREFIX ".temp."
+#define MAILBOX_LIST_INDEX_DEFAULT_ESCAPE_CHAR '^'
+
+struct index_mailbox_list {
+ struct mailbox_list list;
+ const char *temp_prefix;
+
+ const char *create_mailbox_name;
+ guid_128_t create_mailbox_guid;
+};
+
+extern struct mailbox_list index_mailbox_list;
+
+static int
+index_list_rename_mailbox(struct mailbox_list *_oldlist, const char *oldname,
+ struct mailbox_list *_newlist, const char *newname);
+
+static struct mailbox_list *index_list_alloc(void)
+{
+ struct index_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("index list", 2048);
+
+ list = p_new(pool, struct index_mailbox_list, 1);
+ list->list = index_mailbox_list;
+ list->list.pool = pool;
+ list->list.set.storage_name_escape_char = MAILBOX_LIST_INDEX_DEFAULT_ESCAPE_CHAR;
+
+ list->temp_prefix = p_strconcat(pool, GLOBAL_TEMP_PREFIX,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static int index_list_init(struct mailbox_list *_list, const char **error_r)
+{
+ if (!_list->mail_set->mailbox_list_index) {
+ *error_r = "LAYOUT=index requires mailbox_list_index=yes";
+ return -1;
+ }
+ return 0;
+}
+
+static void index_list_deinit(struct mailbox_list *_list)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+
+ pool_unref(&list->list.pool);
+}
+
+static char index_list_get_hierarchy_sep(struct mailbox_list *list)
+{
+ char sep = list->ns->set->separator[0];
+
+ if (sep == '\0')
+ sep = MAILBOX_LIST_INDEX_HIERARCHY_SEP;
+ if (sep == list->set.storage_name_escape_char) {
+ /* Separator conflicts with the escape character.
+ Use something else. */
+ if (sep != MAILBOX_LIST_INDEX_HIERARCHY_SEP)
+ sep = MAILBOX_LIST_INDEX_HIERARCHY_SEP;
+ else
+ sep = MAILBOX_LIST_INDEX_HIERARCHY_ALT_SEP;
+ }
+ return sep;
+}
+
+static int
+index_list_get_refreshed_node_seq(struct index_mailbox_list *list,
+ struct mail_index_view *view,
+ const char *name,
+ struct mailbox_list_index_node **node_r,
+ uint32_t *seq_r)
+{
+ unsigned int i;
+
+ *node_r = NULL;
+ *seq_r = 0;
+
+ for (i = 0; i < 2; i++) {
+ *node_r = mailbox_list_index_lookup(&list->list, name);
+ if (*node_r == NULL)
+ return 0;
+ if (mail_index_lookup_seq(view, (*node_r)->uid, seq_r))
+ return 1;
+ /* mailbox was just expunged. refreshing should notice it. */
+ if (mailbox_list_index_refresh_force(&list->list) < 0)
+ return -1;
+ }
+ i_panic("mailbox list index: refreshing doesn't lose expunged uid=%u",
+ (*node_r)->uid);
+ return -1;
+}
+
+static const char *
+index_get_guid_path(struct mailbox_list *_list, const char *root_dir,
+ const guid_128_t mailbox_guid)
+{
+ return t_strdup_printf("%s/%s%s", root_dir,
+ _list->set.mailbox_dir_name,
+ guid_128_to_string(mailbox_guid));
+}
+
+static int
+index_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ struct mail_index_view *view;
+ struct mailbox_list_index_node *node;
+ struct mailbox_status status;
+ guid_128_t mailbox_guid;
+ const char *root_dir, *reason;
+ uint32_t seq;
+ int ret;
+
+ if (name == NULL) {
+ /* return root directories */
+ return mailbox_list_set_get_root_path(&_list->set, type,
+ path_r) ? 1 : 0;
+ }
+ /* consistently use mailbox_dir_name as part of all mailbox
+ directories (index/control/etc) */
+ switch (type) {
+ case MAILBOX_LIST_PATH_TYPE_MAILBOX:
+ type = MAILBOX_LIST_PATH_TYPE_DIR;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
+ type = MAILBOX_LIST_PATH_TYPE_ALT_DIR;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_LIST_INDEX:
+ i_unreached();
+ default:
+ break;
+ }
+ if (!mailbox_list_set_get_root_path(&_list->set, type, &root_dir))
+ return 0;
+
+ if (list->create_mailbox_name != NULL &&
+ strcmp(list->create_mailbox_name, name) == 0) {
+ *path_r = index_get_guid_path(_list, root_dir,
+ list->create_mailbox_guid);
+ return 1;
+ }
+
+ /* ilist is only required from this point onwards.
+ At least imapc calls index_list_get_path without this context*/
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_list);
+
+ if (ilist->sync_ctx != NULL) {
+ /* we could get here during sync from
+ index_list_mailbox_create_selectable() */
+ view = ilist->sync_ctx->view;
+ node = mailbox_list_index_lookup(&list->list, name);
+ if (node == NULL) {
+ seq = 0;
+ ret = 0;
+ } else if (mail_index_lookup_seq(view, node->uid, &seq)) {
+ ret = 1;
+ } else {
+ i_panic("mailbox list index: lost uid=%u", node->uid);
+ }
+ } else {
+ if (mailbox_list_index_refresh(&list->list) < 0)
+ return -1;
+ view = mail_index_view_open(ilist->index);
+ ret = index_list_get_refreshed_node_seq(list, view, name, &node, &seq);
+ if (ret < 0) {
+ mail_index_view_close(&view);
+ return -1;
+ }
+ }
+ i_assert(ret == 0 || seq != 0);
+ if (ret == 0) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+ ret = -1;
+ } else if (!mailbox_list_index_status(_list, view, seq, 0,
+ &status, mailbox_guid,
+ NULL, &reason) ||
+ guid_128_is_empty(mailbox_guid)) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+ ret = -1;
+ } else {
+ *path_r = index_get_guid_path(_list, root_dir, mailbox_guid);
+ ret = 1;
+ }
+ if (ilist->sync_ctx == NULL)
+ mail_index_view_close(&view);
+ return ret;
+}
+
+static const char *
+index_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+
+ return global ? GLOBAL_TEMP_PREFIX : list->temp_prefix;
+}
+
+static int index_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ const char *path;
+
+ if (_list->set.subscription_fname == NULL) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Subscriptions not supported");
+ return -1;
+ }
+
+ path = t_strconcat(_list->set.control_dir != NULL ?
+ _list->set.control_dir : _list->set.root_dir,
+ "/", _list->set.subscription_fname, NULL);
+ return subsfile_set_subscribed(_list, path, list->temp_prefix,
+ name, set);
+}
+
+static int
+index_list_node_exists(struct index_mailbox_list *list, const char *name,
+ enum mailbox_existence *existence_r)
+{
+ struct mailbox_list_index_node *node;
+
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+
+ if (mailbox_list_index_refresh(&list->list) < 0)
+ return -1;
+
+ node = mailbox_list_index_lookup(&list->list, name);
+ if (node == NULL)
+ return 0;
+
+ if ((node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
+ /* selectable */
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ } else {
+ /* non-selectable */
+ *existence_r = MAILBOX_EXISTENCE_NOSELECT;
+ }
+ return 0;
+}
+
+static int
+index_list_mailbox_create_dir(struct index_mailbox_list *list, const char *name)
+{
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mailbox_list_index_node *node;
+ uint32_t seq;
+ bool created;
+ int ret;
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+
+ seq = mailbox_list_index_sync_name(sync_ctx, name, &node, &created);
+ if (created || (node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0) {
+ /* didn't already exist */
+ node->flags = MAILBOX_LIST_INDEX_FLAG_NOSELECT;
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)node->flags);
+ ret = 1;
+ } else {
+ /* already existed */
+ ret = 0;
+ }
+ if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int
+index_list_mailbox_create_selectable(struct mailbox *box,
+ const guid_128_t mailbox_guid)
+{
+ struct index_mailbox_list *list =
+ (struct index_mailbox_list *)box->list;
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mailbox_list_index_record rec;
+ struct mailbox_list_index_node *node;
+ const void *data;
+ bool expunged, created;
+ uint32_t seq;
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+
+ seq = mailbox_list_index_sync_name(sync_ctx, box->name, &node, &created);
+ if (box->corrupted_mailbox_name) {
+ /* an existing mailbox is being created with a "unknown" name.
+ opening the mailbox will hopefully find its real name and
+ rename it. */
+ node->flags |= MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME;
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_ADD,
+ (enum mail_flags)MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ }
+ if (!created &&
+ (node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
+ /* already selectable */
+ (void)mailbox_list_index_sync_end(&sync_ctx, TRUE);
+ return 0;
+ }
+
+ mail_index_lookup_ext(sync_ctx->view, seq, ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&rec, data, sizeof(rec));
+ i_assert(guid_128_is_empty(rec.guid));
+
+ /* make it selectable */
+ node->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT |
+ MAILBOX_LIST_INDEX_FLAG_NOINFERIORS);
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)node->flags);
+
+ /* set UIDVALIDITY if was set by the storage */
+ if (box->index != NULL) {
+ struct mail_index_view *view;
+
+ view = mail_index_view_open(box->index);
+ if (mail_index_get_header(view)->uid_validity != 0)
+ rec.uid_validity = mail_index_get_header(view)->uid_validity;
+ mail_index_view_close(&view);
+ }
+
+ /* set GUID */
+ memcpy(rec.guid, mailbox_guid, sizeof(rec.guid));
+ mail_index_update_ext(sync_ctx->trans, seq, ilist->ext_id, &rec, NULL);
+
+ if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0) {
+ /* make sure we forget any changes done internally */
+ mailbox_list_index_reset(ilist);
+ return -1;
+ }
+ return 1;
+}
+
+static int
+index_list_mailbox_create(struct mailbox *box,
+ const struct mailbox_update *update, bool directory)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ struct index_mailbox_list *list =
+ (struct index_mailbox_list *)box->list;
+ struct mailbox_update new_update;
+ enum mailbox_existence existence;
+ int ret;
+
+ /* first do a quick check that it doesn't exist */
+ if (index_list_node_exists(list, box->name, &existence) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ if (existence == MAILBOX_EXISTENCE_NONE && directory) {
+ /* now add the directory to index locked */
+ if ((ret = index_list_mailbox_create_dir(list, box->name)) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ } else if (existence != MAILBOX_EXISTENCE_SELECT && !directory) {
+ /* if no GUID is requested, generate it ourself. set
+ UIDVALIDITY to index sometimes later. */
+ if (update == NULL)
+ i_zero(&new_update);
+ else
+ new_update = *update;
+ if (guid_128_is_empty(new_update.mailbox_guid))
+ guid_128_generate(new_update.mailbox_guid);
+
+ /* create the backend mailbox first before it exists in the
+ list. the mailbox creation wants to use get_path() though,
+ so use a bit kludgy create_mailbox_* variables during the
+ creation to return the path. we'll also support recursively
+ creating more mailboxes in here. */
+ const char *old_name;
+ guid_128_t old_guid;
+
+ old_name = list->create_mailbox_name;
+ guid_128_copy(old_guid, list->create_mailbox_guid);
+
+ list->create_mailbox_name = box->name;
+ guid_128_copy(list->create_mailbox_guid, new_update.mailbox_guid);
+
+ ret = ibox->module_ctx.super.create_box(box, &new_update, FALSE);
+
+ if (ret == 0) {
+ /* backend mailbox was successfully created. now add it
+ to the list. */
+ ret = index_list_mailbox_create_selectable(box, new_update.mailbox_guid);
+ if (ret < 0)
+ mail_storage_copy_list_error(box->storage, box->list);
+ if (ret <= 0) {
+ /* failed to add to list. rollback the backend
+ mailbox creation */
+ bool create_error = ret < 0;
+
+ if (create_error)
+ mail_storage_last_error_push(box->storage);
+ if (mailbox_delete(box) < 0)
+ ret = -1;
+ if (create_error)
+ mail_storage_last_error_pop(box->storage);
+ }
+ }
+ list->create_mailbox_name = old_name;
+ guid_128_copy(list->create_mailbox_guid, old_guid);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "Mailbox already exists");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+index_list_mailbox_update(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ const char *root_dir, *old_path, *new_path;
+
+ if (mailbox_list_get_path(box->list, box->name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &old_path) <= 0)
+ old_path = NULL;
+
+ if (ibox->module_ctx.super.update_box(box, update) < 0)
+ return -1;
+
+ /* rename the directory */
+ if (!guid_128_is_empty(update->mailbox_guid) && old_path != NULL &&
+ mailbox_list_set_get_root_path(&box->list->set,
+ MAILBOX_LIST_PATH_TYPE_DIR,
+ &root_dir)) {
+ new_path = index_get_guid_path(box->list, root_dir,
+ update->mailbox_guid);
+ if (strcmp(old_path, new_path) == 0)
+ ;
+ else if (rename(old_path, new_path) == 0)
+ ;
+ else if (errno == ENOENT) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
+ return -1;
+ } else {
+ mailbox_set_critical(box, "rename(%s, %s) failed: %m",
+ old_path, new_path);
+ return -1;
+ }
+ }
+
+ mailbox_list_index_update_mailbox_index(box, update);
+ return 0;
+}
+
+static int
+index_list_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;
+ }
+
+ struct index_mailbox_list *list =
+ (struct index_mailbox_list *)box->list;
+
+ if (index_list_node_exists(list, box->name, existence_r) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ return 0;
+}
+
+static bool mailbox_has_corrupted_name(struct mailbox *box)
+{
+ struct mailbox_list_index_node *node;
+
+ if (box->corrupted_mailbox_name)
+ return TRUE;
+
+ node = mailbox_list_index_lookup(box->list, box->name);
+ return node != NULL &&
+ (node->flags & MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME) != 0;
+}
+
+static void index_list_rename_corrupted(struct mailbox *box, const char *newname)
+{
+ if (index_list_rename_mailbox(box->list, box->name,
+ box->list, newname) == 0 ||
+ box->list->error != MAIL_ERROR_EXISTS)
+ return;
+
+ /* mailbox already exists. don't give up yet, just use the newname
+ as prefix and add the "lost-xx" as suffix. */
+ char sep = mailbox_list_get_hierarchy_sep(box->list);
+ const char *oldname = box->name;
+
+ /* oldname should be at the root level, but check for hierarchies
+ anyway to be safe. */
+ const char *p = strrchr(oldname, sep);
+ if (p != NULL)
+ oldname = p+1;
+
+ newname = t_strdup_printf("%s-%s", newname, oldname);
+ (void)index_list_rename_mailbox(box->list, box->name,
+ box->list, newname);
+}
+
+static int index_list_mailbox_open(struct mailbox *box)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ const void *data;
+ const unsigned char *name_hdr;
+ size_t name_hdr_size;
+
+ if (ibox->module_ctx.super.open(box) < 0)
+ return -1;
+
+ if (box->view == NULL) {
+ /* FIXME: dsync-merge is performing a delete in obox - remove
+ this check once dsync-merging is no longer used. */
+ return 0;
+ }
+
+ /* if mailbox name has changed, update it to the header. Use \0
+ as the hierarchy separator in the header. This is to make sure
+ we don't keep rewriting the name just in case some backend switches
+ between separators when accessed different ways. */
+
+ /* Get the current mailbox name with \0 separators and unesacped. */
+ size_t box_name_len;
+ const unsigned char *box_zerosep_name =
+ mailbox_name_hdr_encode(box->list, box->name, &box_name_len);
+
+ /* Does it match what's in the header now? */
+ mail_index_get_header_ext(box->view, box->box_name_hdr_ext_id,
+ &data, &name_hdr_size);
+ name_hdr = data;
+ while (name_hdr_size > 0 && name_hdr[name_hdr_size-1] == '\0') {
+ /* Remove trailing \0 - header doesn't shrink always */
+ name_hdr_size--;
+ }
+ if (name_hdr_size == box_name_len &&
+ memcmp(box_zerosep_name, name_hdr, box_name_len) == 0) {
+ /* Same mailbox name */
+ } else if (!mailbox_has_corrupted_name(box)) {
+ /* Mailbox name changed - update */
+ struct mail_index_transaction *trans =
+ mail_index_transaction_begin(box->view, 0);
+ mail_index_ext_resize_hdr(trans, box->box_name_hdr_ext_id,
+ box_name_len);
+ mail_index_update_header_ext(trans, box->box_name_hdr_ext_id, 0,
+ box_zerosep_name, box_name_len);
+ (void)mail_index_transaction_commit(&trans);
+ } else if (name_hdr_size > 0) {
+ /* Mailbox name is corrupted. Rename it to the previous name. */
+ const char *newname =
+ mailbox_name_hdr_decode_storage_name(
+ box->list, name_hdr, name_hdr_size);
+ index_list_rename_corrupted(box, newname);
+ }
+ return 0;
+}
+
+void mailbox_list_index_backend_sync_init(struct mailbox *box,
+ enum mailbox_sync_flags flags)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+
+ if ((flags & MAILBOX_SYNC_FLAG_FORCE_RESYNC) != 0 &&
+ !ilist->force_resynced) {
+ enum mail_storage_list_index_rebuild_reason reason =
+ MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_FORCE_RESYNC;
+
+ if (box->storage->v.list_index_rebuild != NULL &&
+ box->storage->v.list_index_rebuild(box->storage, reason) < 0)
+ ilist->force_resync_failed = TRUE;
+ /* try to rebuild list index only once - even if it failed */
+ ilist->force_resynced = TRUE;
+ }
+}
+
+int mailbox_list_index_backend_sync_deinit(struct mailbox *box)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+
+ if (ilist->force_resync_failed) {
+ /* fail this only once */
+ ilist->force_resync_failed = FALSE;
+ return -1;
+ }
+ return 0;
+}
+
+static void
+index_list_try_delete(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type)
+{
+ const char *mailbox_path, *path, *error;
+
+ if (mailbox_list_get_path(_list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &mailbox_path) <= 0 ||
+ mailbox_list_get_path(_list, name, type, &path) <= 0 ||
+ strcmp(path, mailbox_path) == 0)
+ return;
+
+ if (*_list->set.maildir_name == '\0' &&
+ (_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ /* this directory may contain also child mailboxes' data.
+ we don't want to delete that. */
+ bool rmdir_path = *_list->set.maildir_name != '\0';
+ if (mailbox_list_delete_mailbox_nonrecursive(_list, name, path,
+ rmdir_path) < 0)
+ return;
+ } else {
+ if (mailbox_list_delete_trash(path, &error) < 0 &&
+ errno != ENOTEMPTY) {
+ mailbox_list_set_critical(_list,
+ "unlink_directory(%s) failed: %s", path, error);
+ }
+ }
+
+ /* avoid leaving empty directories lying around */
+ mailbox_list_delete_until_root(_list, path, type);
+}
+
+static void
+index_list_delete_finish(struct mailbox_list *list, const char *name)
+{
+ index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_INDEX);
+ index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_CONTROL);
+ index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX);
+}
+
+static int
+index_list_delete_entry(struct index_mailbox_list *list, const char *name,
+ bool delete_selectable)
+{
+ struct mailbox_list_index_sync_context *sync_ctx;
+ int ret;
+
+ if (list->create_mailbox_name != NULL &&
+ strcmp(name, list->create_mailbox_name) == 0) {
+ /* we're rolling back a failed create. if the name exists in the
+ list, it was done by somebody else so we don't want to
+ remove it. */
+ return 0;
+ }
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+ ret = mailbox_list_index_sync_delete(sync_ctx, name, delete_selectable);
+ if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0)
+ return -1;
+ return ret;
+}
+
+static int
+index_list_try_delete_nonexistent_parent(struct mailbox_list *_list,
+ const char *name)
+{
+ struct index_mailbox_list *list =
+ container_of(_list, struct index_mailbox_list, list);
+ struct mailbox_list_index_node *node;
+ string_t *full_name;
+ const char *p;
+ char sep = mailbox_list_get_hierarchy_sep(_list);
+
+ if ((p = strrchr(name, sep)) == NULL) {
+ /* No occurrences of the hierarchy separator could be found
+ in the name, so the mailbox has no parents. */
+ return 0;
+ }
+
+ /* Lookup parent node of of given "name" */
+ node = mailbox_list_index_lookup(_list, t_strdup_until(name, p));
+ full_name = t_str_new(32);
+
+ while (node != NULL) {
+ /* Attempt to delete all parent nodes that are NOSELECT or
+ NONEXISTENT */
+ if (node->children != NULL)
+ break;
+ if ((node->flags & MAILBOX_LIST_INDEX_FLAG_NOSELECT) != 0 ||
+ (node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0) {
+ /* The parent mailbox has no other children and is not
+ existant or not selectable, delete it */
+ str_truncate(full_name, 0);
+ mailbox_list_index_node_get_path(node, sep, full_name);
+ if (index_list_delete_entry(list, str_c(full_name), FALSE) < 0)
+ return -1;
+
+ if ((p = strrchr(str_c(full_name), sep)) == NULL) {
+ /* No occurrences of the hierarchy separator
+ could be found in the mailbox that was
+ just deleted. */
+ node = NULL;
+ } else {
+ /* lookup parent node of the node just deleted */
+ str_truncate(full_name, p - str_c(full_name));
+ node = mailbox_list_index_lookup(_list, str_c(full_name));
+ }
+ } else
+ break;
+
+ }
+ return 0;
+}
+
+static int
+index_list_delete_mailbox(struct mailbox_list *_list, const char *name)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ const char *path;
+ int ret;
+
+ /* first delete the mailbox files */
+ ret = mailbox_list_get_path(_list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path);
+ if (ret <= 0)
+ return ret;
+
+ if ((_list->flags & (MAILBOX_LIST_FLAG_NO_MAIL_FILES |
+ MAILBOX_LIST_FLAG_NO_DELETES)) != 0) {
+ ret = 0;
+ } else if ((_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ ret = mailbox_list_delete_mailbox_file(_list, name, path);
+ } else {
+ ret = mailbox_list_delete_mailbox_nonrecursive(_list, name,
+ path, TRUE);
+ }
+
+ if ((ret == 0 || (_list->props & MAILBOX_LIST_PROP_AUTOCREATE_DIRS) != 0) &&
+ (_list->flags & MAILBOX_LIST_FLAG_NO_DELETES) == 0)
+ index_list_delete_finish(_list, name);
+ if (ret == 0) {
+ if (index_list_delete_entry(list, name, TRUE) < 0)
+ return -1;
+ }
+ if (_list->set.no_noselect && ret == 0)
+ (void)index_list_try_delete_nonexistent_parent(_list, name);
+
+ return ret;
+}
+
+static int
+index_list_delete_dir(struct mailbox_list *_list, const char *name)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ int ret;
+
+ if ((ret = index_list_delete_entry(list, name, FALSE)) < 0)
+ return -1;
+ if (ret == 0) {
+ mailbox_list_set_error(_list, MAIL_ERROR_EXISTS,
+ "Mailbox has children, delete them first");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+index_list_delete_symlink(struct mailbox_list *_list,
+ const char *name ATTR_UNUSED)
+{
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Symlinks not supported");
+ return -1;
+}
+
+static int
+index_list_rename_mailbox(struct mailbox_list *_oldlist, const char *oldname,
+ struct mailbox_list *_newlist, const char *newname)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_oldlist;
+ const size_t oldname_len = strlen(oldname);
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mailbox_list_index_record oldrec, newrec;
+ struct mailbox_list_index_node *oldnode, *newnode, *child;
+ const void *data;
+ bool created, expunged;
+ uint32_t oldseq, newseq;
+ int ret;
+
+ if (_oldlist != _newlist) {
+ mailbox_list_set_error(_oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Renaming not supported across namespaces.");
+ return -1;
+ }
+
+ if (str_begins(newname, oldname) &&
+ newname[oldname_len] == mailbox_list_get_hierarchy_sep(_newlist)) {
+ mailbox_list_set_error(_oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailbox under itself.");
+ return -1;
+ }
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+
+ oldnode = mailbox_list_index_lookup(&list->list, oldname);
+ if (oldnode == NULL) {
+ (void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
+ mailbox_list_set_error(&list->list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname));
+ return -1;
+ }
+ if (!mail_index_lookup_seq(sync_ctx->view, oldnode->uid, &oldseq))
+ i_panic("mailbox list index: lost uid=%u", oldnode->uid);
+
+ newseq = mailbox_list_index_sync_name(sync_ctx, newname,
+ &newnode, &created);
+ if (!created) {
+ (void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
+ mailbox_list_set_error(&list->list, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ return -1;
+ }
+ i_assert(oldnode != newnode);
+
+ /* copy all the data from old node to new node */
+ newnode->uid = oldnode->uid;
+ newnode->flags = oldnode->flags;
+ newnode->children = oldnode->children; oldnode->children = NULL;
+ for (child = newnode->children; child != NULL; child = child->next)
+ child->parent = newnode;
+
+ /* remove the old node from existence */
+ mailbox_list_index_node_unlink(sync_ctx->ilist, oldnode);
+
+ /* update the old index record to contain the new name_id/parent_uid,
+ then expunge the added index record */
+ mail_index_lookup_ext(sync_ctx->view, oldseq, sync_ctx->ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&oldrec, data, sizeof(oldrec));
+
+ mail_index_lookup_ext(sync_ctx->view, newseq, sync_ctx->ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&newrec, data, sizeof(newrec));
+
+ oldrec.name_id = newrec.name_id;
+ oldrec.parent_uid = newrec.parent_uid;
+
+ if ((newnode->flags & MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME) != 0) {
+ /* mailbox is renamed - clear away the corruption flag so the
+ new name will be written to the mailbox index header. */
+ newnode->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ mail_index_update_flags(sync_ctx->trans, oldseq, MODIFY_REMOVE,
+ (enum mail_flags)MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ }
+ mail_index_update_ext(sync_ctx->trans, oldseq,
+ sync_ctx->ilist->ext_id, &oldrec, NULL);
+ mail_index_expunge(sync_ctx->trans, newseq);
+
+ ret = mailbox_list_index_sync_end(&sync_ctx, TRUE);
+
+ if (_oldlist->set.no_noselect && ret == 0)
+ (void)index_list_try_delete_nonexistent_parent(_oldlist, oldname);
+
+ return ret;
+}
+
+static struct mailbox_list_iterate_context *
+index_list_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct mailbox_list_iterate_context *ctx;
+ pool_t pool;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ return mailbox_list_subscriptions_iter_init(list, patterns,
+ flags);
+ }
+
+ pool = pool_alloconly_create("mailbox list index backend iter", 1024);
+ ctx = p_new(pool, struct mailbox_list_iterate_context, 1);
+ ctx->pool = pool;
+ ctx->list = list;
+ ctx->flags = flags;
+ array_create(&ctx->module_contexts, pool, sizeof(void *), 5);
+ return ctx;
+}
+
+static const struct mailbox_info *
+index_list_iter_next(struct mailbox_list_iterate_context *ctx)
+{
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_next(ctx);
+ return NULL;
+}
+
+static int index_list_iter_deinit(struct mailbox_list_iterate_context *ctx)
+{
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_deinit(ctx);
+ pool_unref(&ctx->pool);
+ return 0;
+}
+
+struct mailbox_list index_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_INDEX,
+ .props = MAILBOX_LIST_PROP_NO_ROOT | MAILBOX_LIST_PROP_NO_INTERNAL_NAMES,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = index_list_alloc,
+ .init = index_list_init,
+ .deinit = index_list_deinit,
+ .get_hierarchy_sep = index_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = index_list_get_path,
+ .get_temp_prefix = index_list_get_temp_prefix,
+ .iter_init = index_list_iter_init,
+ .iter_next = index_list_iter_next,
+ .iter_deinit = index_list_iter_deinit,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = index_list_set_subscribed,
+ .delete_mailbox = index_list_delete_mailbox,
+ .delete_dir = index_list_delete_dir,
+ .delete_symlink = index_list_delete_symlink,
+ .rename_mailbox = index_list_rename_mailbox,
+ }
+};
+
+bool mailbox_list_index_backend_init_mailbox(struct mailbox *box,
+ struct mailbox_vfuncs *v)
+{
+ if (strcmp(box->list->name, MAILBOX_LIST_NAME_INDEX) != 0)
+ return TRUE;
+
+ /* NOTE: this is using the same v as
+ mailbox_list_index_status_init_mailbox(), so don't have them
+ accidentally override each others. */
+ v->create_box = index_list_mailbox_create;
+ v->update_box = index_list_mailbox_update;
+ v->exists = index_list_mailbox_exists;
+ v->open = index_list_mailbox_open;
+ return FALSE;
+}