mailbox-list-index-backend.c revision bb2471ff14f01390b47cce63b407820b5547df1a
/* Copyright (c) 2013-2014 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "hostpid.h"
#include "mail-index.h"
#include "subscription-file.h"
#include "mailbox-list-delete.h"
#include "mailbox-list-subscriptions.h"
#include "mailbox-list-index-storage.h"
#include "mailbox-list-index-sync.h"
#include <stdio.h>
#define GLOBAL_TEMP_PREFIX ".temp."
struct index_mailbox_list {
struct mailbox_list list;
const char *temp_prefix;
const char *create_mailbox_name;
guid_128_t create_mailbox_guid;
};
extern struct mailbox_list index_mailbox_list;
static struct mailbox_list *index_list_alloc(void)
{
struct index_mailbox_list *list;
pool_t pool;
pool = pool_alloconly_create("index list", 2048);
list = p_new(pool, struct index_mailbox_list, 1);
list->list = index_mailbox_list;
list->list.pool = pool;
list->temp_prefix = p_strconcat(pool, GLOBAL_TEMP_PREFIX,
my_hostname, ".", my_pid, ".", NULL);
return &list->list;
}
static int index_list_init(struct mailbox_list *_list, const char **error_r)
{
const char *dir;
if (!_list->mail_set->mailbox_list_index) {
*error_r = "LAYOUT=index requires mailbox_list_index=yes";
return -1;
}
if (mailbox_list_get_root_path(_list, MAILBOX_LIST_PATH_TYPE_INDEX, &dir) &&
mailbox_list_mkdir_root(_list, dir, MAILBOX_LIST_PATH_TYPE_INDEX) < 0) {
*error_r = t_strdup_printf("Failed to create the index root directory: %s",
mailbox_list_get_last_error(_list, NULL));
return -1;
}
return 0;
}
static void index_list_deinit(struct mailbox_list *_list)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
pool_unref(&list->list.pool);
}
static char index_list_get_hierarchy_sep(struct mailbox_list *list)
{
return *list->ns->set->separator != '\0' ? *list->ns->set->separator :
MAILBOX_LIST_INDEX_HIERARHCY_SEP;
}
static int
index_list_get_refreshed_node_seq(struct index_mailbox_list *list,
struct mail_index_view *view,
const char *name,
struct mailbox_list_index_node **node_r,
uint32_t *seq_r)
{
unsigned int i;
*node_r = NULL;
*seq_r = 0;
for (i = 0; i < 2; i++) {
*node_r = mailbox_list_index_lookup(&list->list, name);
if (*node_r == NULL)
return 0;
if (mail_index_lookup_seq(view, (*node_r)->uid, seq_r))
return 1;
/* mailbox was just expunged. refreshing should notice it. */
if (mailbox_list_index_refresh_force(&list->list) < 0)
return -1;
}
i_panic("mailbox list index: refreshing doesn't lose expunged uid=%u",
(*node_r)->uid);
return -1;
}
static const char *
index_get_guid_path(struct mailbox_list *_list, const char *root_dir,
const guid_128_t mailbox_guid)
{
return t_strdup_printf("%s/%s%s", root_dir,
_list->set.mailbox_dir_name,
guid_128_to_string(mailbox_guid));
}
static int
index_list_get_path(struct mailbox_list *_list, const char *name,
enum mailbox_list_path_type type, const char **path_r)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(_list);
struct mail_index_view *view;
struct mailbox_list_index_node *node;
struct mailbox_status status;
guid_128_t mailbox_guid;
const char *root_dir;
uint32_t seq;
int ret;
if (name == NULL) {
/* return root directories */
return mailbox_list_set_get_root_path(&_list->set, type,
path_r) ? 1 : 0;
}
/* consistently use mailbox_dir_name as part of all mailbox
directories (index/control/etc) */
switch (type) {
case MAILBOX_LIST_PATH_TYPE_MAILBOX:
type = MAILBOX_LIST_PATH_TYPE_DIR;
break;
case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
type = MAILBOX_LIST_PATH_TYPE_ALT_DIR;
break;
default:
break;
}
if (!mailbox_list_set_get_root_path(&_list->set, type, &root_dir))
return 0;
if (list->create_mailbox_name != NULL &&
strcmp(list->create_mailbox_name, name) == 0) {
*path_r = index_get_guid_path(_list, root_dir,
list->create_mailbox_guid);
return 1;
}
if (ilist->sync_ctx != NULL) {
/* we could get here during sync from
index_list_mailbox_create_selectable() */
view = ilist->sync_ctx->view;
node = mailbox_list_index_lookup(&list->list, name);
if (node == NULL) {
seq = 0;
ret = 0;
} else if (mail_index_lookup_seq(view, node->uid, &seq)) {
ret = 1;
} else {
i_panic("mailbox list index: lost uid=%u", node->uid);
}
} else {
if (mailbox_list_index_refresh(&list->list) < 0)
return -1;
view = mail_index_view_open(ilist->index);
ret = index_list_get_refreshed_node_seq(list, view, name, &node, &seq);
if (ret < 0) {
mail_index_view_close(&view);
return -1;
}
}
i_assert(ret == 0 || seq != 0);
if (ret == 0) {
mailbox_list_set_error(_list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
ret = -1;
} else if (!mailbox_list_index_status(_list, view, seq, 0,
&status, mailbox_guid) ||
guid_128_is_empty(mailbox_guid)) {
mailbox_list_set_error(_list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
ret = -1;
} else {
*path_r = index_get_guid_path(_list, root_dir, mailbox_guid);
ret = 1;
}
if (ilist->sync_ctx == NULL)
mail_index_view_close(&view);
return ret;
}
static const char *
index_list_get_temp_prefix(struct mailbox_list *_list, bool global)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
return global ? GLOBAL_TEMP_PREFIX : list->temp_prefix;
}
static int index_list_set_subscribed(struct mailbox_list *_list,
const char *name, bool set)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
const char *path;
if (_list->set.subscription_fname == NULL) {
mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
"Subscriptions not supported");
return -1;
}
path = t_strconcat(_list->set.control_dir != NULL ?
_list->set.control_dir : _list->set.root_dir,
"/", _list->set.subscription_fname, NULL);
return subsfile_set_subscribed(_list, path, list->temp_prefix,
name, set);
}
static int
index_list_node_exists(struct index_mailbox_list *list, const char *name,
enum mailbox_existence *existence_r)
{
struct mailbox_list_index_node *node;
*existence_r = MAILBOX_EXISTENCE_NONE;
if (mailbox_list_index_refresh(&list->list) < 0)
return -1;
node = mailbox_list_index_lookup(&list->list, name);
if (node == NULL)
return 0;
if ((node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
/* selectable */
*existence_r = MAILBOX_EXISTENCE_SELECT;
} else {
/* non-selectable */
*existence_r = MAILBOX_EXISTENCE_NOSELECT;
}
return 0;
}
static int
index_list_mailbox_create_dir(struct index_mailbox_list *list, const char *name)
{
struct mailbox_list_index_sync_context *sync_ctx;
struct mailbox_list_index_node *node;
uint32_t seq;
bool created;
int ret;
if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
return -1;
seq = mailbox_list_index_sync_name(sync_ctx, name, &node, &created);
if (created || (node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0) {
/* didn't already exist */
node->flags = MAILBOX_LIST_INDEX_FLAG_NOSELECT;
mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
(enum mail_flags)node->flags);
ret = 1;
} else {
/* already existed */
ret = 0;
}
if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0)
ret = -1;
return ret;
}
static int
index_list_mailbox_create_selectable(struct mailbox *box,
const guid_128_t mailbox_guid)
{
struct index_mailbox_list *list =
(struct index_mailbox_list *)box->list;
struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
struct mailbox_list_index_sync_context *sync_ctx;
struct mailbox_list_index_record rec;
struct mailbox_list_index_node *node;
const void *data;
bool expunged, created;
uint32_t seq;
if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
return -1;
seq = mailbox_list_index_sync_name(sync_ctx, box->name, &node, &created);
if (!created &&
(node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
/* already selectable */
(void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
return 0;
}
mail_index_lookup_ext(sync_ctx->view, seq, ilist->ext_id,
&data, &expunged);
i_assert(data != NULL && !expunged);
memcpy(&rec, data, sizeof(rec));
i_assert(guid_128_is_empty(rec.guid));
/* make it selectable */
node->flags = 0;
mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE, 0);
memcpy(rec.guid, mailbox_guid, sizeof(rec.guid));
mail_index_update_ext(sync_ctx->trans, seq, ilist->ext_id, &rec, NULL);
if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0) {
/* make sure we forget any changes done internally */
mailbox_list_index_reset(ilist);
return -1;
}
return 1;
}
static int
index_list_mailbox_create(struct mailbox *box,
const struct mailbox_update *update, bool directory)
{
struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
struct index_mailbox_list *list =
(struct index_mailbox_list *)box->list;
struct mailbox_update new_update;
enum mailbox_existence existence;
int ret;
/* first do a quick check that it doesn't exist */
if ((ret = index_list_node_exists(list, box->name, &existence)) < 0) {
mail_storage_copy_list_error(box->storage, box->list);
return -1;
}
if (existence == MAILBOX_EXISTENCE_NONE && directory) {
/* now add the directory to index locked */
if ((ret = index_list_mailbox_create_dir(list, box->name)) < 0) {
mail_storage_copy_list_error(box->storage, box->list);
return -1;
}
} else if (existence != MAILBOX_EXISTENCE_SELECT && !directory) {
/* if no GUID is requested, generate it ourself. set
UIDVALIDITY to index sometimes later. */
if (update == NULL)
memset(&new_update, 0, sizeof(new_update));
else
new_update = *update;
if (guid_128_is_empty(new_update.mailbox_guid))
guid_128_generate(new_update.mailbox_guid);
/* create the backend mailbox first before it exists in the
list. the mailbox creation wants to use get_path() though,
so use a bit kludgy create_mailbox_* variables during the
creation to return the path. we'll also support recursively
creating more mailboxes in here. */
const char *old_name;
guid_128_t old_guid;
old_name = list->create_mailbox_name;
guid_128_copy(old_guid, list->create_mailbox_guid);
list->create_mailbox_name = box->name;
guid_128_copy(list->create_mailbox_guid, new_update.mailbox_guid);
ret = ibox->module_ctx.super.create_box(box, &new_update, FALSE);
if (ret == 0) {
/* backend mailbox was successfully created. now add it
to the list. */
ret = index_list_mailbox_create_selectable(box, new_update.mailbox_guid);
if (ret < 0)
mail_storage_copy_list_error(box->storage, box->list);
if (ret <= 0) {
/* failed to add to list. rollback the backend
mailbox creation */
(void)mailbox_delete(box);
}
}
list->create_mailbox_name = old_name;
guid_128_copy(list->create_mailbox_guid, old_guid);
if (ret < 0)
return ret;
} else {
ret = 0;
}
if (ret == 0) {
mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
"Mailbox already exists");
return -1;
}
return 0;
}
static int
index_list_mailbox_update(struct mailbox *box,
const struct mailbox_update *update)
{
struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
const char *root_dir, *old_path, *new_path;
if (mailbox_list_get_path(box->list, box->name,
MAILBOX_LIST_PATH_TYPE_MAILBOX,
&old_path) <= 0)
old_path = NULL;
if (ibox->module_ctx.super.update_box(box, update) < 0)
return -1;
/* rename the directory */
if (!guid_128_is_empty(update->mailbox_guid) && old_path != NULL &&
mailbox_list_set_get_root_path(&box->list->set,
MAILBOX_LIST_PATH_TYPE_MAILBOX,
&root_dir)) {
new_path = index_get_guid_path(box->list, root_dir,
update->mailbox_guid);
if (strcmp(old_path, new_path) == 0)
;
else if (rename(old_path, new_path) == 0)
;
else if (errno == ENOENT) {
mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
return -1;
} else {
mail_storage_set_critical(box->storage,
"rename(%s, %s) failed: %m",
old_path, new_path);
return -1;
}
}
mailbox_list_index_update_mailbox_index(box, update);
return 0;
}
static int
index_list_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED,
enum mailbox_existence *existence_r)
{
struct index_mailbox_list *list =
(struct index_mailbox_list *)box->list;
if (index_list_node_exists(list, box->name, existence_r) < 0) {
mail_storage_copy_list_error(box->storage, box->list);
return -1;
}
return 0;
}
static void
index_list_try_delete(struct mailbox_list *_list, const char *name,
enum mailbox_list_path_type type)
{
const char *mailbox_path, *path;
if (mailbox_list_get_path(_list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
&mailbox_path) <= 0 ||
mailbox_list_get_path(_list, name, type, &path) <= 0 ||
strcmp(path, mailbox_path) == 0)
return;
if (*_list->set.maildir_name == '\0' &&
(_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
/* this directory may contain also child mailboxes' data.
we don't want to delete that. */
bool rmdir_path = *_list->set.maildir_name != '\0';
if (mailbox_list_delete_mailbox_nonrecursive(_list, name, path,
rmdir_path) < 0)
return;
} else {
if (mailbox_list_delete_trash(path) < 0 &&
errno != ENOENT && errno != ENOTEMPTY) {
mailbox_list_set_critical(_list,
"unlink_directory(%s) failed: %m", path);
}
}
/* avoid leaving empty directories lying around */
mailbox_list_delete_until_root(_list, path, type);
}
static void
index_list_delete_finish(struct mailbox_list *list, const char *name)
{
index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_INDEX);
index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_CONTROL);
index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX);
}
static int
index_list_delete_entry(struct index_mailbox_list *list, const char *name,
bool delete_selectable)
{
struct mailbox_list_index_sync_context *sync_ctx;
int ret;
if (strcmp(name, list->create_mailbox_name) == 0) {
/* we're rollbacking a failed create. if the name exists in the
list, it was done by somebody else so we don't want to
remove it. */
return 0;
}
if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
return -1;
ret = mailbox_list_index_sync_delete(sync_ctx, name, delete_selectable);
if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0)
return -1;
return ret;
}
static int
index_list_delete_mailbox(struct mailbox_list *_list, const char *name)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
const char *path;
int ret;
/* first delete the mailbox files */
ret = mailbox_list_get_path(_list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
&path);
if (ret <= 0)
return ret;
if ((_list->flags & MAILBOX_LIST_FLAG_NO_MAIL_FILES) != 0) {
ret = 0;
} else if ((_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
ret = mailbox_list_delete_mailbox_file(_list, name, path);
} else {
ret = mailbox_list_delete_mailbox_nonrecursive(_list, name,
path, TRUE);
}
if (ret == 0 || (_list->props & MAILBOX_LIST_PROP_AUTOCREATE_DIRS) != 0)
index_list_delete_finish(_list, name);
if (ret == 0) {
if (index_list_delete_entry(list, name, TRUE) < 0)
return -1;
}
return ret;
}
static int
index_list_delete_dir(struct mailbox_list *_list, const char *name)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
int ret;
if ((ret = index_list_delete_entry(list, name, FALSE)) < 0)
return -1;
if (ret == 0) {
mailbox_list_set_error(_list, MAIL_ERROR_EXISTS,
"Mailbox has children, delete them first");
return -1;
}
return 0;
}
static int
index_list_delete_symlink(struct mailbox_list *_list,
const char *name ATTR_UNUSED)
{
mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
"Symlinks not supported");
return -1;
}
static int
index_list_rename_mailbox(struct mailbox_list *_oldlist, const char *oldname,
struct mailbox_list *_newlist, const char *newname)
{
struct index_mailbox_list *list = (struct index_mailbox_list *)_oldlist;
struct mailbox_list_index_sync_context *sync_ctx;
struct mailbox_list_index_record oldrec, newrec;
struct mailbox_list_index_node *oldnode, *newnode, *child;
const void *data;
bool created, expunged;
uint32_t oldseq, newseq;
if (_oldlist != _newlist) {
mailbox_list_set_error(_oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Renaming not supported across namespaces.");
return -1;
}
if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
return -1;
oldnode = mailbox_list_index_lookup(&list->list, oldname);
if (oldnode == NULL) {
(void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
mailbox_list_set_error(&list->list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname));
return -1;
}
if (!mail_index_lookup_seq(sync_ctx->view, oldnode->uid, &oldseq))
i_panic("mailbox list index: lost uid=%u", oldnode->uid);
newseq = mailbox_list_index_sync_name(sync_ctx, newname,
&newnode, &created);
if (!created) {
(void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
mailbox_list_set_error(&list->list, MAIL_ERROR_EXISTS,
"Target mailbox already exists");
return -1;
}
i_assert(oldnode != newnode);
/* copy all the data from old node to new node */
newnode->uid = oldnode->uid;
newnode->flags = oldnode->flags;
newnode->children = oldnode->children; oldnode->children = NULL;
for (child = newnode->children; child != NULL; child = child->next)
child->parent = newnode;
/* remove the old node from existence */
mailbox_list_index_node_unlink(sync_ctx->ilist, oldnode);
/* update the old index record to contain the new name_id/parent_uid,
then expunge the added index record */
mail_index_lookup_ext(sync_ctx->view, oldseq, sync_ctx->ilist->ext_id,
&data, &expunged);
i_assert(data != NULL && !expunged);
memcpy(&oldrec, data, sizeof(oldrec));
mail_index_lookup_ext(sync_ctx->view, newseq, sync_ctx->ilist->ext_id,
&data, &expunged);
i_assert(data != NULL && !expunged);
memcpy(&newrec, data, sizeof(newrec));
oldrec.name_id = newrec.name_id;
oldrec.parent_uid = newrec.parent_uid;
mail_index_update_ext(sync_ctx->trans, oldseq,
sync_ctx->ilist->ext_id, &oldrec, NULL);
mail_index_expunge(sync_ctx->trans, newseq);
return mailbox_list_index_sync_end(&sync_ctx, TRUE);
}
static struct mailbox_list_iterate_context *
index_list_iter_init(struct mailbox_list *list,
const char *const *patterns,
enum mailbox_list_iter_flags flags)
{
struct mailbox_list_iterate_context *ctx;
pool_t pool;
if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
return mailbox_list_subscriptions_iter_init(list, patterns,
flags);
}
pool = pool_alloconly_create("mailbox list index backend iter", 1024);
ctx = p_new(pool, struct mailbox_list_iterate_context, 1);
ctx->pool = pool;
ctx->list = list;
ctx->flags = flags;
array_create(&ctx->module_contexts, pool, sizeof(void *), 5);
return ctx;
}
static const struct mailbox_info *
index_list_iter_next(struct mailbox_list_iterate_context *ctx)
{
if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
return mailbox_list_subscriptions_iter_next(ctx);
return NULL;
}
static int index_list_iter_deinit(struct mailbox_list_iterate_context *ctx)
{
if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
return mailbox_list_subscriptions_iter_deinit(ctx);
pool_unref(&ctx->pool);
return 0;
}
struct mailbox_list index_mailbox_list = {
.name = MAILBOX_LIST_NAME_INDEX,
.props = MAILBOX_LIST_PROP_NO_ROOT,
.mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
{
index_list_alloc,
index_list_init,
index_list_deinit,
NULL,
index_list_get_hierarchy_sep,
mailbox_list_default_get_vname,
mailbox_list_default_get_storage_name,
index_list_get_path,
index_list_get_temp_prefix,
NULL,
index_list_iter_init,
index_list_iter_next,
index_list_iter_deinit,
NULL,
NULL,
mailbox_list_subscriptions_refresh,
index_list_set_subscribed,
index_list_delete_mailbox,
index_list_delete_dir,
index_list_delete_symlink,
index_list_rename_mailbox,
NULL, NULL, NULL, NULL
}
};
void mailbox_list_index_backend_init_mailbox(struct mailbox *box)
{
if (strcmp(box->list->name, MAILBOX_LIST_NAME_INDEX) != 0)
return;
box->v.create_box = index_list_mailbox_create;
box->v.update_box = index_list_mailbox_update;
box->v.exists = index_list_mailbox_exists;
box->v.list_index_has_changed = NULL;
box->v.list_index_update_sync = NULL;
}