(struct local_dsync_worker *)_worker;
struct local_dsync_mailbox *lbox;
struct mailbox_list *list;
+ struct mailbox *old_box, *new_box;
const char *newname;
lbox = hash_table_lookup(worker->mailbox_hash, mailbox);
}
mailbox_list_set_changelog_timestamp(list, dsync_box->last_change);
- if (mailbox_list_rename_mailbox(list, lbox->storage_name,
- list, newname, FALSE) < 0) {
+ old_box = mailbox_alloc(list, lbox->storage_name, 0);
+ new_box = mailbox_alloc(list, newname, 0);
+ if (mailbox_rename(old_box, new_box, FALSE) < 0) {
+ struct mail_storage *storage = mailbox_get_storage(old_box);
+
i_error("Can't rename mailbox %s to %s: %s", lbox->storage_name,
- newname, mailbox_list_get_last_error(list, NULL));
+ newname, mail_storage_get_last_error(storage, NULL));
dsync_worker_set_failure(_worker);
} else {
lbox->storage_name = p_strdup(worker->pool, newname);
}
+ mailbox_free(&old_box);
+ mailbox_free(&new_box);
mailbox_list_set_changelog_timestamp(list, (time_t)-1);
}
bool cmd_rename(struct client_command_context *cmd)
{
struct mail_namespace *old_ns, *new_ns;
+ struct mailbox *old_box, *new_box;
const char *oldname, *newname;
unsigned int oldlen;
}
}
- if (mailbox_list_rename_mailbox(old_ns->list, oldname,
- new_ns->list, newname, TRUE) < 0)
- client_send_list_error(cmd, old_ns->list);
+ old_box = mailbox_alloc(old_ns->list, oldname, 0);
+ new_box = mailbox_alloc(new_ns->list, newname, 0);
+ if (mailbox_rename(old_box, new_box, TRUE) < 0)
+ client_send_storage_error(cmd, mailbox_get_storage(old_box));
else
client_send_tagline(cmd, "OK Rename completed.");
+ mailbox_free(&old_box);
+ mailbox_free(&new_box);
return TRUE;
}
cydir_mailbox_create,
index_storage_mailbox_update,
index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
NULL,
NULL,
}
}
-static const char *
-dbox_get_alt_path(struct mailbox_list *list, const char *path)
-{
- struct mail_storage *storage = list->ns->storage;
- const char *root;
- unsigned int len;
-
- if (list->set.alt_dir == NULL ||
- (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) != 0)
- return NULL;
-
- root = mailbox_list_get_path(list, NULL, MAILBOX_LIST_PATH_TYPE_DIR);
- len = strlen(root);
- if (strncmp(path, root, len) != 0 && path[len] == '/') {
- /* can't determine the alt path - shouldn't happen */
- return NULL;
- }
- return t_strconcat(list->set.alt_dir, path + len, NULL);
-}
-
int dbox_mailbox_create(struct mailbox *box,
const struct mailbox_update *update, bool directory)
{
}
return ret;
}
-
-static int
-dbox_list_rename_get_alt_paths(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname,
- enum mailbox_list_path_type path_type,
- const char **oldpath_r, const char **newpath_r)
-{
- const char *path;
-
- path = mailbox_list_get_path(oldlist, oldname, path_type);
- *oldpath_r = dbox_get_alt_path(oldlist, path);
- if (*oldpath_r == NULL)
- return 0;
-
- path = mailbox_list_get_path(newlist, newname, path_type);
- *newpath_r = dbox_get_alt_path(newlist, path);
- if (*newpath_r == NULL) {
- /* destination dbox storage doesn't have alt-path defined.
- we can't do the rename easily. */
- mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
- "Can't rename mailboxes across specified storages.");
- return -1;
- }
- return 1;
-}
-
-int dbox_list_rename_mailbox_pre(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname)
-{
- const char *alt_oldpath, *alt_newpath;
- struct stat st;
- int ret;
-
- ret = dbox_list_rename_get_alt_paths(oldlist, oldname, newlist, newname,
- MAILBOX_LIST_PATH_TYPE_DIR,
- &alt_oldpath, &alt_newpath);
- if (ret <= 0)
- return ret;
-
- if (stat(alt_newpath, &st) == 0) {
- /* race condition or a directory left there lying around?
- safest to just report error. */
- mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
- "Target mailbox already exists");
- return -1;
- } else if (errno != ENOENT) {
- mailbox_list_set_critical(oldlist, "stat(%s) failed: %m",
- alt_newpath);
- return -1;
- }
- return 0;
-}
-
-int dbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname,
- bool rename_children)
-{
- enum mailbox_list_path_type path_type;
- const char *alt_oldpath, *alt_newpath, *path;
- int ret;
-
- path_type = rename_children ? MAILBOX_LIST_PATH_TYPE_DIR :
- MAILBOX_LIST_PATH_TYPE_MAILBOX;
- ret = dbox_list_rename_get_alt_paths(oldlist, oldname, newlist, newname,
- path_type, &alt_oldpath,
- &alt_newpath);
- if (ret <= 0)
- return ret;
-
- if (rename(alt_oldpath, alt_newpath) == 0) {
- /* ok */
- if (!rename_children) {
- path = mailbox_list_get_path(oldlist, oldname,
- MAILBOX_LIST_PATH_TYPE_DIR);
- if (rmdir(path) < 0 &&
- errno != ENOENT && errno != ENOTEMPTY) {
- mailbox_list_set_critical(oldlist,
- "rmdir(%s) failed: %m", path);
- }
- }
- } else if (errno != ENOENT) {
- /* renaming is done already, so just log the error */
- mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
- alt_oldpath, alt_newpath);
- }
- return 0;
-}
const char *mailbox_name,
enum mailbox_list_file_type type,
enum mailbox_info_flags *flags);
-int dbox_list_rename_mailbox_pre(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname);
-int dbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname,
- bool rename_children);
#endif
return index_storage_mailbox_delete(box);
}
-static int
-mdbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname,
- bool rename_children)
-{
- struct mdbox_mailbox_list *oldmlist = MDBOX_LIST_CONTEXT(oldlist);
-
- if (oldmlist->module_ctx.super.
- rename_mailbox(oldlist, oldname, newlist, newname,
- rename_children) < 0)
- return -1;
- return dbox_list_rename_mailbox(oldlist, oldname, newlist, newname,
- rename_children);
-}
-
static void dbox_storage_add_list(struct mail_storage *storage ATTR_UNUSED,
struct mailbox_list *list)
{
mlist = p_new(list->pool, struct mdbox_mailbox_list, 1);
mlist->module_ctx.super = list->v;
-
list->v.iter_is_mailbox = dbox_list_iter_is_mailbox;
- list->v.rename_mailbox = mdbox_list_rename_mailbox;
- list->v.rename_mailbox_pre = dbox_list_rename_mailbox_pre;
MODULE_CONTEXT_SET(list, mdbox_mailbox_list_module, mlist);
}
dbox_mailbox_create,
mdbox_mailbox_update,
mdbox_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
mdbox_mailbox_get_guid,
NULL,
return sdbox_write_index_header(box, update);
}
-static int
-sdbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname,
- bool rename_children)
-{
- struct sdbox_mailbox_list *oldmlist = SDBOX_LIST_CONTEXT(oldlist);
-
- if (oldmlist->module_ctx.super.
- rename_mailbox(oldlist, oldname, newlist, newname,
- rename_children) < 0)
- return -1;
- return dbox_list_rename_mailbox(oldlist, oldname, newlist, newname,
- rename_children);
-}
-
static void sdbox_storage_add_list(struct mail_storage *storage ATTR_UNUSED,
struct mailbox_list *list)
{
mlist = p_new(list->pool, struct sdbox_mailbox_list, 1);
mlist->module_ctx.super = list->v;
-
list->v.iter_is_mailbox = dbox_list_iter_is_mailbox;
- list->v.rename_mailbox = sdbox_list_rename_mailbox;
- list->v.rename_mailbox_pre = dbox_list_rename_mailbox_pre;
MODULE_CONTEXT_SET(list, sdbox_mailbox_list_module, mlist);
}
dbox_mailbox_create,
dbox_mailbox_update,
index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
sdbox_mailbox_get_guid,
NULL,
return index_storage_mailbox_delete_dir(box, TRUE);
}
+int index_storage_mailbox_rename(struct mailbox *src, struct mailbox *dest,
+ bool rename_children)
+{
+ uint8_t guid[MAIL_GUID_128_SIZE];
+
+ if (src->list->v.rename_mailbox(src->list, src->name,
+ dest->list, dest->name,
+ rename_children) < 0)
+ return -1;
+
+ /* we'll track mailbox names, instead of GUIDs. We may be renaming a
+ non-selectable mailbox (directory), which doesn't even have a GUID */
+ mailbox_name_get_sha128(dest->name, guid);
+ mailbox_list_add_change(src->list, MAILBOX_LOG_RECORD_RENAME, guid);
+ return 0;
+}
+
bool index_storage_is_readonly(struct mailbox *box)
{
return (box->flags & MAILBOX_FLAG_READONLY) != 0 ||
const struct mailbox_update *update);
int index_storage_mailbox_delete(struct mailbox *box);
int index_storage_mailbox_delete_dir(struct mailbox *box, bool mailbox_deleted);
+int index_storage_mailbox_rename(struct mailbox *src, struct mailbox *dest,
+ bool rename_children);
bool index_storage_is_readonly(struct mailbox *box);
bool index_storage_allow_new_keywords(struct mailbox *box);
const struct maildir_settings *set;
};
-struct rename_context {
- bool found;
- size_t oldnamelen;
- const char *newname;
-};
-
extern struct mail_storage maildir_storage;
extern struct mailbox maildir_mailbox;
return maildir_uidlist_get_mailbox_guid(mbox->uidlist, guid);
}
-static int
-maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname,
- bool rename_children)
-{
- struct maildir_mailbox_list_context *oldmlist =
- MAILDIR_LIST_CONTEXT(oldlist);
- const char *path1, *path2;
-
- if (strcmp(oldname, "INBOX") == 0) {
- /* INBOX often exists as the root ~/Maildir.
- We can't rename it then. */
- path1 = mailbox_list_get_path(oldlist, oldname,
- MAILBOX_LIST_PATH_TYPE_MAILBOX);
- path2 = mailbox_list_get_path(oldlist, NULL,
- MAILBOX_LIST_PATH_TYPE_MAILBOX);
- if (strcmp(path1, path2) == 0) {
- mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
- "Renaming INBOX isn't supported.");
- return -1;
- }
- }
-
- return oldmlist->module_ctx.super.
- rename_mailbox(oldlist, oldname, newlist, newname,
- rename_children);
-}
-
static void maildir_mailbox_close(struct mailbox *box)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
maildir_storage_is_valid_create_name;
list->v.iter_is_mailbox = maildir_list_iter_is_mailbox;
}
- list->v.rename_mailbox = maildir_list_rename_mailbox;
MODULE_CONTEXT_SET(list, maildir_mailbox_list_module, mlist);
}
maildir_mailbox_create,
maildir_mailbox_update,
index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
maildir_mailbox_get_guid,
maildir_list_index_has_changed,
mbox_mailbox_create,
mbox_mailbox_update,
index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
mbox_mailbox_get_guid,
NULL,
raw_mailbox_create,
raw_mailbox_update,
index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
NULL,
NULL,
if (shared_list_rename_get_ns(oldlist, &oldname,
newlist, &newname, &ns) < 0)
return -1;
- ret = mailbox_list_rename_mailbox(ns->list, oldname, ns->list, newname,
- rename_children);
- if (ret < 0)
- shared_list_copy_error(oldlist, ns);
- return ret;
-}
-
-static int
-shared_list_rename_mailbox_pre(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname)
-{
- struct mail_namespace *ns;
- int ret;
- if (shared_list_rename_get_ns(oldlist, &oldname,
- newlist, &newname, &ns) < 0)
- return -1;
- ret = ns->list->v.rename_mailbox_pre(ns->list, oldname,
- ns->list, newname);
+ ret = ns->list->v.rename_mailbox(ns->list, oldname, ns->list, newname,
+ rename_children);
if (ret < 0)
shared_list_copy_error(oldlist, ns);
return ret;
shared_list_create_mailbox_dir,
shared_list_delete_mailbox,
shared_list_delete_dir,
- shared_list_rename_mailbox,
- shared_list_rename_mailbox_pre
+ shared_list_rename_mailbox
}
};
const char *newname, bool rename_children)
{
struct mail_storage *oldstorage;
- const char *oldpath, *newpath, *p, *origin;
- enum mailbox_list_path_type path_type;
+ const char *oldpath, *newpath, *alt_oldpath, *alt_newpath, *root_path;
+ const char *p, *origin;
+ enum mailbox_list_path_type path_type, alt_path_type;
struct stat st;
mode_t mode;
gid_t gid;
if (mailbox_list_get_storage(&oldlist, &oldname, &oldstorage) < 0)
return -1;
- if (rename_children)
+ if (rename_children) {
path_type = MAILBOX_LIST_PATH_TYPE_DIR;
- else if (mail_storage_is_mailbox_file(oldstorage) ||
- *oldlist->set.maildir_name != '\0')
+ alt_path_type = MAILBOX_LIST_PATH_TYPE_ALT_DIR;
+ } else if (mail_storage_is_mailbox_file(oldstorage) ||
+ *oldlist->set.maildir_name != '\0') {
path_type = MAILBOX_LIST_PATH_TYPE_MAILBOX;
- else {
+ alt_path_type = MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX;
+ } else {
/* we can't do this, our children would get renamed with us */
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Can't rename mailbox without its children.");
oldpath = mailbox_list_get_path(oldlist, oldname, path_type);
newpath = mailbox_list_get_path(newlist, newname, path_type);
+ alt_oldpath = mailbox_list_get_path(oldlist, oldname, alt_path_type);
+ alt_newpath = mailbox_list_get_path(newlist, newname, alt_path_type);
+
+ root_path = mailbox_list_get_path(oldlist, NULL,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (strcmp(oldpath, root_path) == 0) {
+ /* most likely INBOX */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ t_strdup_printf("Renaming %s isn't supported.",
+ oldname));
+ return -1;
+ }
/* create the hierarchy */
p = strrchr(newpath, '/');
return -1;
}
- if (oldlist->v.rename_mailbox_pre != NULL) {
- if (oldlist->v.rename_mailbox_pre(oldlist, oldname,
- newlist, newname) < 0)
+ if ((alt_newpath != NULL && alt_oldpath == NULL) ||
+ (alt_newpath == NULL && alt_oldpath != NULL)) {
+ /* both or neither source/dest must to have alt path defined.
+ otherwise we'd have to do the merging ourself, which would
+ be possible but a bit too much trouble for now */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailboxes across specified storages.");
+ return -1;
+ }
+
+ if (alt_newpath != NULL) {
+ if (stat(alt_newpath, &st) == 0) {
+ /* race condition or a directory left there lying around?
+ safest to just report error. */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
return -1;
+ } else if (errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "stat(%s) failed: %m",
+ alt_newpath);
+ return -1;
+ }
}
- /* NOTE: renaming INBOX works just fine with us, it's simply recreated
- the next time it's needed. */
if (rename(oldpath, newpath) < 0) {
if (ENOTFOUND(errno)) {
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND,
}
}
+ if (alt_newpath != NULL) {
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ alt_path_type, rmdir_parent);
+ }
(void)rename_dir(oldlist, oldname, newlist, newname,
MAILBOX_LIST_PATH_TYPE_CONTROL, rmdir_parent);
(void)rename_dir(oldlist, oldname, newlist, newname,
fs_list_create_mailbox_dir,
fs_list_delete_mailbox,
fs_list_delete_dir,
- fs_list_rename_mailbox,
- NULL
+ fs_list_rename_mailbox
}
};
name, set);
}
-static int rename_dir(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname,
- enum mailbox_list_path_type type)
-{
- const char *oldpath, *newpath;
-
- oldpath = mailbox_list_get_path(oldlist, oldname, type);
- newpath = mailbox_list_get_path(newlist, newname, type);
-
- if (strcmp(oldpath, newpath) == 0)
- return 0;
-
- if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
- mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
- oldpath, newpath);
- return -1;
- }
- return 0;
-}
-
-static int
-maildir_rename_children(struct mailbox_list *oldlist, const char *oldname,
- struct mailbox_list *newlist, const char *newname)
-{
- struct mailbox_list_iterate_context *iter;
- const struct mailbox_info *info;
- ARRAY_DEFINE(names_arr, const char *);
- const char *pattern, *oldpath, *newpath, *old_listname, *new_listname;
- const char *const *names;
- unsigned int i, count;
- size_t oldnamelen;
- pool_t pool;
- char old_sep;
- int ret;
-
- ret = 0;
- oldnamelen = strlen(oldname);
-
- /* first get the list of the children and save them to memory, because
- we can't rely on readdir() not skipping files while the directory
- is being modified. this doesn't protect against modifications by
- other processes though. */
- pool = pool_alloconly_create("Maildir++ children list", 1024);
- i_array_init(&names_arr, 64);
-
- old_sep = mailbox_list_get_hierarchy_sep(oldlist);
- pattern = t_strdup_printf("%s%c*", oldname, old_sep);
- iter = mailbox_list_iter_init(oldlist, pattern,
- MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
- MAILBOX_LIST_ITER_RAW_LIST);
- while ((info = mailbox_list_iter_next(iter)) != NULL) {
- const char *name;
-
- /* verify that the prefix matches, otherwise we could have
- problems with mailbox names containing '%' and '*' chars */
- if (strncmp(info->name, oldname, oldnamelen) == 0 &&
- info->name[oldnamelen] == old_sep) {
- name = p_strdup(pool, info->name + oldnamelen);
- array_append(&names_arr, &name, 1);
- }
- }
- if (mailbox_list_iter_deinit(&iter) < 0) {
- ret = -1;
- names = NULL; count = 0;
- } else {
- names = array_get(&names_arr, &count);
- }
-
- for (i = 0; i < count; i++) {
- old_listname = t_strconcat(oldname, names[i], NULL);
- if (strcmp(old_listname, newname) == 0) {
- /* When doing RENAME "a" "a.b" we see "a.b" here.
- We don't want to rename it anymore to "a.b.b". */
- continue;
- }
-
- new_listname = t_strconcat(newname, names[i], NULL);
- oldpath = mailbox_list_get_path(oldlist, old_listname,
- MAILBOX_LIST_PATH_TYPE_MAILBOX);
- newpath = mailbox_list_get_path(newlist, new_listname,
- MAILBOX_LIST_PATH_TYPE_MAILBOX);
-
- /* FIXME: it's possible to merge two mailboxes if either one of
- them doesn't have existing root mailbox. We could check this
- but I'm not sure if it's worth it. It could be even
- considered as a feature.
-
- Anyway, the bug with merging is that if both mailboxes have
- identically named child mailbox they conflict. Just ignore
- those and leave them under the old mailbox. */
- if (rename(oldpath, newpath) == 0 || EDESTDIREXISTS(errno))
- ret = 1;
- else {
- mailbox_list_set_critical(oldlist,
- "rename(%s, %s) failed: %m", oldpath, newpath);
- ret = -1;
- break;
- }
-
- (void)rename_dir(oldlist, old_listname, newlist, new_listname,
- MAILBOX_LIST_PATH_TYPE_CONTROL);
- (void)rename_dir(oldlist, old_listname, newlist, new_listname,
- MAILBOX_LIST_PATH_TYPE_INDEX);
- }
- array_free(&names_arr);
- pool_unref(&pool);
-
- return ret;
-}
-
static int
maildir_list_create_maildirfolder_file(struct mailbox_list *list,
const char *dir)
return -1;
}
+static int rename_dir(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname,
+ enum mailbox_list_path_type type)
+{
+ const char *oldpath, *newpath;
+
+ oldpath = mailbox_list_get_path(oldlist, oldname, type);
+ newpath = mailbox_list_get_path(newlist, newname, type);
+
+ if (strcmp(oldpath, newpath) == 0)
+ return 0;
+
+ if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+maildir_rename_children(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ ARRAY_DEFINE(names_arr, const char *);
+ const char *pattern, *oldpath, *newpath, *old_listname, *new_listname;
+ const char *const *names;
+ unsigned int i, count;
+ size_t oldnamelen;
+ pool_t pool;
+ char old_sep;
+ int ret;
+
+ ret = 0;
+ oldnamelen = strlen(oldname);
+
+ /* first get the list of the children and save them to memory, because
+ we can't rely on readdir() not skipping files while the directory
+ is being modified. this doesn't protect against modifications by
+ other processes though. */
+ pool = pool_alloconly_create("Maildir++ children list", 1024);
+ i_array_init(&names_arr, 64);
+
+ old_sep = mailbox_list_get_hierarchy_sep(oldlist);
+ pattern = t_strdup_printf("%s%c*", oldname, old_sep);
+ iter = mailbox_list_iter_init(oldlist, pattern,
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
+ MAILBOX_LIST_ITER_RAW_LIST);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ const char *name;
+
+ /* verify that the prefix matches, otherwise we could have
+ problems with mailbox names containing '%' and '*' chars */
+ if (strncmp(info->name, oldname, oldnamelen) == 0 &&
+ info->name[oldnamelen] == old_sep) {
+ name = p_strdup(pool, info->name + oldnamelen);
+ array_append(&names_arr, &name, 1);
+ }
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ ret = -1;
+ names = NULL; count = 0;
+ } else {
+ names = array_get(&names_arr, &count);
+ }
+
+ for (i = 0; i < count; i++) {
+ old_listname = t_strconcat(oldname, names[i], NULL);
+ if (strcmp(old_listname, newname) == 0) {
+ /* When doing RENAME "a" "a.b" we see "a.b" here.
+ We don't want to rename it anymore to "a.b.b". */
+ continue;
+ }
+
+ new_listname = t_strconcat(newname, names[i], NULL);
+ oldpath = mailbox_list_get_path(oldlist, old_listname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ newpath = mailbox_list_get_path(newlist, new_listname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+
+ /* FIXME: it's possible to merge two mailboxes if either one of
+ them doesn't have existing root mailbox. We could check this
+ but I'm not sure if it's worth it. It could be even
+ considered as a feature.
+
+ Anyway, the bug with merging is that if both mailboxes have
+ identically named child mailbox they conflict. Just ignore
+ those and leave them under the old mailbox. */
+ if (rename(oldpath, newpath) == 0 || EDESTDIREXISTS(errno))
+ ret = 1;
+ else {
+ mailbox_list_set_critical(oldlist,
+ "rename(%s, %s) failed: %m", oldpath, newpath);
+ ret = -1;
+ break;
+ }
+
+ (void)rename_dir(oldlist, old_listname, newlist, new_listname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL);
+ (void)rename_dir(oldlist, old_listname, newlist, new_listname,
+ MAILBOX_LIST_PATH_TYPE_INDEX);
+ }
+ array_free(&names_arr);
+ pool_unref(&pool);
+
+ return ret;
+}
+
static int
maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
struct mailbox_list *newlist, const char *newname,
bool rename_children)
{
- const char *oldpath, *newpath;
+ const char *oldpath, *newpath, *root_path;
int ret;
bool found;
newpath = mailbox_list_get_path(newlist, newname,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ root_path = mailbox_list_get_path(oldlist, NULL,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (strcmp(oldpath, root_path) == 0) {
+ /* most likely INBOX */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ t_strdup_printf("Renaming %s isn't supported.",
+ oldname));
+ return -1;
+ }
+
ret = rename(oldpath, newpath);
if (ret == 0 || errno == ENOENT) {
(void)rename_dir(oldlist, oldname, newlist, newname,
.name = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS,
.hierarchy_sep = '.',
.props = MAILBOX_LIST_PROP_NO_MAILDIR_NAME |
+ MAILBOX_LIST_PROP_NO_ALT_DIR |
MAILBOX_LIST_PROP_NO_NOSELECT,
.mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
maildir_list_create_mailbox_dir,
maildir_list_delete_mailbox,
maildir_list_delete_dir,
- maildir_list_rename_mailbox,
- NULL
+ maildir_list_rename_mailbox
}
};
.name = MAILBOX_LIST_NAME_IMAPDIR,
.hierarchy_sep = '.',
.props = MAILBOX_LIST_PROP_NO_MAILDIR_NAME |
+ MAILBOX_LIST_PROP_NO_ALT_DIR |
MAILBOX_LIST_PROP_NO_NOSELECT,
.mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
maildir_list_create_mailbox_dir,
maildir_list_delete_mailbox,
maildir_list_delete_dir,
- maildir_list_rename_mailbox,
- NULL
+ maildir_list_rename_mailbox
}
};
bool directory);
int (*update)(struct mailbox *box, const struct mailbox_update *update);
int (*delete)(struct mailbox *box);
+ int (*rename)(struct mailbox *src, struct mailbox *dest,
+ bool rename_children);
void (*get_status)(struct mailbox *box, enum mailbox_status_items items,
struct mailbox_status *status_r);
if (mail_storage_is_mailbox_file(storage_class))
list_flags |= MAILBOX_LIST_FLAG_MAILBOX_FILES;
if (mailbox_list_create(list_set.layout, ns, &list_set,
- list_flags, error_r) < 0)
+ list_flags, error_r) < 0) {
+ *error_r = t_strdup_printf("Mailbox list driver %s: %s",
+ list_set.layout, *error_r);
return -1;
+ }
if (mail_storage_create_root(ns->list, flags, error_r) < 0)
return -1;
}
return ret;
}
+static bool nullequals(const void *p1, const void *p2)
+{
+ return (p1 == NULL && p2 == NULL) || (p1 != NULL && p2 != NULL);
+}
+
+int mailbox_rename(struct mailbox *src, struct mailbox *dest,
+ bool rename_children)
+{
+ if (!mailbox_list_is_valid_existing_name(src->list, src->name) ||
+ *src->name == '\0' ||
+ !mailbox_list_is_valid_create_name(dest->list, dest->name)) {
+ mailbox_list_set_error(src->list, MAIL_ERROR_PARAMS,
+ "Invalid mailbox name");
+ return -1;
+ }
+ if (strcmp(src->storage->name, dest->storage->name) != 0) {
+ mailbox_list_set_error(src->list, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailbox to another storage type.");
+ return -1;
+ }
+ if (!nullequals(src->list->set.index_dir, dest->list->set.index_dir) ||
+ !nullequals(src->list->set.control_dir, dest->list->set.control_dir)) {
+ mailbox_list_set_error(src->list, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailboxes across specified storages.");
+ return -1;
+ }
+ if (src->list->ns->type != NAMESPACE_PRIVATE ||
+ dest->list->ns->type != NAMESPACE_PRIVATE) {
+ mailbox_list_set_error(src->list, MAIL_ERROR_NOTPOSSIBLE,
+ "Renaming not supported across non-private namespaces.");
+ return -1;
+ }
+
+ return src->v.rename(src, dest, rename_children);
+}
+
struct mail_storage *mailbox_get_storage(const struct mailbox *box)
{
return box->storage;
int mailbox_update(struct mailbox *box, const struct mailbox_update *update);
/* Delete mailbox (and its parent directory, if it has no siblings) */
int mailbox_delete(struct mailbox *box);
+/* Rename mailbox. Renaming across different mailbox lists is possible only
+ between private namespaces and storages of the same type. If the rename
+ fails, the error is set to src's storage. */
+int mailbox_rename(struct mailbox *src, struct mailbox *dest,
+ bool rename_children);
/* Enable the given feature for the mailbox. */
int mailbox_enable(struct mailbox *box, enum mailbox_feature features);
int (*rename_mailbox)(struct mailbox_list *oldlist, const char *oldname,
struct mailbox_list *newlist, const char *newname,
bool rename_children);
- /* called by rename_mailbox() just before running the actual rename() */
- int (*rename_mailbox_pre)(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname);
};
struct mailbox_list_module_register {
*set->subscription_fname != '\0');
if (!mailbox_list_driver_find(driver, &idx)) {
- *error_r = t_strdup_printf("Unknown mailbox list driver: %s",
- driver);
+ *error_r = "Unknown driver name";
return -1;
}
class_p = array_idx(&mailbox_list_drivers, idx);
+ if (((*class_p)->props & MAILBOX_LIST_PROP_NO_MAILDIR_NAME) != 0 &&
+ set->maildir_name != NULL) {
+ *error_r = "maildir_name not supported by this driver";
+ return -1;
+ }
+ if (((*class_p)->props & MAILBOX_LIST_PROP_NO_ALT_DIR) != 0 &&
+ set->alt_dir != NULL) {
+ *error_r = "alt_dir not supported by this driver";
+ return -1;
+ }
+
list = (*class_p)->v.alloc();
array_create(&list->module_contexts, list->pool, sizeof(void *), 5);
list->set.inbox_path = p_strdup(list->pool, set->inbox_path);
list->set.subscription_fname =
p_strdup(list->pool, set->subscription_fname);
- list->set.maildir_name = set->maildir_name == NULL ||
- (list->props & MAILBOX_LIST_PROP_NO_MAILDIR_NAME) != 0 ? "" :
+ list->set.maildir_name = set->maildir_name == NULL ? "" :
p_strdup(list->pool, set->maildir_name);
list->set.mailbox_dir_name =
p_strdup(list->pool, set->mailbox_dir_name);
return list->v.delete_dir(list, name);
}
-static bool nullequals(const void *p1, const void *p2)
-{
- return (p1 == NULL && p2 == NULL) || (p1 != NULL && p2 != NULL);
-}
-
-int mailbox_list_rename_mailbox(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname, bool rename_children)
-{
- struct mail_storage *oldstorage;
- struct mail_storage *newstorage;
- uint8_t guid[MAIL_GUID_128_SIZE];
-
- if (mailbox_list_get_storage(&oldlist, &oldname, &oldstorage) < 0)
- return -1;
-
- newstorage = oldstorage;
- mailbox_list_get_closest_storage(newlist, &newstorage);
-
- if (!mailbox_list_is_valid_existing_name(oldlist, oldname) ||
- *oldname == '\0' ||
- !mailbox_list_is_valid_create_name(newlist, newname)) {
- mailbox_list_set_error(oldlist, MAIL_ERROR_PARAMS,
- "Invalid mailbox name");
- return -1;
- }
- if (strcmp(oldstorage->name, newstorage->name) != 0) {
- mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
- "Can't rename mailbox to another storage type.");
- return -1;
- }
- if (!nullequals(oldlist->set.index_dir, newlist->set.index_dir) ||
- !nullequals(oldlist->set.control_dir, newlist->set.control_dir)) {
- mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
- "Can't rename mailboxes across specified storages.");
- return -1;
- }
- if (oldlist->ns->type != NAMESPACE_PRIVATE ||
- newlist->ns->type != NAMESPACE_PRIVATE) {
- mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
- "Renaming not supported across non-private namespaces.");
- return -1;
- }
-
- if (oldlist->v.rename_mailbox(oldlist, oldname, newlist, newname,
- rename_children) < 0)
- return -1;
-
- /* we'll track mailbox names, instead of GUIDs. We may be renaming a
- non-selectable mailbox (directory), which doesn't even have a GUID */
- mailbox_name_get_sha128(newname, guid);
- mailbox_list_add_change(oldlist, MAILBOX_LOG_RECORD_RENAME, guid);
- return 0;
-}
-
void mailbox_name_get_sha128(const char *name, uint8_t guid[MAIL_GUID_128_SIZE])
{
unsigned char sha[SHA1_RESULTLEN];
enum mailbox_list_properties {
/* maildir_name must always be empty */
MAILBOX_LIST_PROP_NO_MAILDIR_NAME = 0x01,
+ /* alt directories not supported */
+ MAILBOX_LIST_PROP_NO_ALT_DIR = 0x02,
/* no support for \noselect directories, only mailboxes */
- MAILBOX_LIST_PROP_NO_NOSELECT = 0x02
+ MAILBOX_LIST_PROP_NO_NOSELECT = 0x04
};
enum mailbox_list_flags {
/* Delete a non-selectable mailbox. Fail if the mailbox is selectable. */
int mailbox_list_delete_dir(struct mailbox_list *list, const char *name);
-/* Rename mailbox. Renaming across different mailbox lists is possible only
- between private namespaces and storages of the same type. If the rename
- fails, the error is set to oldlist. */
-int mailbox_list_rename_mailbox(struct mailbox_list *oldlist,
- const char *oldname,
- struct mailbox_list *newlist,
- const char *newname, bool rename_children);
/* Returns the error message of last occurred error. */
const char *mailbox_list_get_last_error(struct mailbox_list *list,
return -1;
}
+static int test_mailbox_rename(struct mailbox *src,
+ struct mailbox *dest ATTR_UNUSED,
+ bool rename_children ATTR_UNUSED)
+{
+ mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE,
+ "Test mailbox rename isn't supported");
+ return -1;
+}
+
static void test_mailbox_get_status(struct mailbox *box ATTR_UNUSED,
enum mailbox_status_items items ATTR_UNUSED,
struct mailbox_status *status_r)
test_mailbox_create,
test_mailbox_update,
test_mailbox_delete,
+ test_mailbox_rename,
test_mailbox_get_status,
NULL,
NULL,
}
static int
-mailbox_move(struct mailbox_list *src_list, const char *src_name,
- struct mailbox_list *dest_list, const char **_dest_name)
+mailbox_move(struct mailbox *src_box, struct mailbox_list *dest_list,
+ const char *wanted_destname, struct mailbox **dest_box_r)
{
- const char *dir, *origin, *dest_name = *_dest_name;
+ const char *dest_name = wanted_destname;
+ struct mailbox *dest_box;
+ const char *dir, *origin;
enum mail_error error;
mode_t mode;
gid_t gid;
dir = mailbox_list_get_path(dest_list, NULL, MAILBOX_LIST_PATH_TYPE_DIR);
if (mkdir_parents_chgrp(dir, mode, gid, origin) < 0 &&
errno != EEXIST) {
- mailbox_list_set_critical(src_list,
+ mail_storage_set_critical(src_box->storage,
"mkdir_parents(%s) failed: %m", dir);
return -1;
}
- while (mailbox_list_rename_mailbox(src_list, src_name,
- dest_list, dest_name, FALSE) < 0) {
- mailbox_list_get_last_error(src_list, &error);
+ for (;;) {
+ dest_box = mailbox_alloc(dest_list, dest_name,
+ MAILBOX_FLAG_OPEN_DELETED);
+ if (mailbox_rename(src_box, dest_box, FALSE) == 0)
+ break;
+ mailbox_free(&dest_box);
+
+ mail_storage_get_last_error(src_box->storage, &error);
switch (error) {
case MAIL_ERROR_EXISTS:
break;
return -1;
}
- /* mailbox is being deleted multiple times per second.
- update the filename. */
- dest_name = t_strdup_printf("%s-%04u", *_dest_name,
+ /* destination already exists. generate a different name. */
+ dest_name = t_strdup_printf("%s-%04u", wanted_destname,
(uint32_t)random());
}
- *_dest_name = dest_name;
+
+ *dest_box_r = dest_box;
return 1;
}
}
/* first move the actual mailbox */
- if ((ret = mailbox_move(list, box->name, dest_ns->list, &destname)) < 0)
+ if ((ret = mailbox_move(box, dest_ns->list, destname,
+ &expunge_box)) < 0)
return -1;
if (ret == 0) {
- mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
return -1;
}
/* other sessions now see the mailbox completely deleted.
since it's not really deleted in the lazy-expunge namespace,
we might want to change it again. so mark the index undeleted. */
- expunge_box = mailbox_alloc(dest_ns->list, destname,
- MAILBOX_FLAG_OPEN_DELETED);
if (mailbox_open(expunge_box) < 0) {
str = mail_storage_get_last_error(expunge_box->storage, &error);
i_error("lazy_expunge: Couldn't open DELETEd mailbox "
- "%s: %s", destname, str);
+ "%s: %s", expunge_box->name, str);
mailbox_free(&expunge_box);
return -1;
}
return -1;
}
- if (expunge_ns == dest_ns && strcmp(destname, box->name) != 0)
+ if (expunge_ns == dest_ns && strcmp(expunge_box->name, box->name) != 0)
ret = mailbox_move_all_mails(expunge_box, box->name);
else
ret = 0;
/* next move the expunged messages mailbox, if it exists */
dest_ns = get_lazy_ns(list->ns->user, LAZY_NAMESPACE_DELETE_EXPUNGE);
if (expunge_ns != dest_ns) {
- if (mailbox_move(expunge_ns->list, box->name,
- dest_ns->list, &destname) < 0)
- ret = -1;
+ struct mailbox *ret_box;
+
+ expunge_box = mailbox_alloc(expunge_ns->list, box->name, 0);
+ ret = mailbox_move(expunge_box, dest_ns->list,
+ destname, &ret_box);
+ if (ret > 0)
+ mailbox_free(&ret_box);
+ mailbox_free(&expunge_box);
}
- return ret;
+ return ret < 0 ? -1 : 0;
}
static void lazy_expunge_mailbox_allocated(struct mailbox *box)
virtual_mailbox_create,
virtual_mailbox_update,
index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
index_storage_get_status,
NULL,
NULL,