mailbox-list-index.c revision 51b979b6414b940f04677a7e2d064be119345954
98N/A/* Copyright (C) 2006 Timo Sirainen */
98N/A
98N/A#include "lib.h"
98N/A#include "array.h"
1088N/A#include "crc32.h"
98N/A#include "ioloop.h"
98N/A#include "str.h"
919N/A#include "file-cache.h"
919N/A#include "file-dotlock.h"
919N/A#include "mmap-util.h"
919N/A#include "write-full.h"
919N/A#include "nfs-workarounds.h"
919N/A#include "mail-index-private.h"
919N/A#include "mailbox-list-index-private.h"
919N/A
919N/A#include <stdlib.h>
919N/A#include <unistd.h>
919N/A#include <fcntl.h>
919N/A#include <sys/stat.h>
919N/A
919N/Astruct mailbox_list_iter_path {
919N/A const struct mailbox_list_dir_record *dir;
919N/A unsigned int pos;
919N/A unsigned int name_path_len;
98N/A};
98N/A
98N/Astruct mailbox_list_index_view {
98N/A struct mailbox_list_index *index;
98N/A struct mail_index_view *mail_view;
98N/A};
493N/A
493N/Astruct mailbox_list_iter_ctx {
98N/A struct mailbox_list_index_view *view;
911N/A unsigned int recurse_level;
1179N/A
1179N/A struct mailbox_list_iter_path cur;
1179N/A ARRAY_DEFINE(path, struct mailbox_list_iter_path);
911N/A string_t *name_path;
98N/A
235N/A unsigned int failed:1;
493N/A};
98N/A
916N/Aconst struct dotlock_settings default_dotlock_set = {
916N/A MEMBER(temp_prefix) NULL,
916N/A MEMBER(lock_suffix) NULL,
916N/A
970N/A MEMBER(timeout) 60,
970N/A MEMBER(stale_timeout) 30,
970N/A
970N/A MEMBER(callback) NULL,
970N/A MEMBER(context) NULL,
970N/A
970N/A MEMBER(use_excl_lock) FALSE
970N/A};
970N/A
970N/Aint mailbox_list_index_set_syscall_error(struct mailbox_list_index *index,
970N/A const char *function)
970N/A{
970N/A i_error("%s failed with file %s: %m", index->filepath, function);
970N/A return -1;
970N/A}
970N/A
970N/Astatic void mailbox_list_index_unmap(struct mailbox_list_index *index)
970N/A{
970N/A if (index->mail_index->nfs_flush && index->fd != -1) {
970N/A nfs_flush_read_cache(index->filepath, index->fd,
970N/A F_UNLCK, FALSE);
970N/A }
970N/A
970N/A if (index->file_cache != NULL)
970N/A file_cache_invalidate(index->file_cache, 0, (uoff_t)-1);
1029N/A
1029N/A if (index->mmap_base != NULL) {
1124N/A if (munmap(index->mmap_base, index->mmap_size) < 0)
1124N/A mailbox_list_index_set_syscall_error(index, "munmap()");
1124N/A index->mmap_base = NULL;
1124N/A }
1124N/A index->const_mmap_base = NULL;
1124N/A index->mmap_size = 0;
970N/A
98N/A index->hdr = NULL;
98N/A}
1124N/A
1124N/Avoid mailbox_list_index_file_close(struct mailbox_list_index *index)
1124N/A{
1124N/A mailbox_list_index_unmap(index);
1124N/A
1124N/A if (index->file_cache != NULL)
1124N/A file_cache_free(&index->file_cache);
1124N/A if (index->fd != -1) {
98N/A if (close(index->fd) < 0)
493N/A mailbox_list_index_set_syscall_error(index, "close()");
98N/A index->fd = -1;
970N/A }
970N/A}
970N/A
1026N/Aint mailbox_list_index_set_corrupted(struct mailbox_list_index *index,
970N/A const char *str)
970N/A{
1026N/A (void)unlink(index->filepath);
1124N/A mailbox_list_index_file_close(index);
1003N/A
98N/A i_error("Corrupted mailbox list index file %s: %s",
98N/A index->filepath, str);
98N/A return -1;
908N/A}
591N/A
493N/Astatic int
493N/Amailbox_list_index_check_header(struct mailbox_list_index *index,
493N/A const struct mailbox_list_index_header *hdr)
493N/A{
493N/A if (hdr->major_version != MAILBOX_LIST_INDEX_MAJOR_VERSION)
493N/A return -1;
493N/A
493N/A if (hdr->header_size < sizeof(*hdr)) {
1026N/A return mailbox_list_index_set_corrupted(index,
705N/A "header_size is too small");
493N/A }
557N/A if (hdr->header_size > index->mmap_size) {
1176N/A return mailbox_list_index_set_corrupted(index,
493N/A "header_size is too large");
965N/A }
965N/A
965N/A if (hdr->uid_validity == 0) {
606N/A return mailbox_list_index_set_corrupted(index,
606N/A "uid_validity is 0");
851N/A }
851N/A if (hdr->next_uid == 0)
851N/A return mailbox_list_index_set_corrupted(index, "next_uid is 0");
851N/A
851N/A if (index->mail_index->map == NULL) {
851N/A /* index already marked as corrupted */
851N/A return -1;
98N/A }
591N/A
963N/A if (hdr->uid_validity != index->mail_index->map->hdr.uid_validity &&
963N/A index->mail_index->map->hdr.uid_validity != 0) {
910N/A mail_index_set_error(index->mail_index,
1097N/A "uid_validity changed in file %s: %u -> %u",
910N/A index->filepath,
1097N/A index->mail_index->map->hdr.uid_validity,
910N/A hdr->uid_validity);
98N/A mail_index_mark_corrupted(index->mail_index);
98N/A return -1;
910N/A }
910N/A
970N/A return 0;
1030N/A}
1030N/A
970N/Aint mailbox_list_index_map(struct mailbox_list_index *index)
970N/A{
970N/A const struct mailbox_list_index_header *hdr;
1097N/A ssize_t ret;
1097N/A
1030N/A mailbox_list_index_unmap(index);
1030N/A
910N/A if (!index->mmap_disable) {
98N/A index->mmap_base = mmap_rw_file(index->fd, &index->mmap_size);
1183N/A if (index->mmap_base == MAP_FAILED) {
1183N/A index->mmap_base = NULL;
1183N/A mailbox_list_index_set_syscall_error(index, "mmap()");
1183N/A return -1;
1183N/A }
1183N/A
1183N/A index->const_mmap_base = index->mmap_base;
606N/A } else {
98N/A if (index->file_cache == NULL)
606N/A index->file_cache = file_cache_new(index->fd);
98N/A
591N/A ret = file_cache_read(index->file_cache, 0, SSIZE_T_MAX);
851N/A if (ret < 0) {
111N/A mailbox_list_index_set_syscall_error(index,
606N/A "file_cache_read()");
1088N/A return -1;
1088N/A }
1088N/A index->const_mmap_base = file_cache_get_map(index->file_cache,
733N/A &index->mmap_size);
970N/A }
733N/A
733N/A if (index->mmap_size < sizeof(*hdr)) {
963N/A mailbox_list_index_set_corrupted(index, "File too small");
851N/A index->const_mmap_base = NULL;
963N/A return 0;
1088N/A }
1088N/A
1088N/A hdr = index->const_mmap_base;
1088N/A if (mailbox_list_index_check_header(index, hdr) < 0) {
98N/A index->const_mmap_base = NULL;
851N/A return 0;
1097N/A }
1097N/A
851N/A index->hdr = hdr;
935N/A return 1;
851N/A}
851N/A
935N/Astatic int mailbox_list_index_map_area(struct mailbox_list_index *index,
606N/A uoff_t offset, size_t size)
935N/A{
935N/A if (offset < index->mmap_size && size <= index->mmap_size - offset)
935N/A return 1;
935N/A
212N/A if (mailbox_list_index_map(index) <= 0)
1183N/A return -1;
1183N/A
1183N/A if (offset < index->mmap_size && size <= index->mmap_size - offset)
1183N/A return 1;
1183N/A /* outside the file */
1183N/A return 0;
1183N/A}
1183N/A
1183N/Astatic void
1183N/Amailbox_list_index_init_header(struct mailbox_list_index_header *hdr,
1183N/A uint32_t uid_validity)
1183N/A{
98N/A memset(hdr, 0, sizeof(*hdr));
98N/A hdr->major_version = MAILBOX_LIST_INDEX_MAJOR_VERSION;
1183N/A hdr->minor_version = MAILBOX_LIST_INDEX_MINOR_VERSION;
1183N/A
1183N/A hdr->header_size = sizeof(*hdr);
908N/A hdr->used_space = hdr->header_size;
963N/A
963N/A hdr->uid_validity = uid_validity;
963N/A hdr->next_uid = 1;
963N/A}
963N/A
963N/Astatic int mailbox_list_index_is_recreated(struct mailbox_list_index *index)
1030N/A{
970N/A struct stat st1, st2;
1097N/A
1097N/A if (index->fd == -1)
970N/A return 1;
970N/A
970N/A if (index->mail_index->nfs_flush)
970N/A nfs_flush_attr_cache(index->filepath);
970N/A
970N/A if (nfs_safe_stat(index->filepath, &st1) < 0) {
1152N/A if (errno == ENOENT)
970N/A return 1;
970N/A
970N/A mailbox_list_index_set_syscall_error(index, "stat()");
970N/A return -1;
1026N/A }
974N/A if (fstat(index->fd, &st2) < 0) {
970N/A mailbox_list_index_set_syscall_error(index, "fstat()");
970N/A return -1;
1026N/A }
970N/A
970N/A return st1.st_ino != st2.st_ino ||
974N/A !CMP_DEV_T(st1.st_dev, st2.st_dev);
974N/A}
970N/A
1026N/Aint mailbox_list_index_file_create(struct mailbox_list_index *index,
970N/A uint32_t uid_validity)
970N/A{
970N/A struct mailbox_list_index_header hdr;
970N/A struct dotlock *dotlock;
970N/A int fd, ret;
970N/A
970N/A fd = file_dotlock_open(&index->dotlock_set, index->filepath,
970N/A 0, &dotlock);
970N/A if (fd == -1) {
970N/A mailbox_list_index_set_syscall_error(index,
970N/A "file_dotlock_open()");
970N/A return -1;
970N/A }
970N/A
970N/A if (index->fd != -1) {
970N/A /* if the file has been recreated by someone else,
1003N/A retry opening it */
1003N/A ret = mailbox_list_index_is_recreated(index);
1003N/A if (ret != 0) {
1003N/A (void)file_dotlock_delete(&dotlock);
1003N/A return ret < 0 ? -1 : 0;
1003N/A }
970N/A }
970N/A
970N/A mailbox_list_index_init_header(&hdr, uid_validity);
970N/A if (write_full(fd, &hdr, sizeof(hdr)) < 0) {
970N/A mailbox_list_index_set_syscall_error(index, "write_full()");
970N/A (void)file_dotlock_delete(&dotlock);
970N/A return -1;
970N/A }
970N/A
970N/A if (index->mail_index->nfs_flush && fdatasync(fd) < 0) {
970N/A mailbox_list_index_set_syscall_error(index, "fdatasync()");
970N/A (void)file_dotlock_delete(&dotlock);
970N/A return -1;
1030N/A }
1030N/A
1030N/A if (file_dotlock_replace(&dotlock,
1030N/A DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) < 0) {
1030N/A mailbox_list_index_set_syscall_error(index,
1030N/A "file_dotlock_replace()");
1030N/A (void)close(fd);
1030N/A return -1;
1030N/A }
1030N/A
1030N/A if (index->fd != -1)
1030N/A mailbox_list_index_file_close(index);
1030N/A index->fd = fd;
1030N/A
1030N/A ret = mailbox_list_index_map(index);
1030N/A if (ret == 0) {
1030N/A i_error("Self-created mailbox list index file %s was corrupted",
1030N/A index->filepath);
1030N/A return -1;
1030N/A }
1030N/A return ret;
1030N/A}
1030N/A
1030N/Astatic int
1192N/Amailbox_list_index_file_try_open_or_create(struct mailbox_list_index *index)
1030N/A{
1030N/A int ret;
1030N/A
1030N/A i_assert(index->fd == -1);
1030N/A
1030N/A index->fd = open(index->filepath, O_RDWR);
1030N/A if (index->fd == -1) {
1030N/A if (errno != ENOENT) {
1030N/A mailbox_list_index_set_syscall_error(index, "open()");
1030N/A return -1;
1030N/A }
1030N/A } else {
1030N/A ret = mailbox_list_index_map(index);
1030N/A if (ret != 0) {
1030N/A if (ret < 0)
1030N/A mailbox_list_index_file_close(index);
1030N/A return ret;
1030N/A }
1030N/A }
1030N/A
1030N/A ret = mailbox_list_index_file_create(index, ioloop_time);
1030N/A if (ret <= 0)
1030N/A mailbox_list_index_file_close(index);
1030N/A return ret;
1030N/A}
1030N/A
int mailbox_list_index_open_or_create(struct mailbox_list_index *index)
{
int ret;
while ((ret = mailbox_list_index_file_try_open_or_create(index)) == 0) {
/* file was recreated by someone else, try reopening */
}
return ret < 0 ? -1 : 0;
}
struct mailbox_list_index *
mailbox_list_index_alloc(const char *path, char separator,
struct mail_index *mail_index)
{
struct mailbox_list_index *index;
index = i_new(struct mailbox_list_index, 1);
index->filepath = i_strdup(path);
index->separator = separator;
index->mail_index = mail_index;
index->fd = -1;
index->mmap_disable = mail_index->mmap_disable;
index->dotlock_set = default_dotlock_set;
index->dotlock_set.use_excl_lock = mail_index->use_excl_dotlocks;
return index;
}
void mailbox_list_index_free(struct mailbox_list_index **_index)
{
struct mailbox_list_index *index = *_index;
*_index = NULL;
mailbox_list_index_file_close(index);
i_free(index->filepath);
i_free(index);
}
struct mailbox_list_index_lookup_key {
uint32_t name_hash;
struct mailbox_list_index *index;
const char *name;
bool *failed;
};
static int
mailbox_list_get_name(struct mailbox_list_index *index, pool_t pool,
const struct mailbox_list_record *rec,
const char **name_r)
{
size_t max_len;
const char *name;
if (rec->name_offset >= index->mmap_size) {
mailbox_list_index_set_corrupted(index,
"record name_offset points outside file");
return -1;
}
max_len = index->mmap_size - rec->name_offset;
name = CONST_PTR_OFFSET(index->const_mmap_base, rec->name_offset);
/* get name length. don't bother checking if it's not NUL-terminated,
because practically it always is even if the file is corrupted.
just make sure we don't crash if it happens. */
*name_r = p_strndup(pool, name, max_len);
return 0;
}
static int mailbox_list_record_cmp(const void *_key, const void *_rec)
{
const struct mailbox_list_index_lookup_key *key = _key;
const struct mailbox_list_record *rec = _rec;
const char *name;
int ret;
if (key->name_hash < rec->name_hash)
return -1;
if (key->name_hash > rec->name_hash)
return 1;
t_push();
if (mailbox_list_get_name(key->index, unsafe_data_stack_pool,
rec, &name) < 0) {
*key->failed = TRUE;
ret = -1;
} else {
ret = strcmp(key->name, name);
}
t_pop();
return ret;
}
int mailbox_list_index_get_dir(struct mailbox_list_index_view *view,
uint32_t *offset,
const struct mailbox_list_dir_record **dir_r)
{
struct mailbox_list_index *index = view->index;
const struct mailbox_list_dir_record *dir;
uint32_t next_offset, view_sync_offset, cur_offset = *offset;
int ret;
i_assert(index->mmap_size > 0);
if (view->mail_view == NULL)
view_sync_offset = (uint32_t)-1;
else {
const struct mail_index_header *hdr =
mail_index_get_header(view->mail_view);
view_sync_offset = hdr->sync_size;
i_assert(*offset < view_sync_offset);
}
do {
ret = mailbox_list_index_map_area(index, cur_offset,
sizeof(*dir));
if (ret <= 0) {
if (ret < 0)
return -1;
return mailbox_list_index_set_corrupted(index,
"dir_offset points outside file");
}
if ((cur_offset % 4) != 0) {
return mailbox_list_index_set_corrupted(index,
"dir_offset not 32bit aligned");
}
dir = CONST_PTR_OFFSET(index->const_mmap_base, cur_offset);
next_offset = mail_index_offset_to_uint32(dir->next_offset);
if (next_offset != 0 && next_offset <= cur_offset) {
return mailbox_list_index_set_corrupted(index,
"next_offset points backwards");
}
if (dir->dir_size < sizeof(*dir) +
dir->count * sizeof(struct mailbox_list_record)) {
return mailbox_list_index_set_corrupted(index,
"dir_size is smaller than record count");
}
if (next_offset >= view_sync_offset) {
/* the list index has been changed since the mail
view's last sync. don't show the changes. */
break;
}
cur_offset = next_offset;
} while (cur_offset != 0);
if (dir->count > INT_MAX/sizeof(struct mailbox_list_record)) {
mailbox_list_index_set_corrupted(index, "dir count too large");
return -1;
}
cur_offset = (const char *)dir - (const char *)index->const_mmap_base;
ret = mailbox_list_index_map_area(index, cur_offset, dir->dir_size);
if (ret <= 0) {
if (ret < 0)
return -1;
return mailbox_list_index_set_corrupted(index,
"dir points outside file");
}
*offset = cur_offset;
*dir_r = dir;
return 0;
}
int mailbox_list_index_dir_lookup_rec(struct mailbox_list_index *index,
const struct mailbox_list_dir_record *dir,
const char *name,
const struct mailbox_list_record **rec_r)
{
const struct mailbox_list_record *rec;
struct mailbox_list_index_lookup_key key;
bool failed = FALSE;
/* binary search the current hierarchy level name. the values are
sorted primarily by their hash value and secondarily by the actual
name */
memset(&key, 0, sizeof(key));
key.index = index;
key.name = name;
key.name_hash = crc32_str(name);
key.failed = &failed;
rec = bsearch(&key, MAILBOX_LIST_RECORDS(dir), dir->count, sizeof(*rec),
mailbox_list_record_cmp);
if (failed)
return -1;
if (rec == NULL)
return 0;
*rec_r = rec;
return 1;
}
static bool
mailbox_list_index_offset_exists(struct mailbox_list_index_view *view,
uint32_t offset)
{
const struct mail_index_header *hdr;
if (view->index->mmap_size <= sizeof(*view->index->hdr))
return FALSE;
if (view->mail_view == NULL)
return TRUE;
else {
hdr = mail_index_get_header(view->mail_view);
return hdr->sync_size > offset;
}
}
static int
mailbox_list_index_lookup_rec(struct mailbox_list_index_view *view,
uint32_t dir_offset, const char *name,
const struct mailbox_list_record **rec_r)
{
struct mailbox_list_index *index = view->index;
const struct mailbox_list_dir_record *dir;
const char *p, *hier_name;
int ret;
if (dir_offset == sizeof(*index->hdr) &&
!mailbox_list_index_offset_exists(view, dir_offset)) {
/* root doesn't exist in the file yet */
return 0;
}
if (mailbox_list_index_get_dir(view, &dir_offset, &dir) < 0)
return -1;
p = strchr(name, index->separator);
hier_name = p == NULL ? name : t_strdup_until(name, p);
ret = mailbox_list_index_dir_lookup_rec(index, dir, hier_name, rec_r);
if (ret <= 0)
return ret;
if (p == NULL) {
/* found it */
return 1;
}
/* recurse to children */
dir_offset = mail_index_offset_to_uint32((*rec_r)->dir_offset);
if (dir_offset == 0)
return 0;
if (!mailbox_list_index_offset_exists(view, dir_offset)) {
/* child isn't visible yet */
return 0;
}
return mailbox_list_index_lookup_rec(view, dir_offset, p + 1, rec_r);
}
int mailbox_list_index_refresh(struct mailbox_list_index *index)
{
int ret;
if ((ret = mailbox_list_index_is_recreated(index)) <= 0) {
if (ret < 0)
return -1;
if (mailbox_list_index_map(index) < 0)
ret = -1;
return ret;
}
mailbox_list_index_file_close(index);
return mailbox_list_index_open_or_create(index);
}
struct mailbox_list_index_view *
mailbox_list_index_view_init(struct mailbox_list_index *index,
struct mail_index_view *mail_view)
{
struct mailbox_list_index_view *view;
view = i_new(struct mailbox_list_index_view, 1);
view->index = index;
view->mail_view = mail_view;
return view;
}
void mailbox_list_index_view_deinit(struct mailbox_list_index_view **_view)
{
struct mailbox_list_index_view *view = *_view;
*_view = NULL;
i_free(view);
}
int mailbox_list_index_lookup(struct mailbox_list_index_view *view,
const char *name, uint32_t *uid_r)
{
const struct mailbox_list_record *rec;
uint32_t offset = sizeof(*view->index->hdr);
int ret;
ret = mailbox_list_index_lookup_rec(view, offset, name, &rec);
if (ret == 0) {
/* not found, see if it's found after a refresh */
if ((ret = mailbox_list_index_refresh(view->index)) <= 0)
return ret;
ret = mailbox_list_index_lookup_rec(view, offset, name, &rec);
}
*uid_r = ret <= 0 ? 0 : rec->uid;
return ret;
}
struct mailbox_list_iter_ctx *
mailbox_list_index_iterate_init(struct mailbox_list_index_view *view,
const char *path, int recurse_level)
{
struct mailbox_list_iter_ctx *ctx;
const struct mailbox_list_record *rec;
uint32_t offset = sizeof(*view->index->hdr);
int ret;
ctx = i_new(struct mailbox_list_iter_ctx, 1);
ctx->view = view;
ctx->recurse_level = recurse_level < 0 ? (unsigned int)-1 :
(unsigned int)recurse_level;
ctx->name_path = str_new(default_pool, 512);
if (mailbox_list_index_refresh(view->index) < 0)
ctx->failed = TRUE;
if (!ctx->failed && *path != '\0') {
ret = mailbox_list_index_lookup_rec(view, offset, path, &rec);
if (ret < 0)
ctx->failed = TRUE;
else {
offset = ret == 0 ? 0 :
mail_index_offset_to_uint32(rec->dir_offset);
}
}
if (!mailbox_list_index_offset_exists(view, offset)) {
/* root doesn't exist / offset isn't visible */
} else if (!ctx->failed && offset != 0) {
if (mailbox_list_index_get_dir(view, &offset,
&ctx->cur.dir) < 0)
ctx->failed = TRUE;
}
i_array_init(&ctx->path, I_MIN(ctx->recurse_level, 16));
return ctx;
}
int mailbox_list_index_iterate_next(struct mailbox_list_iter_ctx *ctx,
struct mailbox_list_index_info *info_r)
{
const struct mailbox_list_iter_path *cur;
const struct mailbox_list_record *recs;
const char *name;
uint32_t dir_offset;
unsigned int count;
if (ctx->failed)
return -1;
if (ctx->cur.dir == NULL) {
/* no mailboxes */
i_assert(array_count(&ctx->path) == 0);
return 0;
}
while (ctx->cur.pos == ctx->cur.dir->count) {
count = array_count(&ctx->path);
if (count == 0) {
/* we're done */
return 0;
}
/* go back to parent path */
cur = array_idx(&ctx->path, count-1);
ctx->cur = *cur;
array_delete(&ctx->path, count-1, 1);
ctx->cur.pos++;
}
recs = MAILBOX_LIST_RECORDS(ctx->cur.dir);
recs += ctx->cur.pos;
if (recs->deleted) {
ctx->cur.pos++;
return mailbox_list_index_iterate_next(ctx, info_r);
}
t_push();
if (mailbox_list_get_name(ctx->view->index, unsafe_data_stack_pool,
recs, &name) < 0) {
ctx->failed = TRUE;
t_pop();
return -1;
}
str_truncate(ctx->name_path, ctx->cur.name_path_len);
if (ctx->cur.name_path_len > 0)
str_append_c(ctx->name_path, ctx->view->index->separator);
str_append(ctx->name_path, name);
t_pop();
dir_offset = mail_index_offset_to_uint32(recs->dir_offset);
if (dir_offset != 0 &&
!mailbox_list_index_offset_exists(ctx->view, dir_offset)) {
/* child isn't visible yet */
dir_offset = 0;
}
if (dir_offset != 0 && array_count(&ctx->path) < ctx->recurse_level) {
/* recurse into children */
array_append(&ctx->path, &ctx->cur, 1);
ctx->cur.name_path_len = str_len(ctx->name_path);
ctx->cur.pos = 0;
if (mailbox_list_index_get_dir(ctx->view, &dir_offset,
&ctx->cur.dir) < 0) {
ctx->failed = TRUE;
return -1;
}
} else {
ctx->cur.pos++;
}
info_r->name = str_c(ctx->name_path);
info_r->uid = recs->uid;
info_r->has_children = dir_offset != 0;
return 1;
}
void mailbox_list_index_iterate_deinit(struct mailbox_list_iter_ctx **_ctx)
{
struct mailbox_list_iter_ctx *ctx = *_ctx;
*_ctx = NULL;
array_free(&ctx->path);
str_free(&ctx->name_path);
i_free(ctx);
}