]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-storage: Support storing <ns prefix>/INBOX in mailbox list index
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 26 Jan 2026 21:58:29 +0000 (23:58 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 7 May 2026 20:22:22 +0000 (20:22 +0000)
Since it's identical to the INBOX as name, use a separate header field for
identifying it.

src/lib-storage/list/mailbox-list-index-iter.c
src/lib-storage/list/mailbox-list-index-sync.c
src/lib-storage/list/mailbox-list-index.c
src/lib-storage/list/mailbox-list-index.h

index 7088d11a84e49d0cc9e5a119a7ccd05b8798cda1..76326c693fc8c73f181f784892837affbd401172 100644 (file)
@@ -62,6 +62,8 @@ mailbox_list_index_iter_init(struct mailbox_list *list,
 static void
 mailbox_list_index_update_info(struct mailbox_list_index_iterate_context *ctx)
 {
+       struct mailbox_list_index *ilist =
+               INDEX_LIST_CONTEXT_REQUIRE(ctx->ctx.list);
        struct mailbox_list_index_node *node = ctx->next_node;
        struct mailbox *box;
 
@@ -94,7 +96,11 @@ mailbox_list_index_update_info(struct mailbox_list_index_iterate_context *ctx)
                /* listing INBOX/INBOX */
                ctx->info.vname = p_strconcat(ctx->ctx.info_pool,
                        ctx->ctx.list->ns->prefix, "INBOX", NULL);
-               ctx->info.flags |= MAILBOX_NONEXISTENT;
+               if (node->raw_name != ilist->raw_inbox_inbox_name_ptr) {
+                       /* We can't access it without escape character
+                          configured. */
+                       ctx->info.flags |= MAILBOX_NONEXISTENT;
+               }
        }
        if ((node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0)
                ctx->info.flags |= MAILBOX_NONEXISTENT;
index a5bf396ba7e58f4aba65636f17bb9decc9c3c5ff..756e576c35805e5b7ceb09f5501760d39fc5f4fd 100644 (file)
@@ -54,22 +54,15 @@ node_add_to_index(struct mailbox_list_index_sync_context *ctx,
 }
 
 static struct mailbox_list_index_node *
-mailbox_list_index_node_add(struct mailbox_list_index_sync_context *ctx,
-                           struct mailbox_list_index_node *parent,
-                           const char *name, uint32_t *seq_r)
+mailbox_list_index_node_add_common(struct mailbox_list_index_sync_context *ctx,
+                                  struct mailbox_list_index_node *parent)
 {
        struct mailbox_list_index_node *node;
-       char *dup_name;
-       mailbox_list_name_unescape(&name,
-               ctx->list->mail_set->mailbox_list_storage_escape_char[0]);
 
        node = p_new(ctx->ilist->mailbox_pool,
                     struct mailbox_list_index_node, 1);
        node->flags = MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
                MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS;
-       /* we don't bother doing name deduplication here, even though it would
-          be possible. */
-       node->raw_name = dup_name = p_strdup(ctx->ilist->mailbox_pool, name);
        node->name_id = ++ctx->ilist->highest_name_id;
        node->uid = ctx->next_uid++;
 
@@ -83,6 +76,24 @@ mailbox_list_index_node_add(struct mailbox_list_index_sync_context *ctx,
        }
        hash_table_insert(ctx->ilist->mailbox_hash,
                          POINTER_CAST(node->uid), node);
+       return node;
+}
+
+static struct mailbox_list_index_node *
+mailbox_list_index_node_add(struct mailbox_list_index_sync_context *ctx,
+                           struct mailbox_list_index_node *parent,
+                           const char *name, uint32_t *seq_r)
+{
+       struct mailbox_list_index_node *node =
+               mailbox_list_index_node_add_common(ctx, parent);
+
+       char *dup_name;
+       mailbox_list_name_unescape(&name,
+               ctx->list->mail_set->mailbox_list_storage_escape_char[0]);
+
+       /* we don't bother doing name deduplication here, even though it would
+          be possible. */
+       node->raw_name = dup_name = p_strdup(ctx->ilist->mailbox_pool, name);
        hash_table_insert(ctx->ilist->mailbox_names,
                          POINTER_CAST(node->name_id), dup_name);
 
@@ -90,6 +101,18 @@ mailbox_list_index_node_add(struct mailbox_list_index_sync_context *ctx,
        return node;
 }
 
+static struct mailbox_list_index_node *
+mailbox_list_index_node_add_inbox_inbox(struct mailbox_list_index_sync_context *ctx,
+                                       uint32_t *seq_r)
+{
+       struct mailbox_list_index_node *node =
+               mailbox_list_index_node_add_common(ctx, NULL);
+       node->raw_name = ctx->ilist->raw_inbox_inbox_name_ptr;
+       ctx->ilist->inbox_inbox_name_id = node->name_id;
+       node_add_to_index(ctx, node, seq_r);
+       return node;
+}
+
 uint32_t mailbox_list_index_sync_name(struct mailbox_list_index_sync_context *ctx,
                                      const char *name,
                                      struct mailbox_list_index_node **node_r,
@@ -100,6 +123,8 @@ uint32_t mailbox_list_index_sync_name(struct mailbox_list_index_sync_context *ct
        unsigned int i;
        uint32_t seq = 0;
 
+       *created_r = FALSE;
+
        path = *name == '\0' ? empty_path :
                t_strsplit(name, ctx->sep);
        /* find the last node that exists in the path */
@@ -120,9 +145,13 @@ uint32_t mailbox_list_index_sync_name(struct mailbox_list_index_sync_context *ct
                i_assert(node != NULL);
                if (!mail_index_lookup_seq(ctx->view, node->uid, &seq))
                        i_panic("mailbox list index: lost uid=%u", node->uid);
-               *created_r = FALSE;
        } else {
                /* create missing parts of the path */
+               if (i == 0 && ctx->ilist->inbox_inbox_storage_name != NULL &&
+                   strcmp(path[0], ctx->ilist->inbox_inbox_storage_name) == 0) {
+                       node = mailbox_list_index_node_add_inbox_inbox(ctx, &seq);
+                       i++;
+               }
                for (; path[i] != NULL; i++) {
                        node = mailbox_list_index_node_add(ctx, node, path[i],
                                                           &seq);
@@ -165,11 +194,17 @@ mailbox_list_index_sync_names(struct mailbox_list_index_sync_context *ctx)
        buffer_append_zero(hdr_buf, sizeof(struct mailbox_list_index_header));
 
        /* add existing names to header (with deduplication) */
+       struct mailbox_list_index_header2 hdr2 = { 0, };
        array_foreach_elem(&existing_name_ids, id) {
                if (id != prev_id) {
                        buffer_append(hdr_buf, &id, sizeof(id));
-                       name = hash_table_lookup(ilist->mailbox_names,
-                                                POINTER_CAST(id));
+                       if (id != ilist->inbox_inbox_name_id) {
+                               name = hash_table_lookup(ilist->mailbox_names,
+                                                        POINTER_CAST(id));
+                       } else {
+                               name = ilist->raw_inbox_inbox_name_ptr;
+                               hdr2.inbox_inbox_name_id = id;
+                       }
                        i_assert(name != NULL);
                        buffer_append(hdr_buf, name, strlen(name) + 1);
                        prev_id = id;
@@ -188,6 +223,12 @@ mailbox_list_index_sync_names(struct mailbox_list_index_sync_context *ctx)
        }
        mail_index_update_header_ext(ctx->trans, ilist->ext_id,
                                     0, hdr_buf->data, hdr_buf->used);
+       /* Update ext2 header if it has changed. Note that lib-index doesn't
+          currently support dropping the header entirely. */
+       if (hdr2.inbox_inbox_name_id != 0 || ilist->inbox_inbox_name_id != 0) {
+               mail_index_update_header_ext(ctx->trans, ilist->ext2_id,
+                                            0, &hdr2, sizeof(hdr2));
+       }
        buffer_free(&hdr_buf);
        array_free(&existing_name_ids);
 }
index e575d4cfb62e760f625cea9709eb24f1bed9a7d5..cc611b81f46a731302e3cfd780cd4f59aa6c9957 100644 (file)
@@ -122,11 +122,23 @@ mailbox_list_index_node_find_sibling(struct mailbox_list *list,
                                     struct mailbox_list_index_node *node,
                                     const char *name)
 {
+       struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+       if (ilist->inbox_inbox_storage_name != NULL &&
+           strcmp(name, ilist->inbox_inbox_storage_name) == 0) {
+               for (; node != NULL; node = node->next) {
+                       if (node->name_id == ilist->inbox_inbox_name_id)
+                               return node;
+               }
+               return NULL;
+       }
+
        mailbox_list_name_unescape(&name,
                list->mail_set->mailbox_list_storage_escape_char[0]);
 
        while (node != NULL) {
-               if (strcmp(node->raw_name, name) == 0)
+               if (node->raw_name != ilist->raw_inbox_inbox_name_ptr &&
+                   strcmp(node->raw_name, name) == 0)
                        return node;
                node = node->next;
        }
@@ -177,12 +189,16 @@ void mailbox_list_get_escaped_mailbox_name(struct mailbox_list *list,
                                           const struct mailbox_list_index_node *node,
                                           string_t *escaped_name)
 {
+       struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
        const char escape_chars[] = {
                list->mail_set->mailbox_list_storage_escape_char[0],
                mailbox_list_get_hierarchy_sep(list),
                '\0'
        };
-       mailbox_list_name_escape(node->raw_name, escape_chars, escaped_name);
+       if (node->raw_name != ilist->raw_inbox_inbox_name_ptr)
+               mailbox_list_name_escape(node->raw_name, escape_chars, escaped_name);
+       else
+               str_append(escaped_name, ilist->inbox_inbox_storage_name);
 }
 
 void mailbox_list_index_node_get_path(struct mailbox_list *list,
@@ -219,6 +235,14 @@ static int mailbox_list_index_parse_header(struct mailbox_list_index *ilist,
        char *name;
        int ret = 0;
 
+       mail_index_map_get_header_ext(view, view->map, ilist->ext2_id, &data, &size);
+       if (size >= sizeof(struct mailbox_list_index_header2)) {
+               const struct mailbox_list_index_header2 *hdr2 = data;
+               ilist->inbox_inbox_name_id = hdr2->inbox_inbox_name_id;
+       } else {
+               ilist->inbox_inbox_name_id = 0;
+       }
+
        mail_index_map_get_header_ext(view, view->map, ilist->ext_id, &data, &size);
        if (size == 0)
                return 0;
@@ -256,7 +280,10 @@ static int mailbox_list_index_parse_header(struct mailbox_list_index *ilist,
                i += len + 1;
 
                /* add id => name to hash table */
-               hash_table_insert(ilist->mailbox_names, POINTER_CAST(id), name);
+               if (id != ilist->inbox_inbox_name_id) {
+                       hash_table_insert(ilist->mailbox_names,
+                                         POINTER_CAST(id), name);
+               }
                ilist->highest_name_id = id;
        }
        i_assert(i == size);
@@ -288,6 +315,8 @@ mailbox_list_index_generate_name(struct mailbox_list_index *ilist,
 static int mailbox_list_index_node_cmp(const struct mailbox_list_index_node *n1,
                                       const struct mailbox_list_index_node *n2)
 {
+       /* Used only for duplicate checking, which skips raw_inbox_inbox_name_ptr
+          mailbox, so we don't need to check for that. */
        return  n1->parent == n2->parent &&
                strcmp(n1->raw_name, n2->raw_name) == 0 ? 0 : -1;
 }
@@ -360,9 +389,12 @@ static int mailbox_list_index_parse_records(struct mailbox_list_index *ilist,
                        /* invalid name_id - assign a new one */
                        node->name_id = ++ilist->highest_name_id;
                        node->corrupted_ext = TRUE;
-               }
-               node->raw_name = hash_table_lookup(ilist->mailbox_names,
+               } else if (irec->name_id != ilist->inbox_inbox_name_id) {
+                       node->raw_name = hash_table_lookup(ilist->mailbox_names,
                                               POINTER_CAST(irec->name_id));
+               } else {
+                       node->raw_name = ilist->raw_inbox_inbox_name_ptr;
+               }
                if (node->raw_name == NULL) {
                        *error_r = t_strdup_printf(
                                "name_id=%u not in index header", irec->name_id);
@@ -457,7 +489,12 @@ static int mailbox_list_index_parse_records(struct mailbox_list_index *ilist,
                } else if (strcasecmp(node->raw_name, "INBOX") == 0) {
                        ilist->rebuild_on_missing_inbox = FALSE;
                }
-               if (hash_table_lookup(duplicate_hash, node) == NULL)
+
+               if (node->raw_name == ilist->raw_inbox_inbox_name_ptr) {
+                       /* The raw_name's value may exist here for another
+                          mailbox, but it's not a duplicate of this
+                          <prefix>/INBOX, since it's unique. */
+               } else if (hash_table_lookup(duplicate_hash, node) == NULL)
                        hash_table_insert(duplicate_hash, node, node);
                else {
                        const char *old_name = node->raw_name;
@@ -1121,6 +1158,12 @@ static void mailbox_list_index_created(struct mailbox_list *list)
        list->vlast = &ilist->module_ctx.super;
        ilist->has_backing_store = has_backing_store;
        ilist->pending_init = TRUE;
+       /* Make sure this gets a unique pointer, and the value is not "INBOX" */
+       ilist->raw_inbox_inbox_name_ptr = p_strdup(list->pool, "INBOXINBOX");
+
+       const char escape_char = list->mail_set->mailbox_list_storage_escape_char[0];
+       ilist->inbox_inbox_storage_name = escape_char == '\0' ? NULL :
+               p_strdup_printf(list->pool, "%c49NBOX", escape_char);
 
        v->deinit = mailbox_list_index_deinit;
        v->iter_init = mailbox_list_index_iter_init;
@@ -1177,6 +1220,9 @@ static void mailbox_list_index_init_finish(struct mailbox_list *list)
                                sizeof(struct mailbox_list_index_header),
                                sizeof(struct mailbox_list_index_record),
                                sizeof(uint32_t));
+       ilist->ext2_id = mail_index_ext_register(ilist->index, "list2",
+                               sizeof(struct mailbox_list_index_header2),
+                               0, 0);
        ilist->subs_hdr_ext_id = mail_index_ext_register(ilist->index, "subs",
                                                         sizeof(uint32_t), 0,
                                                         sizeof(uint32_t));
index 16680e9ff298b3c8c4ae56460a333df81259675a..5c76c38365484d93e1c9c01eb58c1811dbc254ae 100644 (file)
@@ -62,6 +62,15 @@ struct mailbox_list_index_header {
        /* array of { uint32_t id; char name[]; } */
 };
 
+struct mailbox_list_index_header2 {
+       /* ID number in mailbox_list_index_header which should be considered
+          as the <prefix>/INBOX mailbox, which would be escaped as
+          <escape char>49NBOX to differentiate it from the actual INBOX.
+          The name in the header can be anything - it would be used only by
+          old Dovecot versions. */
+       uint32_t inbox_inbox_name_id;
+};
+
 struct mailbox_list_index_record {
        /* points to given id in header */
        uint32_t name_id;
@@ -93,6 +102,11 @@ struct mailbox_list_index_node {
        bool corrupted_ext;
        /* flags are corrupted on disk - need to update it */
        bool corrupted_flags;
+       /* Raw (unescaped) mailbox name. Unfortunately for now, it may be
+          either in UTF8 or mUTF-7 depending on the mailbox_list_utf8 setting.
+
+          As a special case, if raw_name points to ilist->inbox_inbox_name,
+          it's actually the <ns prefix>/INBOX mailbox (not INBOX). */
        const char *raw_name;
 };
 
@@ -101,7 +115,7 @@ struct mailbox_list_index {
 
        const char *path;
        struct mail_index *index;
-       uint32_t ext_id, msgs_ext_id, hmodseq_ext_id, subs_hdr_ext_id;
+       uint32_t ext_id, ext2_id, msgs_ext_id, hmodseq_ext_id, subs_hdr_ext_id;
        uint32_t vsize_ext_id, first_saved_ext_id, deleted_count_id;
        struct timeval last_refresh_timeval;
 
@@ -116,6 +130,17 @@ struct mailbox_list_index {
        uint32_t sync_stamp;
        struct timeout *to_refresh;
 
+       /* If mailbox_list_index_node.raw_name contains this pointer, it's
+          the <ns prefix>/INBOX (not INBOX). Note: Compare this against the
+          raw_name pointer directly - not the string content. */
+       void *raw_inbox_inbox_name_ptr;
+       /* <escape char>49NBOX, if mailbox_list_storage_escape_char is specified.
+          Otherwise NULL. */
+       const char *inbox_inbox_storage_name;
+       /* If mailbox_list_index_record.name_id contains this (non-0) ID, it's
+          the <ns prefix>/INBOX (not INBOX). */
+       uint32_t inbox_inbox_name_id;
+
        /* uint32_t uid => node */
        HASH_TABLE(void *, struct mailbox_list_index_node *) mailbox_hash;
        struct mailbox_list_index_node *mailbox_tree;