summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-index-notify.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-index-notify.c')
-rw-r--r--src/lib-storage/list/mailbox-list-index-notify.c967
1 files changed, 967 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-index-notify.c b/src/lib-storage/list/mailbox-list-index-notify.c
new file mode 100644
index 0000000..c07e95b
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-notify.c
@@ -0,0 +1,967 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-index-private.h"
+#include "mail-transaction-log-private.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-notify.h"
+#include "mailbox-list-notify-tree.h"
+#include "mailbox-list-index.h"
+
+#include <sys/stat.h>
+
+#define NOTIFY_DELAY_MSECS 500
+
+enum ilist_ext_type {
+ ILIST_EXT_NONE,
+ ILIST_EXT_BASE,
+ ILIST_EXT_MSGS,
+ ILIST_EXT_HIGHESTMODSEQ,
+ ILIST_EXT_UNKNOWN
+};
+
+struct mailbox_list_notify_rename {
+ uint32_t old_uid, new_uid;
+};
+
+struct mailbox_list_inotify_entry {
+ uint32_t uid;
+ guid_128_t guid;
+ bool expunge;
+};
+
+struct mailbox_list_notify_index {
+ struct mailbox_list_notify notify;
+
+ struct mailbox_tree_context *subscriptions;
+ struct mailbox_list_notify_tree *tree;
+ struct mail_index_view *view, *old_view;
+ struct mail_index_view_sync_ctx *sync_ctx;
+ enum ilist_ext_type cur_ext;
+ uint32_t cur_ext_id;
+
+ void (*wait_callback)(void *context);
+ void *wait_context;
+ struct io *io_wait, *io_wait_inbox;
+ struct timeout *to_wait, *to_notify;
+
+ ARRAY_TYPE(seq_range) new_uids, expunged_uids, changed_uids;
+ ARRAY_TYPE(const_string) new_subscriptions, new_unsubscriptions;
+ ARRAY(struct mailbox_list_notify_rename) renames;
+ struct seq_range_iter new_uids_iter, expunged_uids_iter;
+ struct seq_range_iter changed_uids_iter;
+ unsigned int new_uids_n, expunged_uids_n, changed_uids_n;
+ unsigned int rename_idx, subscription_idx, unsubscription_idx;
+
+ struct mailbox_list_notify_rec notify_rec;
+ string_t *rec_name;
+
+ char *list_log_path, *inbox_log_path;
+ struct stat list_last_st, inbox_last_st;
+ struct mailbox *inbox;
+
+ bool initialized:1;
+ bool read_failed:1;
+ bool inbox_event_pending:1;
+};
+
+static const enum mailbox_status_items notify_status_items =
+ STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES |
+ STATUS_UNSEEN | STATUS_HIGHESTMODSEQ;
+
+static enum mailbox_list_notify_event
+mailbox_list_index_get_changed_events(const struct mailbox_notify_node *nnode,
+ const struct mailbox_status *status)
+{
+ enum mailbox_list_notify_event events = 0;
+
+ if (nnode->uidvalidity != status->uidvalidity)
+ events |= MAILBOX_LIST_NOTIFY_UIDVALIDITY;
+ if (nnode->uidnext != status->uidnext)
+ events |= MAILBOX_LIST_NOTIFY_APPENDS;
+ if (nnode->messages > status->messages) {
+ /* NOTE: not entirely reliable, since there could be both
+ expunges and appends.. but it shouldn't make any difference
+ in practise, since anybody interested in expunges is most
+ likely also interested in appends. */
+ events |= MAILBOX_LIST_NOTIFY_EXPUNGES;
+ }
+ if (nnode->unseen != status->unseen)
+ events |= MAILBOX_LIST_NOTIFY_SEEN_CHANGES;
+ if (nnode->highest_modseq < status->highest_modseq)
+ events |= MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES;
+ return events;
+}
+
+static void
+mailbox_notify_node_update_status(struct mailbox_notify_node *nnode,
+ struct mailbox_status *status)
+{
+ nnode->uidvalidity = status->uidvalidity;
+ nnode->uidnext = status->uidnext;
+ nnode->messages = status->messages;
+ nnode->unseen = status->unseen;
+ nnode->highest_modseq = status->highest_modseq;
+}
+
+static void
+mailbox_list_index_notify_init_inbox(struct mailbox_list_notify_index *inotify)
+{
+ inotify->inbox = mailbox_alloc(inotify->notify.list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ if (mailbox_open(inotify->inbox) < 0)
+ mailbox_free(&inotify->inbox);
+ else
+ inotify->inbox_log_path =
+ i_strconcat(inotify->inbox->index->filepath,
+ ".log", NULL);
+}
+
+int mailbox_list_index_notify_init(struct mailbox_list *list,
+ enum mailbox_list_notify_event mask,
+ struct mailbox_list_notify **notify_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
+ struct mailbox_list_notify_index *inotify;
+ const char *index_dir;
+
+ if (ilist == NULL) {
+ /* can't do this without mailbox list indexes */
+ return -1;
+ }
+
+ (void)mailbox_list_index_refresh(list);
+
+ inotify = i_new(struct mailbox_list_notify_index, 1);
+ inotify->notify.list = list;
+ inotify->notify.mask = mask;
+ inotify->view = mail_index_view_open(ilist->index);
+ inotify->old_view = mail_index_view_dup_private(inotify->view);
+ inotify->tree = mailbox_list_notify_tree_init(list);
+ i_array_init(&inotify->new_uids, 8);
+ i_array_init(&inotify->expunged_uids, 8);
+ i_array_init(&inotify->changed_uids, 16);
+ i_array_init(&inotify->renames, 16);
+ i_array_init(&inotify->new_subscriptions, 16);
+ i_array_init(&inotify->new_unsubscriptions, 16);
+ inotify->rec_name = str_new(default_pool, 64);
+ if ((mask & (MAILBOX_LIST_NOTIFY_SUBSCRIBE |
+ MAILBOX_LIST_NOTIFY_UNSUBSCRIBE)) != 0) {
+ (void)mailbox_list_iter_subscriptions_refresh(list);
+ mailbox_tree_sort(list->subscriptions);
+ inotify->subscriptions = mailbox_tree_dup(list->subscriptions);
+ }
+ inotify->list_log_path = i_strdup(ilist->index->log->filepath);
+ if (list->mail_set->mailbox_list_index_include_inbox) {
+ /* INBOX can be handled also using mailbox list index */
+ } else if ((list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) == 0) {
+ /* no INBOX in this namespace */
+ } else if ((mask & MAILBOX_LIST_NOTIFY_STATUS) == 0) {
+ /* not interested in mailbox changes */
+ } else if (mailbox_list_get_path(list, "INBOX", MAILBOX_LIST_PATH_TYPE_INDEX,
+ &index_dir) <= 0) {
+ /* no indexes for INBOX? can't handle it */
+ } else {
+ mailbox_list_index_notify_init_inbox(inotify);
+ }
+
+ *notify_r = &inotify->notify;
+ return 1;
+}
+
+void mailbox_list_index_notify_deinit(struct mailbox_list_notify *notify)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+ bool b;
+
+ if (inotify->inbox != NULL)
+ mailbox_free(&inotify->inbox);
+ if (inotify->subscriptions != NULL)
+ mailbox_tree_deinit(&inotify->subscriptions);
+ io_remove(&inotify->io_wait);
+ io_remove(&inotify->io_wait_inbox);
+ timeout_remove(&inotify->to_wait);
+ timeout_remove(&inotify->to_notify);
+ if (inotify->sync_ctx != NULL)
+ (void)mail_index_view_sync_commit(&inotify->sync_ctx, &b);
+ mail_index_view_close(&inotify->view);
+ mail_index_view_close(&inotify->old_view);
+ mailbox_list_notify_tree_deinit(&inotify->tree);
+ array_free(&inotify->new_subscriptions);
+ array_free(&inotify->new_unsubscriptions);
+ array_free(&inotify->new_uids);
+ array_free(&inotify->expunged_uids);
+ array_free(&inotify->changed_uids);
+ array_free(&inotify->renames);
+ str_free(&inotify->rec_name);
+ i_free(inotify->list_log_path);
+ i_free(inotify->inbox_log_path);
+ i_free(inotify);
+}
+
+static struct mailbox_list_index_node *
+notify_lookup_guid(struct mailbox_list_notify_index *inotify,
+ struct mail_index_view *view,
+ uint32_t uid, enum mailbox_status_items items,
+ struct mailbox_status *status_r, guid_128_t guid_r)
+{
+ struct mailbox_list_index *ilist =
+ INDEX_LIST_CONTEXT_REQUIRE(inotify->notify.list);
+ struct mailbox_list_index_node *index_node;
+ const char *reason;
+ uint32_t seq;
+
+ if (!mail_index_lookup_seq(view, uid, &seq))
+ return NULL;
+
+ index_node = mailbox_list_index_lookup_uid(ilist, uid);
+ if (index_node == NULL) {
+ /* re-parse the index list using the given view. we could be
+ jumping here between old and new view. */
+ (void)mailbox_list_index_parse(inotify->notify.list,
+ view, FALSE);
+ index_node = mailbox_list_index_lookup_uid(ilist, uid);
+ if (index_node == NULL)
+ return NULL;
+ }
+
+ /* get GUID */
+ i_zero(status_r);
+ memset(guid_r, 0, GUID_128_SIZE);
+ (void)mailbox_list_index_status(inotify->notify.list, view, seq,
+ items, status_r, guid_r, NULL, &reason);
+ return index_node;
+}
+
+static void notify_update_stat(struct mailbox_list_notify_index *inotify,
+ bool stat_list, bool stat_inbox)
+{
+ bool call = FALSE;
+
+ if (stat_list &&
+ stat(inotify->list_log_path, &inotify->list_last_st) < 0 &&
+ errno != ENOENT) {
+ e_error(inotify->notify.list->ns->user->event,
+ "stat(%s) failed: %m", inotify->list_log_path);
+ call = TRUE;
+ }
+ if (inotify->inbox_log_path != NULL && stat_inbox) {
+ if (stat(inotify->inbox_log_path, &inotify->inbox_last_st) < 0 &&
+ errno != ENOENT) {
+ e_error(inotify->notify.list->ns->user->event,
+ "stat(%s) failed: %m", inotify->inbox_log_path);
+ call = TRUE;
+ }
+ }
+ if (call)
+ mailbox_list_index_notify_wait(&inotify->notify, NULL, NULL);
+}
+
+static void
+mailbox_list_index_notify_sync_init(struct mailbox_list_notify_index *inotify)
+{
+ struct mail_index_view_sync_rec sync_rec;
+
+ notify_update_stat(inotify, TRUE, TRUE);
+ (void)mail_index_refresh(inotify->view->index);
+
+ /* sync the view so that map extensions gets updated */
+ inotify->sync_ctx = mail_index_view_sync_begin(inotify->view, 0);
+ mail_transaction_log_view_mark(inotify->view->log_view);
+ while (mail_index_view_sync_next(inotify->sync_ctx, &sync_rec)) ;
+ mail_transaction_log_view_rewind(inotify->view->log_view);
+
+ inotify->cur_ext = ILIST_EXT_NONE;
+ inotify->cur_ext_id = (uint32_t)-1;
+}
+
+static bool notify_ext_rec(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify *notify = &inotify->notify;
+
+ switch (inotify->cur_ext) {
+ case ILIST_EXT_NONE:
+ i_unreached();
+ case ILIST_EXT_BASE:
+ /* UIDVALIDITY changed */
+ if ((notify->mask & MAILBOX_LIST_NOTIFY_UIDVALIDITY) == 0)
+ return FALSE;
+ break;
+ case ILIST_EXT_MSGS:
+ /* APPEND, EXPUNGE, \Seen or \Recent flag change */
+ if ((notify->mask & MAILBOX_LIST_NOTIFY_STATUS) == 0)
+ return FALSE;
+ break;
+ case ILIST_EXT_HIGHESTMODSEQ:
+ /* when this doesn't come with EXT_MSGS update,
+ it can only be a flag change or an explicit
+ modseq change */
+ if ((notify->mask & MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES) == 0)
+ return FALSE;
+ break;
+ case ILIST_EXT_UNKNOWN:
+ return FALSE;
+ }
+ seq_range_array_add(&inotify->changed_uids, uid);
+ return TRUE;
+}
+
+static int
+mailbox_list_index_notify_read_next(struct mailbox_list_notify_index *inotify)
+{
+ struct mailbox_list_notify *notify = &inotify->notify;
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(notify->list);
+ const struct mail_transaction_header *hdr;
+ const void *data;
+ int ret;
+
+ ret = mail_transaction_log_view_next(inotify->view->log_view,
+ &hdr, &data);
+ if (ret <= 0)
+ return ret;
+
+ if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
+ /* all mailbox index updates are external */
+ return 1;
+ }
+ switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
+ case MAIL_TRANSACTION_APPEND: {
+ /* mailbox added or renamed */
+ const struct mail_index_record *rec, *end;
+
+ if ((notify->mask & (MAILBOX_LIST_NOTIFY_CREATE |
+ MAILBOX_LIST_NOTIFY_RENAME)) == 0)
+ break;
+
+ end = CONST_PTR_OFFSET(data, hdr->size);
+ for (rec = data; rec != end; rec++)
+ seq_range_array_add(&inotify->new_uids, rec->uid);
+ break;
+ }
+ case MAIL_TRANSACTION_EXPUNGE_GUID: {
+ /* mailbox deleted or renamed */
+ const struct mail_transaction_expunge_guid *rec, *end;
+
+ if ((notify->mask & (MAILBOX_LIST_NOTIFY_DELETE |
+ MAILBOX_LIST_NOTIFY_RENAME)) == 0)
+ break;
+
+ end = CONST_PTR_OFFSET(data, hdr->size);
+ for (rec = data; rec != end; rec++)
+ seq_range_array_add(&inotify->expunged_uids, rec->uid);
+ break;
+ }
+ case MAIL_TRANSACTION_EXT_INTRO: {
+ struct mail_index_map *map = inotify->view->map;
+ const struct mail_transaction_ext_intro *rec = data;
+ const struct mail_index_ext *ext = NULL;
+ const char *name;
+ uint32_t ext_map_idx;
+
+ if (!array_is_created(&map->extensions))
+ break;
+ /* we want to know what extension the future
+ ext-rec-updates are changing. we're assuming here that
+ there is only one ext-intro record before those,
+ which is true at least for now. */
+ if (rec->ext_id != (uint32_t)-1 &&
+ rec->ext_id < array_count(&map->extensions)) {
+ /* get extension by id */
+ ext = array_idx(&map->extensions, rec->ext_id);
+ } else if (rec->name_size > 0) {
+ /* by name */
+ name = t_strndup(rec+1, rec->name_size);
+ if (mail_index_map_lookup_ext(map, name, &ext_map_idx))
+ ext = array_idx(&map->extensions, ext_map_idx);
+ }
+ if (ext != NULL) {
+ if (ext->index_idx == ilist->ext_id)
+ inotify->cur_ext = ILIST_EXT_BASE;
+ else if (ext->index_idx == ilist->msgs_ext_id)
+ inotify->cur_ext = ILIST_EXT_MSGS;
+ else if (ext->index_idx == ilist->hmodseq_ext_id)
+ inotify->cur_ext = ILIST_EXT_HIGHESTMODSEQ;
+ else
+ inotify->cur_ext = ILIST_EXT_UNKNOWN;
+ inotify->cur_ext_id = ext->index_idx;
+ }
+ break;
+ }
+ case MAIL_TRANSACTION_EXT_REC_UPDATE: {
+ const struct mail_index_registered_ext *ext;
+ const struct mail_transaction_ext_rec_update *rec;
+ unsigned int i, record_size;
+
+ if (inotify->cur_ext == ILIST_EXT_NONE) {
+ e_error(ilist->index->event,
+ "%s: Missing ext-intro for ext-rec-update",
+ ilist->index->filepath);
+ break;
+ }
+
+ /* the record is padded to 32bits in the transaction log */
+ ext = array_idx(&inotify->view->index->extensions,
+ inotify->cur_ext_id);
+ record_size = (sizeof(*rec) + ext->record_size + 3) & ~3U;
+ for (i = 0; i < hdr->size; i += record_size) {
+ rec = CONST_PTR_OFFSET(data, i);
+
+ if (i + record_size > hdr->size)
+ break;
+ if (!notify_ext_rec(inotify, rec->uid))
+ break;
+ }
+ break;
+ }
+ }
+ return 1;
+}
+
+static int
+mailbox_list_inotify_entry_guid_cmp(const struct mailbox_list_inotify_entry *r1,
+ const struct mailbox_list_inotify_entry *r2)
+{
+ int ret;
+
+ ret = memcmp(r1->guid, r2->guid, sizeof(r1->guid));
+ if (ret != 0)
+ return ret;
+
+ if (r1->expunge == r2->expunge) {
+ /* this really shouldn't happen */
+ return 0;
+ }
+ return r1->expunge ? -1 : 1;
+}
+
+static void
+mailbox_list_index_notify_find_renames(struct mailbox_list_notify_index *inotify)
+{
+ ARRAY(struct mailbox_list_inotify_entry) entries;
+ struct mailbox_status status;
+ struct mailbox_list_notify_rename *rename;
+ struct mailbox_list_inotify_entry *entry;
+ const struct mailbox_list_inotify_entry *e;
+ unsigned int i, count;
+ guid_128_t guid;
+ uint32_t uid;
+
+ /* first get all of the added and expunged GUIDs */
+ t_array_init(&entries, array_count(&inotify->new_uids) +
+ array_count(&inotify->expunged_uids));
+ while (seq_range_array_iter_nth(&inotify->expunged_uids_iter,
+ inotify->expunged_uids_n++, &uid)) {
+ if (notify_lookup_guid(inotify, inotify->old_view, uid,
+ 0, &status, guid) != NULL &&
+ !guid_128_is_empty(guid)) {
+ entry = array_append_space(&entries);
+ entry->uid = uid;
+ entry->expunge = TRUE;
+ memcpy(entry->guid, guid, sizeof(entry->guid));
+ }
+ }
+
+ (void)mailbox_list_index_parse(inotify->notify.list,
+ inotify->view, TRUE);
+ while (seq_range_array_iter_nth(&inotify->new_uids_iter,
+ inotify->new_uids_n++, &uid)) {
+ if (notify_lookup_guid(inotify, inotify->view, uid,
+ 0, &status, guid) != NULL &&
+ !guid_128_is_empty(guid)) {
+ entry = array_append_space(&entries);
+ entry->uid = uid;
+ memcpy(entry->guid, guid, sizeof(entry->guid));
+ }
+ }
+
+ /* now sort the entries by GUID and find those that have been both
+ added and expunged */
+ array_sort(&entries, mailbox_list_inotify_entry_guid_cmp);
+
+ e = array_get(&entries, &count);
+ for (i = 1; i < count; i++) {
+ if (e[i-1].expunge && !e[i].expunge &&
+ memcmp(e[i-1].guid, e[i].guid, sizeof(e[i].guid)) == 0) {
+ rename = array_append_space(&inotify->renames);
+ rename->old_uid = e[i-1].uid;
+ rename->new_uid = e[i].uid;
+
+ seq_range_array_remove(&inotify->expunged_uids,
+ rename->old_uid);
+ seq_range_array_remove(&inotify->new_uids,
+ rename->new_uid);
+ }
+ }
+}
+
+static void
+mailbox_list_index_notify_find_subscribes(struct mailbox_list_notify_index *inotify)
+{
+ struct mailbox_tree_iterate_context *old_iter, *new_iter;
+ struct mailbox_tree_context *old_tree, *new_tree;
+ const char *old_path = NULL, *new_path = NULL;
+ pool_t pool;
+ int ret;
+
+ if (mailbox_list_iter_subscriptions_refresh(inotify->notify.list) < 0)
+ return;
+ mailbox_tree_sort(inotify->notify.list->subscriptions);
+
+ old_tree = inotify->subscriptions;
+ new_tree = mailbox_tree_dup(inotify->notify.list->subscriptions);
+
+ old_iter = mailbox_tree_iterate_init(old_tree, NULL, MAILBOX_SUBSCRIBED);
+ new_iter = mailbox_tree_iterate_init(new_tree, NULL, MAILBOX_SUBSCRIBED);
+
+ pool = mailbox_tree_get_pool(new_tree);
+ for (;;) {
+ if (old_path == NULL) {
+ if (mailbox_tree_iterate_next(old_iter, &old_path) == NULL)
+ old_path = NULL;
+ }
+ if (new_path == NULL) {
+ if (mailbox_tree_iterate_next(new_iter, &new_path) == NULL)
+ new_path = NULL;
+ }
+
+ if (old_path == NULL) {
+ if (new_path == NULL)
+ break;
+ ret = 1;
+ } else if (new_path == NULL)
+ ret = -1;
+ else {
+ ret = strcmp(old_path, new_path);
+ }
+
+ if (ret == 0) {
+ old_path = NULL;
+ new_path = NULL;
+ } else if (ret > 0) {
+ new_path = p_strdup(pool, new_path);
+ array_push_back(&inotify->new_subscriptions,
+ &new_path);
+ new_path = NULL;
+ } else {
+ old_path = p_strdup(pool, old_path);
+ array_push_back(&inotify->new_unsubscriptions,
+ &old_path);
+ old_path = NULL;
+ }
+ }
+ mailbox_tree_iterate_deinit(&old_iter);
+ mailbox_tree_iterate_deinit(&new_iter);
+
+ mailbox_tree_deinit(&inotify->subscriptions);
+ inotify->subscriptions = new_tree;
+}
+
+static void
+mailbox_list_index_notify_reset_iters(struct mailbox_list_notify_index *inotify)
+{
+ seq_range_array_iter_init(&inotify->new_uids_iter,
+ &inotify->new_uids);
+ seq_range_array_iter_init(&inotify->expunged_uids_iter,
+ &inotify->expunged_uids);
+ seq_range_array_iter_init(&inotify->changed_uids_iter,
+ &inotify->changed_uids);
+ inotify->changed_uids_n = 0;
+ inotify->new_uids_n = 0;
+ inotify->expunged_uids_n = 0;
+ inotify->rename_idx = 0;
+ inotify->subscription_idx = 0;
+ inotify->unsubscription_idx = 0;
+}
+
+static void
+mailbox_list_index_notify_read_init(struct mailbox_list_notify_index *inotify)
+{
+ bool b;
+ int ret;
+
+ mailbox_list_index_notify_sync_init(inotify);
+
+ /* read all changes from .log file */
+ while ((ret = mailbox_list_index_notify_read_next(inotify)) > 0) ;
+ inotify->read_failed = ret < 0;
+
+ (void)mail_index_view_sync_commit(&inotify->sync_ctx, &b);
+
+ /* remove changes for already deleted mailboxes */
+ seq_range_array_remove_seq_range(&inotify->new_uids,
+ &inotify->expunged_uids);
+ seq_range_array_remove_seq_range(&inotify->changed_uids,
+ &inotify->expunged_uids);
+ mailbox_list_index_notify_reset_iters(inotify);
+ if (array_count(&inotify->new_uids) > 0 &&
+ array_count(&inotify->expunged_uids) > 0) {
+ mailbox_list_index_notify_find_renames(inotify);
+ mailbox_list_index_notify_reset_iters(inotify);
+ }
+ if (inotify->subscriptions != NULL)
+ mailbox_list_index_notify_find_subscribes(inotify);
+
+ inotify->initialized = TRUE;
+}
+
+static void
+mailbox_list_index_notify_read_deinit(struct mailbox_list_notify_index *inotify)
+{
+ /* save the old view so we can look up expunged records */
+ mail_index_view_close(&inotify->old_view);
+ inotify->old_view = mail_index_view_dup_private(inotify->view);
+
+ array_clear(&inotify->new_subscriptions);
+ array_clear(&inotify->new_unsubscriptions);
+ array_clear(&inotify->new_uids);
+ array_clear(&inotify->expunged_uids);
+ array_clear(&inotify->changed_uids);
+ array_clear(&inotify->renames);
+
+ inotify->initialized = FALSE;
+}
+
+static bool
+mailbox_list_index_notify_lookup(struct mailbox_list_notify_index *inotify,
+ struct mail_index_view *view,
+ uint32_t uid, enum mailbox_status_items items,
+ struct mailbox_status *status_r,
+ struct mailbox_list_notify_rec **rec_r)
+{
+ struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+ struct mailbox_list_index_node *index_node;
+ const char *storage_name;
+ char ns_sep = mailbox_list_get_hierarchy_sep(inotify->notify.list);
+
+ i_zero(rec);
+ index_node = notify_lookup_guid(inotify, view, uid,
+ items, status_r, rec->guid);
+ if (index_node == NULL)
+ return FALSE;
+
+ /* get storage_name */
+ str_truncate(inotify->rec_name, 0);
+ mailbox_list_index_node_get_path(index_node, ns_sep, inotify->rec_name);
+ storage_name = str_c(inotify->rec_name);
+
+ rec->storage_name = storage_name;
+ rec->vname = mailbox_list_get_vname(inotify->notify.list,
+ rec->storage_name);
+ *rec_r = rec;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_rename(struct mailbox_list_notify_index *inotify,
+ unsigned int idx)
+{
+ const struct mailbox_list_notify_rename *rename;
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_status status;
+ const char *old_vname;
+
+ rename = array_idx(&inotify->renames, idx);
+
+ /* lookup the old name */
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->old_view,
+ rename->old_uid, 0, &status, &rec))
+ return FALSE;
+ old_vname = t_strdup(rec->vname);
+
+ /* return using the new name */
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+ rename->new_uid, 0, &status, &rec))
+ return FALSE;
+
+ rec->old_vname = old_vname;
+ rec->events = MAILBOX_LIST_NOTIFY_RENAME;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_subscribe(struct mailbox_list_notify_index *inotify,
+ unsigned int idx)
+{
+ struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+
+ i_zero(rec);
+ rec->vname = array_idx_elem(&inotify->new_subscriptions, idx);
+ rec->storage_name = mailbox_list_get_storage_name(inotify->notify.list,
+ rec->vname);
+ rec->events = MAILBOX_LIST_NOTIFY_SUBSCRIBE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_unsubscribe(struct mailbox_list_notify_index *inotify,
+ unsigned int idx)
+{
+ struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+
+ i_zero(rec);
+ rec->vname = array_idx_elem(&inotify->new_unsubscriptions, idx);
+ rec->storage_name = mailbox_list_get_storage_name(inotify->notify.list,
+ rec->vname);
+ rec->events = MAILBOX_LIST_NOTIFY_UNSUBSCRIBE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_expunge(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_status status;
+
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->old_view,
+ uid, 0, &status, &rec))
+ return FALSE;
+ rec->events = MAILBOX_LIST_NOTIFY_DELETE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_new(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_status status;
+
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+ uid, 0, &status, &rec))
+ i_unreached();
+ rec->events = MAILBOX_LIST_NOTIFY_CREATE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_change(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_notify_node *nnode, empty_node;
+ struct mailbox_status status;
+
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+ uid, notify_status_items,
+ &status, &rec)) {
+ /* Mailbox is already deleted. We won't get here if we're
+ tracking MAILBOX_LIST_NOTIFY_DELETE or _RENAME
+ (which update expunged_uids). */
+ return FALSE;
+ }
+
+ /* get the old status */
+ nnode = mailbox_list_notify_tree_lookup(inotify->tree,
+ rec->storage_name);
+ if (nnode == NULL) {
+ /* mailbox didn't exist earlier - report all events as new */
+ i_zero(&empty_node);
+ nnode = &empty_node;
+ }
+ rec->events |= mailbox_list_index_get_changed_events(nnode, &status);
+ /* update internal state */
+ mailbox_notify_node_update_status(nnode, &status);
+ return rec->events != 0;
+}
+
+static bool
+mailbox_list_index_notify_try_next(struct mailbox_list_notify_index *inotify)
+{
+ uint32_t uid;
+
+ /* first show mailbox deletes */
+ if (seq_range_array_iter_nth(&inotify->expunged_uids_iter,
+ inotify->expunged_uids_n++, &uid))
+ return mailbox_list_index_notify_expunge(inotify, uid);
+
+ /* mailbox renames */
+ if (inotify->rename_idx < array_count(&inotify->renames)) {
+ return mailbox_list_index_notify_rename(inotify,
+ inotify->rename_idx++);
+ }
+
+ /* next mailbox creates */
+ if (seq_range_array_iter_nth(&inotify->new_uids_iter,
+ inotify->new_uids_n++, &uid))
+ return mailbox_list_index_notify_new(inotify, uid);
+
+ /* subscribes */
+ if (inotify->subscription_idx < array_count(&inotify->new_subscriptions)) {
+ return mailbox_list_index_notify_subscribe(inotify,
+ inotify->subscription_idx++);
+ }
+ if (inotify->unsubscription_idx < array_count(&inotify->new_unsubscriptions)) {
+ return mailbox_list_index_notify_unsubscribe(inotify,
+ inotify->unsubscription_idx++);
+ }
+
+ /* STATUS updates */
+ while (seq_range_array_iter_nth(&inotify->changed_uids_iter,
+ inotify->changed_uids_n++, &uid)) {
+ if (mailbox_list_index_notify_change(inotify, uid))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static enum mailbox_list_notify_event
+mailbox_list_notify_inbox_get_events(struct mailbox_list_notify_index *inotify)
+{
+ struct mailbox_status old_status, new_status;
+ struct mailbox_notify_node old_nnode;
+
+ mailbox_get_open_status(inotify->inbox, notify_status_items, &old_status);
+ if (mailbox_sync(inotify->inbox, MAILBOX_SYNC_FLAG_FAST) < 0) {
+ e_error(inotify->notify.list->ns->user->event,
+ "Mailbox list index notify: Failed to sync INBOX: %s",
+ mailbox_get_last_internal_error(inotify->inbox, NULL));
+ return 0;
+ }
+ mailbox_get_open_status(inotify->inbox, notify_status_items, &new_status);
+
+ mailbox_notify_node_update_status(&old_nnode, &old_status);
+ return mailbox_list_index_get_changed_events(&old_nnode, &new_status);
+}
+
+int mailbox_list_index_notify_next(struct mailbox_list_notify *notify,
+ const struct mailbox_list_notify_rec **rec_r)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+
+ if (!inotify->initialized)
+ mailbox_list_index_notify_read_init(inotify);
+ if (mailbox_list_index_handle_corruption(notify->list) < 0)
+ return -1;
+
+ while (mailbox_list_index_notify_try_next(inotify)) {
+ if ((inotify->notify_rec.events & inotify->notify.mask) != 0) {
+ *rec_r = &inotify->notify_rec;
+ return 1;
+ } else {
+ /* caller doesn't care about this change */
+ }
+ }
+ if (inotify->inbox_event_pending) {
+ inotify->inbox_event_pending = FALSE;
+ i_zero(&inotify->notify_rec);
+ inotify->notify_rec.vname = "INBOX";
+ inotify->notify_rec.storage_name = "INBOX";
+ inotify->notify_rec.events =
+ mailbox_list_notify_inbox_get_events(inotify);
+ *rec_r = &inotify->notify_rec;
+ return 1;
+ }
+
+ mailbox_list_index_notify_read_deinit(inotify);
+ return inotify->read_failed ? -1 : 0;
+}
+
+static void notify_now_callback(struct mailbox_list_notify_index *inotify)
+{
+ timeout_remove(&inotify->to_notify);
+ inotify->wait_callback(inotify->wait_context);
+}
+
+static void list_notify_callback(struct mailbox_list_notify_index *inotify)
+{
+ struct stat list_prev_st = inotify->list_last_st;
+
+ if (inotify->to_notify != NULL) {
+ /* there's a pending notification already -
+ no need to stat() again */
+ return;
+ }
+
+ notify_update_stat(inotify, TRUE, FALSE);
+ if (ST_CHANGED(inotify->list_last_st, list_prev_st)) {
+ /* log has changed. call the callback with a small delay
+ to allow bundling multiple changes together */
+ inotify->to_notify =
+ timeout_add_short(NOTIFY_DELAY_MSECS,
+ notify_now_callback, inotify);
+ }
+}
+
+static void inbox_notify_callback(struct mailbox_list_notify_index *inotify)
+{
+ struct stat inbox_prev_st = inotify->inbox_last_st;
+
+ if (inotify->to_notify != NULL && inotify->inbox_event_pending) {
+ /* there's a pending INBOX notification already -
+ no need to stat() again */
+ return;
+ }
+
+ notify_update_stat(inotify, FALSE, TRUE);
+ if (ST_CHANGED(inotify->inbox_last_st, inbox_prev_st))
+ inotify->inbox_event_pending = TRUE;
+ if (inotify->inbox_event_pending && inotify->to_notify == NULL) {
+ /* log has changed. call the callback with a small delay
+ to allow bundling multiple changes together */
+ inotify->to_notify =
+ timeout_add_short(NOTIFY_DELAY_MSECS,
+ notify_now_callback, inotify);
+ }
+}
+
+static void full_notify_callback(struct mailbox_list_notify_index *inotify)
+{
+ list_notify_callback(inotify);
+ inbox_notify_callback(inotify);
+}
+
+void mailbox_list_index_notify_wait(struct mailbox_list_notify *notify,
+ void (*callback)(void *context),
+ void *context)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+ unsigned int check_interval;
+
+ inotify->wait_callback = callback;
+ inotify->wait_context = context;
+
+ if (callback == NULL) {
+ io_remove(&inotify->io_wait);
+ io_remove(&inotify->io_wait_inbox);
+ timeout_remove(&inotify->to_wait);
+ timeout_remove(&inotify->to_notify);
+ } else if (inotify->to_wait == NULL) {
+ (void)io_add_notify(inotify->list_log_path, list_notify_callback,
+ inotify, &inotify->io_wait);
+ /* we need to check for INBOX explicitly, because INBOX changes
+ don't get added to mailbox.list.index.log */
+ if (inotify->inbox_log_path != NULL) {
+ (void)io_add_notify(inotify->inbox_log_path,
+ inbox_notify_callback, inotify,
+ &inotify->io_wait_inbox);
+ }
+ /* check with timeout as well, in case io_add_notify()
+ doesn't work (e.g. NFS) */
+ check_interval = notify->list->mail_set->mailbox_idle_check_interval;
+ i_assert(check_interval > 0);
+ inotify->to_wait = timeout_add(check_interval * 1000,
+ full_notify_callback, inotify);
+ notify_update_stat(inotify, TRUE, TRUE);
+ }
+}
+
+void mailbox_list_index_notify_flush(struct mailbox_list_notify *notify)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+
+ if (inotify->to_notify == NULL &&
+ notify->list->mail_set->mailbox_idle_check_interval > 0) {
+ /* no pending notification - check if anything had changed */
+ full_notify_callback(inotify);
+ }
+ if (inotify->to_notify != NULL)
+ notify_now_callback(inotify);
+}