summaryrefslogtreecommitdiffstats
path: root/src/plugins/virtual/virtual-storage.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/virtual/virtual-storage.c')
-rw-r--r--src/plugins/virtual/virtual-storage.c953
1 files changed, 953 insertions, 0 deletions
diff --git a/src/plugins/virtual/virtual-storage.c b/src/plugins/virtual/virtual-storage.c
new file mode 100644
index 0000000..6d7dd98
--- /dev/null
+++ b/src/plugins/virtual/virtual-storage.c
@@ -0,0 +1,953 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "str.h"
+#include "llist.h"
+#include "mkdir-parents.h"
+#include "unlink-directory.h"
+#include "index-mail.h"
+#include "mail-copy.h"
+#include "mail-search.h"
+#include "mailbox-list-private.h"
+#include "virtual-plugin.h"
+#include "virtual-transaction.h"
+#include "virtual-storage.h"
+#include "mailbox-list-notify.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES 64
+
+#define VIRTUAL_BACKEND_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, virtual_backend_storage_module)
+
+struct virtual_backend_mailbox {
+ union mailbox_module_context module_ctx;
+};
+
+extern struct mail_storage virtual_storage;
+extern struct mailbox virtual_mailbox;
+extern struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs;
+
+struct virtual_storage_module virtual_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(virtual_backend_storage_module,
+ &mail_storage_module_register);
+
+static bool ns_is_visible(struct mail_namespace *ns)
+{
+ return (ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 ||
+ (ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 ||
+ (ns->flags & NAMESPACE_FLAG_HIDDEN) == 0;
+}
+
+static const char *get_user_visible_mailbox_name(struct mailbox *box)
+{
+ if (ns_is_visible(box->list->ns))
+ return box->vname;
+ else {
+ return t_strdup_printf("<hidden>%c%s",
+ mail_namespace_get_sep(box->list->ns),
+ box->vname);
+ }
+}
+
+void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src)
+{
+ const char *name, *str;
+ enum mail_error error;
+
+ name = get_user_visible_mailbox_name(src);
+ str = mailbox_get_last_error(src, &error);
+
+ str = t_strdup_printf("%s (for backend mailbox %s)", str, name);
+ mail_storage_set_error(dest->storage, error, str);
+}
+
+static struct mail_storage *virtual_storage_alloc(void)
+{
+ struct virtual_storage *storage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("virtual storage", 1024);
+ storage = p_new(pool, struct virtual_storage, 1);
+ storage->storage = virtual_storage;
+ storage->storage.pool = pool;
+ p_array_init(&storage->open_stack, pool, 8);
+ return &storage->storage;
+}
+
+static int
+virtual_storage_create(struct mail_storage *_storage,
+ struct mail_namespace *ns ATTR_UNUSED,
+ const char **error_r)
+{
+ struct virtual_storage *storage = (struct virtual_storage *)_storage;
+ const char *value;
+
+ value = mail_user_plugin_getenv(_storage->user, "virtual_max_open_mailboxes");
+ if (value == NULL)
+ storage->max_open_mailboxes = VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES;
+ else if (str_to_uint(value, &storage->max_open_mailboxes) < 0) {
+ *error_r = "Invalid virtual_max_open_mailboxes setting";
+ return -1;
+ }
+ return 0;
+}
+
+static void
+virtual_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
+ struct mailbox_list_settings *set)
+{
+ if (set->layout == NULL)
+ set->layout = MAILBOX_LIST_NAME_FS;
+ if (set->subscription_fname == NULL)
+ set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME;
+}
+
+struct virtual_backend_box *
+virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(bboxes[i]->name, name) == 0)
+ return bboxes[i];
+ }
+ return NULL;
+}
+
+bool virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id,
+ struct virtual_backend_box **bbox_r)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ *bbox_r = NULL;
+ if (mailbox_id == 0)
+ return FALSE;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->mailbox_id == mailbox_id) {
+ *bbox_r = bboxes[i];
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool virtual_mailbox_is_in_open_stack(struct virtual_storage *storage,
+ const char *name)
+{
+ const char *const *names;
+ unsigned int i, count;
+
+ names = array_get(&storage->open_stack, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(names[i], name) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int virtual_backend_box_open_failed(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ enum mail_error error;
+ const char *str;
+
+ str = t_strdup_printf(
+ "Virtual mailbox open failed because of mailbox %s: %s",
+ get_user_visible_mailbox_name(bbox->box),
+ mailbox_get_last_error(bbox->box, &error));
+ mail_storage_set_error(mbox->box.storage, error, str);
+ mailbox_free(&bbox->box);
+
+ if (error == MAIL_ERROR_PERM && bbox->wildcard) {
+ /* this mailbox wasn't explicitly specified. just skip it. */
+ return 0;
+ }
+ return -1;
+}
+
+static int virtual_backend_box_alloc(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox,
+ enum mailbox_flags flags)
+{
+ struct mail_user *user = mbox->storage->storage.user;
+ struct mail_namespace *ns;
+ const char *mailbox;
+ enum mailbox_existence existence;
+
+ i_assert(bbox->box == NULL);
+
+ if (!bbox->clear_recent)
+ flags &= ENUM_NEGATE(MAILBOX_FLAG_DROP_RECENT);
+
+ mailbox = bbox->name;
+ ns = mail_namespace_find(user->namespaces, mailbox);
+ bbox->box = mailbox_alloc(ns->list, mailbox, flags);
+ MODULE_CONTEXT_SET(bbox->box, virtual_storage_module, bbox);
+
+ if (bbox == mbox->save_bbox) {
+ /* Assume that the save_bbox exists, whether or not it truly
+ does. This at least gives a better error message than crash
+ later on. */
+ existence = MAILBOX_EXISTENCE_SELECT;
+ } else {
+ if (mailbox_exists(bbox->box, TRUE, &existence) < 0)
+ return virtual_backend_box_open_failed(mbox, bbox);
+ }
+ if (existence != MAILBOX_EXISTENCE_SELECT) {
+ /* ignore this. it could be intentional. */
+ e_debug(mbox->box.event,
+ "Skipping non-existing mailbox %s",
+ bbox->box->vname);
+ mailbox_free(&bbox->box);
+ return 0;
+ }
+
+ i_array_init(&bbox->uids, 64);
+ i_array_init(&bbox->sync_pending_removes, 64);
+ /* we use modseqs for being able to check quickly if backend mailboxes
+ have changed. make sure the backend has them enabled. */
+ (void)mailbox_enable(bbox->box, MAILBOX_FEATURE_CONDSTORE);
+ return 1;
+}
+
+static int virtual_mailboxes_open(struct virtual_mailbox *mbox,
+ enum mailbox_flags flags)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+ int ret;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; ) {
+ ret = virtual_backend_box_alloc(mbox, bboxes[i], flags);
+ if (ret <= 0) {
+ if (ret < 0)
+ break;
+ array_delete(&mbox->backend_boxes, i, 1);
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ } else {
+ i++;
+ }
+ }
+ if (i == count)
+ return 0;
+ else {
+ /* failed */
+ for (; i > 0; i--) {
+ mailbox_free(&bboxes[i-1]->box);
+ array_free(&bboxes[i-1]->uids);
+ }
+ return -1;
+ }
+}
+
+static struct mailbox *
+virtual_mailbox_alloc(struct mail_storage *_storage, struct mailbox_list *list,
+ const char *vname, enum mailbox_flags flags)
+{
+ struct virtual_storage *storage = (struct virtual_storage *)_storage;
+ struct virtual_mailbox *mbox;
+ pool_t pool;
+
+ pool = pool_alloconly_create("virtual mailbox", 2048);
+ mbox = p_new(pool, struct virtual_mailbox, 1);
+ mbox->box = virtual_mailbox;
+ mbox->box.pool = pool;
+ mbox->box.storage = _storage;
+ mbox->box.list = list;
+ mbox->box.mail_vfuncs = &virtual_mail_vfuncs;
+ mbox->box.virtual_vfuncs = &virtual_mailbox_vfuncs;
+
+ index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX);
+
+ mbox->storage = storage;
+ mbox->virtual_ext_id = (uint32_t)-1;
+ mbox->virtual_ext2_id = (uint32_t)-1;
+ mbox->virtual_guid_ext_id = (uint32_t)-1;
+ return &mbox->box;
+}
+
+void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox)
+{
+ struct mailbox_transaction_context *trans;
+
+ if (bbox->sync_mail != NULL) {
+ trans = bbox->sync_mail->transaction;
+ mail_free(&bbox->sync_mail);
+ (void)mailbox_transaction_commit(&trans);
+ }
+}
+
+static bool virtual_backend_box_can_close(struct virtual_backend_box *bbox)
+{
+ if (bbox->box->notify_callback != NULL) {
+ /* we can close it if notify is set
+ because we have no need to keep it open
+ for tracking changes */
+ return bbox->notify != NULL;
+ }
+ if (array_count(&bbox->sync_pending_removes) > 0) {
+ /* FIXME: we could probably close this by making
+ syncing support it? */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+virtual_backend_box_close_any_except(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *except_bbox)
+{
+ struct virtual_backend_box *bbox;
+
+ /* first try to close a mailbox without any transactions.
+ we'll also skip any mailbox that has notifications enabled (ideally
+ these would be handled by mailbox list index) */
+ for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) {
+ i_assert(bbox->box->opened);
+
+ if (bbox != except_bbox &&
+ bbox->box->transaction_count == 0 &&
+ virtual_backend_box_can_close(bbox)) {
+ i_assert(bbox->sync_mail == NULL);
+ virtual_backend_box_close(mbox, bbox);
+ return TRUE;
+ }
+ }
+
+ /* next try to close a mailbox that has sync_mail, but no
+ other transactions */
+ for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) {
+ if (bbox != except_bbox &&
+ bbox->sync_mail != NULL &&
+ bbox->box->transaction_count == 1 &&
+ virtual_backend_box_can_close(bbox)) {
+ virtual_backend_box_sync_mail_unset(bbox);
+ i_assert(bbox->box->transaction_count == 0);
+ virtual_backend_box_close(mbox, bbox);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void virtual_backend_mailbox_close(struct mailbox *box)
+{
+ struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box);
+ struct virtual_backend_mailbox *vbox = VIRTUAL_BACKEND_CONTEXT(box);
+
+ if (bbox != NULL && bbox->open_tracked) {
+ /* we could have gotten here from e.g. mailbox_autocreate()
+ without going through virtual_mailbox_close() */
+ virtual_backend_box_close(bbox->virtual_mbox, bbox);
+ }
+ vbox->module_ctx.super.close(box);
+}
+
+void virtual_backend_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct virtual_backend_mailbox *vbox;
+
+ vbox = p_new(box->pool, struct virtual_backend_mailbox, 1);
+ vbox->module_ctx.super = *v;
+ box->vlast = &vbox->module_ctx.super;
+ v->close = virtual_backend_mailbox_close;
+ MODULE_CONTEXT_SET(box, virtual_backend_storage_module, vbox);
+}
+
+void virtual_backend_mailbox_opened(struct mailbox *box)
+{
+ struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box);
+ struct virtual_mailbox *mbox;
+
+ if (bbox == NULL) {
+ /* not a backend for a virtual mailbox */
+ return;
+ }
+ i_assert(!bbox->open_tracked);
+ mbox = bbox->virtual_mbox;
+
+ /* the backend mailbox was already opened. if we didn't get here
+ from virtual_backend_box_open() we may need to close a mailbox */
+ while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes &&
+ virtual_backend_box_close_any_except(mbox, bbox))
+ ;
+
+ bbox->open_tracked = TRUE;
+ mbox->backends_open_count++;
+ DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+}
+
+int virtual_backend_box_open(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ i_assert(!bbox->box->opened);
+
+ /* try to keep the number of open mailboxes below the threshold
+ before opening the mailbox */
+ while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes &&
+ virtual_backend_box_close_any_except(mbox, bbox))
+ ;
+
+ return mailbox_open(bbox->box);
+}
+
+void virtual_backend_box_close(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ i_assert(bbox->box->opened);
+ i_assert(bbox->open_tracked);
+
+ if (bbox->search_result != NULL)
+ mailbox_search_result_free(&bbox->search_result);
+
+ if (bbox->search_args != NULL &&
+ bbox->search_args_initialized) {
+ mail_search_args_deinit(bbox->search_args);
+ bbox->search_args_initialized = FALSE;
+ }
+ i_assert(mbox->backends_open_count > 0);
+ mbox->backends_open_count--;
+ bbox->open_tracked = FALSE;
+
+ DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+
+ /* stop receiving notifications */
+ if (bbox->notify_changes_started)
+ mailbox_notify_changes_stop(bbox->box);
+ bbox->notify_changes_started = FALSE;
+
+ mailbox_close(bbox->box);
+}
+
+void virtual_backend_box_accessed(struct virtual_mailbox *mbox,
+ struct virtual_backend_box *bbox)
+{
+ DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+ DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head,
+ &mbox->open_backend_boxes_tail, bbox,
+ prev_open, next_open);
+}
+
+static void virtual_mailbox_close_internal(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box **bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get_modifiable(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (bboxes[i]->box == NULL)
+ continue;
+ if (bboxes[i]->notify != NULL)
+ mailbox_list_notify_deinit(&bboxes[i]->notify);
+ if (bboxes[i]->box->opened)
+ virtual_backend_box_close(mbox, bboxes[i]);
+ mailbox_free(&bboxes[i]->box);
+ if (array_is_created(&bboxes[i]->sync_outside_expunges))
+ array_free(&bboxes[i]->sync_outside_expunges);
+ array_free(&bboxes[i]->sync_pending_removes);
+ array_free(&bboxes[i]->uids);
+ }
+ i_assert(mbox->backends_open_count == 0);
+}
+
+static int
+virtual_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED,
+ enum mailbox_existence *existence_r)
+{
+ return index_storage_mailbox_exists_full(box, VIRTUAL_CONFIG_FNAME,
+ existence_r);
+}
+
+static int virtual_mailbox_open(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ bool broken;
+ int ret = 0;
+
+ if (virtual_mailbox_is_in_open_stack(mbox->storage, box->name)) {
+ mailbox_set_critical(box,
+ "Virtual mailbox loops: %s", box->name);
+ return -1;
+ }
+
+ if (!array_is_created(&mbox->backend_boxes))
+ ret = virtual_config_read(mbox);
+ if (ret == 0) {
+ array_push_back(&mbox->storage->open_stack, &box->name);
+ ret = virtual_mailboxes_open(mbox, box->flags);
+ array_pop_back(&mbox->storage->open_stack);
+ }
+ if (ret == 0)
+ ret = index_storage_mailbox_open(box, FALSE);
+ if (ret < 0) {
+ virtual_mailbox_close_internal(mbox);
+ return -1;
+ }
+
+ mbox->virtual_ext_id =
+ mail_index_ext_register(mbox->box.index, "virtual", 0,
+ sizeof(struct virtual_mail_index_record),
+ sizeof(uint32_t));
+ mbox->virtual_ext2_id =
+ mail_index_ext_register(mbox->box.index, "virtual2", 0, 0, 0);
+
+ mbox->virtual_guid_ext_id =
+ mail_index_ext_register(mbox->box.index, "virtual-guid", GUID_128_SIZE,
+ 0, 0);
+
+ if (virtual_mailbox_ext_header_read(mbox, box->view, &broken) < 0) {
+ virtual_mailbox_close_internal(mbox);
+ index_storage_mailbox_close(box);
+ return -1;
+ }
+
+ /* if GUID is missing write it here */
+ if (guid_128_is_empty(mbox->guid)) {
+ guid_128_generate(mbox->guid);
+ struct mail_index_transaction *t =
+ mail_index_transaction_begin(box->view, 0);
+ mail_index_update_header_ext(t, mbox->virtual_guid_ext_id,
+ 0, mbox->guid, GUID_128_SIZE);
+ if (mail_index_transaction_commit(&t) < 0) {
+ mailbox_set_critical(box,
+ "Cannot write GUID for virtual mailbox to index");
+ virtual_mailbox_close_internal(mbox);
+ index_storage_mailbox_close(box);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void virtual_mailbox_close(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ virtual_mailbox_close_internal(mbox);
+ index_storage_mailbox_close(box);
+}
+
+static void virtual_mailbox_free(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ virtual_config_free(mbox);
+ index_storage_mailbox_free(box);
+}
+
+static int
+virtual_mailbox_create(struct mailbox *box,
+ const struct mailbox_update *update ATTR_UNUSED,
+ bool directory ATTR_UNUSED)
+{
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't create virtual mailboxes");
+ return -1;
+}
+
+static int
+virtual_mailbox_update(struct mailbox *box,
+ const struct mailbox_update *update ATTR_UNUSED)
+{
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't update virtual mailboxes");
+ return -1;
+}
+
+static int virtual_storage_set_have_guid_flags(struct virtual_mailbox *mbox)
+{
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+ struct mailbox_status status;
+
+ if (!mbox->box.opened) {
+ if (mailbox_open(&mbox->box) < 0)
+ return -1;
+ }
+
+ mbox->have_guids = TRUE;
+ mbox->have_save_guids = TRUE;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (mailbox_get_status(bboxes[i]->box, 0, &status) < 0) {
+ const char *errstr;
+ enum mail_error error;
+
+ errstr = mailbox_get_last_error(bboxes[i]->box, &error);
+ if (error == MAIL_ERROR_NOTFOUND) {
+ /* backend mailbox was just lost - skip it */
+ continue;
+ }
+ /* Not expected to happen, but we can't return failure
+ since this could be called from
+ mailbox_get_open_status() and it would panic.
+ So just log the error and skip the mailbox. */
+ mailbox_set_critical(&mbox->box,
+ "Virtual mailbox: Failed to get have_guid existence for backend mailbox %s: %s",
+ mailbox_get_vname(bboxes[i]->box), errstr);
+ continue;
+ }
+ if (!status.have_guids)
+ mbox->have_guids = FALSE;
+ if (!status.have_save_guids)
+ mbox->have_save_guids = FALSE;
+ }
+ return 0;
+}
+
+static int
+virtual_storage_get_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ if ((items & STATUS_LAST_CACHED_SEQ) != 0)
+ items |= STATUS_MESSAGES;
+
+ if (index_storage_get_status(box, items, status_r) < 0)
+ return -1;
+
+ if ((items & STATUS_LAST_CACHED_SEQ) != 0) {
+ /* Virtual mailboxes have no cached data of their own, so the
+ current value is always 0. The most important use for this
+ functionality is for "doveadm index" to do FTS indexing and
+ it doesn't really matter there if we set this value
+ correctly or not. So for now just assume that everything is
+ indexed. */
+ status_r->last_cached_seq = status_r->messages;
+ }
+ if (!mbox->have_guid_flags_set) {
+ if (virtual_storage_set_have_guid_flags(mbox) < 0)
+ return -1;
+ mbox->have_guid_flags_set = TRUE;
+ }
+
+ if (mbox->have_guids)
+ status_r->have_guids = TRUE;
+ if (mbox->have_save_guids)
+ status_r->have_save_guids = TRUE;
+ return 0;
+}
+
+static int
+virtual_mailbox_get_metadata(struct mailbox *box,
+ enum mailbox_metadata_items items,
+ struct mailbox_metadata *metadata_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ if (index_mailbox_get_metadata(box, items, metadata_r) < 0)
+ return -1;
+ i_assert(box->opened);
+ if ((items & MAILBOX_METADATA_GUID) != 0) {
+ if (guid_128_is_empty(mbox->guid)) {
+ mailbox_set_critical(box, "GUID missing for virtual folder");
+ return -1;
+ }
+ guid_128_copy(metadata_r->guid, mbox->guid);
+ }
+ return 0;
+}
+
+static void
+virtual_notify_callback(struct mailbox *bbox ATTR_UNUSED, struct mailbox *box)
+{
+ box->notify_callback(box, box->notify_context);
+}
+
+static void virtual_backend_box_changed(struct virtual_backend_box *bbox)
+{
+ virtual_notify_callback(bbox->box, &bbox->virtual_mbox->box);
+}
+
+static int virtual_notify_start(struct virtual_backend_box *bbox)
+{
+ i_assert(bbox->notify == NULL);
+ if (mailbox_list_notify_init(bbox->box->list, MAILBOX_LIST_NOTIFY_STATUS,
+ &bbox->notify) < 0)
+ /* did not support notifications */
+ return -1;
+ mailbox_list_notify_wait(bbox->notify, virtual_backend_box_changed, bbox);
+ return 0;
+}
+
+static void virtual_notify_changes(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box **bboxp;
+
+ if (box->notify_callback == NULL) {
+ array_foreach_modifiable(&mbox->backend_boxes, bboxp) {
+ if ((*bboxp)->notify_changes_started) {
+ mailbox_notify_changes_stop((*bboxp)->box);
+ (*bboxp)->notify_changes_started = FALSE;
+ }
+ if ((*bboxp)->notify != NULL)
+ mailbox_list_notify_deinit(&(*bboxp)->notify);
+ }
+ return;
+ }
+
+ array_foreach_modifiable(&mbox->backend_boxes, bboxp) {
+ if (array_count(&mbox->backend_boxes) == 1 &&
+ (*bboxp)->box->opened) {
+ /* There's only a single backend mailbox and its
+ indexes are already opened. Might as well use the
+ backend directly for notifications. */
+ } else {
+ /* we are already waiting for notifications */
+ if ((*bboxp)->notify != NULL)
+ continue;
+ /* wait for notifications */
+ if (virtual_notify_start(*bboxp) == 0)
+ continue;
+ /* it did not work, so open the mailbox and use
+ alternative method */
+ }
+
+ if (!(*bboxp)->box->opened &&
+ virtual_backend_box_open(mbox, *bboxp) < 0) {
+ /* we can't report error in here, so do it later */
+ (*bboxp)->open_failed = TRUE;
+ continue;
+ }
+ mailbox_notify_changes((*bboxp)->box,
+ virtual_notify_callback, box);
+ (*bboxp)->notify_changes_started = TRUE;
+ }
+}
+
+static void
+virtual_uidmap_to_uid_array(struct virtual_backend_box *bbox,
+ ARRAY_TYPE(seq_range) *uids_r)
+{
+ const struct virtual_backend_uidmap *uid;
+ array_foreach(&bbox->uids, uid) {
+ seq_range_array_add(uids_r, uid->real_uid);
+ }
+}
+
+static void
+virtual_get_virtual_uids(struct mailbox *box,
+ struct mailbox *backend_mailbox,
+ const ARRAY_TYPE(seq_range) *backend_uids,
+ ARRAY_TYPE(seq_range) *virtual_uids_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box *bbox;
+ const struct virtual_backend_uidmap *uids;
+ ARRAY_TYPE(seq_range) uid_range;
+ struct seq_range_iter iter;
+ unsigned int n, i, count;
+ uint32_t uid;
+
+ if (mbox->lookup_prev_bbox != NULL &&
+ strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0)
+ bbox = mbox->lookup_prev_bbox;
+ else {
+ bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname);
+ mbox->lookup_prev_bbox = bbox;
+ }
+ if (bbox == NULL)
+ return;
+
+ uids = array_get(&bbox->uids, &count); i = 0;
+
+ t_array_init(&uid_range, 8);
+ virtual_uidmap_to_uid_array(bbox, &uid_range);
+ seq_range_array_intersect(&uid_range, backend_uids);
+
+ seq_range_array_iter_init(&iter, &uid_range); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+ while (i < count && uids[i].real_uid < uid) i++;
+ if (i < count && uids[i].real_uid == uid) {
+ i_assert(uids[i].virtual_uid > 0);
+ seq_range_array_add(virtual_uids_r,
+ uids[i].virtual_uid);
+ i++;
+ }
+ }
+}
+
+static void
+virtual_get_virtual_uid_map(struct mailbox *box,
+ struct mailbox *backend_mailbox,
+ const ARRAY_TYPE(seq_range) *backend_uids,
+ ARRAY_TYPE(uint32_t) *virtual_uids_r)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box *bbox;
+ const struct virtual_backend_uidmap *uids;
+ struct seq_range_iter iter;
+ unsigned int n, i, count;
+ uint32_t uid;
+
+ if (mbox->lookup_prev_bbox != NULL &&
+ strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0)
+ bbox = mbox->lookup_prev_bbox;
+ else {
+ bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname);
+ mbox->lookup_prev_bbox = bbox;
+ }
+ if (bbox == NULL)
+ return;
+
+ uids = array_get(&bbox->uids, &count); i = 0;
+ seq_range_array_iter_init(&iter, backend_uids); n = 0;
+ while (seq_range_array_iter_nth(&iter, n++, &uid)) {
+ while (i < count && uids[i].real_uid < uid) i++;
+ if (i == count || uids[i].real_uid > uid) {
+ uint32_t zero = 0;
+
+ array_push_back(virtual_uids_r, &zero);
+ } else {
+ i_assert(uids[i].virtual_uid > 0);
+ array_push_back(virtual_uids_r, &uids[i].virtual_uid);
+ i++;
+ }
+ }
+}
+
+static void
+virtual_get_virtual_backend_boxes(struct mailbox *box,
+ ARRAY_TYPE(mailboxes) *mailboxes,
+ bool only_with_msgs)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+ struct virtual_backend_box *const *bboxes;
+ unsigned int i, count;
+
+ bboxes = array_get(&mbox->backend_boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (!only_with_msgs || array_count(&bboxes[i]->uids) > 0)
+ array_push_back(mailboxes, &bboxes[i]->box);
+ }
+}
+
+static bool virtual_is_inconsistent(struct mailbox *box)
+{
+ struct virtual_mailbox *mbox = (struct virtual_mailbox *)box;
+
+ if (mbox->inconsistent)
+ return TRUE;
+
+ return index_storage_is_inconsistent(box);
+}
+
+static int
+virtual_list_index_has_changed(struct mailbox *box ATTR_UNUSED,
+ struct mail_index_view *list_view ATTR_UNUSED,
+ uint32_t seq ATTR_UNUSED, bool quick ATTR_UNUSED,
+ const char **reason_r)
+{
+ /* we don't have any quick and easy optimizations for tracking
+ virtual folders. ideally we'd completely disable mailbox list
+ indexes for them, but this is the easiest way to do it for now. */
+ *reason_r = "Virtual indexes always change";
+ return 1;
+}
+
+static void
+virtual_list_index_update_sync(struct mailbox *box ATTR_UNUSED,
+ struct mail_index_transaction *trans ATTR_UNUSED,
+ uint32_t seq ATTR_UNUSED)
+{
+}
+
+struct mail_storage virtual_storage = {
+ .name = VIRTUAL_STORAGE_NAME,
+ .class_flags = MAIL_STORAGE_CLASS_FLAG_NOQUOTA |
+ MAIL_STORAGE_CLASS_FLAG_SECONDARY_INDEX,
+
+ .v = {
+ NULL,
+ virtual_storage_alloc,
+ virtual_storage_create,
+ index_storage_destroy,
+ NULL,
+ virtual_storage_get_list_settings,
+ NULL,
+ virtual_mailbox_alloc,
+ NULL,
+ NULL,
+ }
+};
+
+struct mailbox virtual_mailbox = {
+ .v = {
+ index_storage_is_readonly,
+ index_storage_mailbox_enable,
+ virtual_mailbox_exists,
+ virtual_mailbox_open,
+ virtual_mailbox_close,
+ virtual_mailbox_free,
+ virtual_mailbox_create,
+ virtual_mailbox_update,
+ index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
+ virtual_storage_get_status,
+ virtual_mailbox_get_metadata,
+ index_storage_set_subscribed,
+ index_storage_attribute_set,
+ index_storage_attribute_get,
+ index_storage_attribute_iter_init,
+ index_storage_attribute_iter_next,
+ index_storage_attribute_iter_deinit,
+ virtual_list_index_has_changed,
+ virtual_list_index_update_sync,
+ virtual_storage_sync_init,
+ index_mailbox_sync_next,
+ index_mailbox_sync_deinit,
+ NULL,
+ virtual_notify_changes,
+ virtual_transaction_begin,
+ virtual_transaction_commit,
+ virtual_transaction_rollback,
+ NULL,
+ virtual_mail_alloc,
+ virtual_search_init,
+ virtual_search_deinit,
+ virtual_search_next_nonblock,
+ virtual_search_next_update_seq,
+ index_storage_search_next_match_mail,
+ virtual_save_alloc,
+ virtual_save_begin,
+ virtual_save_continue,
+ virtual_save_finish,
+ virtual_save_cancel,
+ mail_storage_copy,
+ NULL,
+ NULL,
+ NULL,
+ virtual_is_inconsistent
+ }
+};
+
+struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs = {
+ virtual_get_virtual_uids,
+ virtual_get_virtual_uid_map,
+ virtual_get_virtual_backend_boxes
+};