From: Timo Sirainen Date: Fri, 22 May 2009 23:16:03 +0000 (-0400) Subject: Implemented initial support for renaming mailboxes across namespaces. X-Git-Tag: 2.0.alpha1~699 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=91b203fd2132510a47a4b34252c0ae0efd688a19;p=thirdparty%2Fdovecot%2Fcore.git Implemented initial support for renaming mailboxes across namespaces. --HG-- branch : HEAD --- diff --git a/src/imap/cmd-rename.c b/src/imap/cmd-rename.c index bcbbd745ff..fb7d208f9a 100644 --- a/src/imap/cmd-rename.c +++ b/src/imap/cmd-rename.c @@ -7,8 +7,8 @@ bool cmd_rename(struct client_command_context *cmd) { struct mail_storage *old_storage, *new_storage; - struct mailbox_list *list; - struct mail_namespace *ns; + struct mailbox_list *old_list, *new_list; + struct mail_namespace *old_ns; const char *oldname, *newname; unsigned int oldlen; @@ -22,33 +22,30 @@ bool cmd_rename(struct client_command_context *cmd) old_storage = client_find_storage(cmd, &oldname); if (old_storage == NULL) return TRUE; + old_list = mail_storage_get_list(old_storage); new_storage = client_find_storage(cmd, &newname); if (new_storage == NULL) return TRUE; - - if (old_storage != new_storage) { - client_send_tagline(cmd, - "NO Can't rename mailbox to another storage type."); - return TRUE; - } - - /* disallow box -> box/child, because it may break clients and there's - really no point in doing it anyway. */ - list = mail_storage_get_list(old_storage); - ns = mailbox_list_get_namespace(list); - oldlen = strlen(oldname); - if (strncmp(oldname, newname, oldlen) == 0 && - newname[oldlen] == ns->real_sep) { - client_send_tagline(cmd, - "NO Can't rename mailbox under its own child."); - return TRUE; + new_list = mail_storage_get_list(new_storage); + + if (old_storage == new_storage) { + /* disallow box -> box/child, because it may break clients and + there's really no point in doing it anyway. */ + old_ns = mailbox_list_get_namespace(old_list); + oldlen = strlen(oldname); + if (strncmp(oldname, newname, oldlen) == 0 && + newname[oldlen] == old_ns->real_sep) { + client_send_tagline(cmd, + "NO Can't rename mailbox under its own child."); + return TRUE; + } } - if (mailbox_list_rename_mailbox(list, oldname, newname) < 0) - client_send_list_error(cmd, list); - else { + if (mailbox_list_rename_mailbox(old_list, oldname, + new_list, newname, TRUE) < 0) + client_send_list_error(cmd, old_list); + else client_send_tagline(cmd, "OK Rename completed."); - } return TRUE; } diff --git a/src/lib-storage/index/dbox/dbox-storage.c b/src/lib-storage/index/dbox/dbox-storage.c index adcd997322..0d0c6b626b 100644 --- a/src/lib-storage/index/dbox/dbox-storage.c +++ b/src/lib-storage/index/dbox/dbox-storage.c @@ -39,11 +39,14 @@ static MODULE_CONTEXT_DEFINE_INIT(dbox_mailbox_list_module, static int dbox_list_delete_mailbox(struct mailbox_list *list, const char *name); static int -dbox_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname); +dbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children); static int -dbox_list_rename_mailbox_pre(struct mailbox_list *list, - const char *oldname, const char *newname); +dbox_list_rename_mailbox_pre(struct mailbox_list *oldlist, + const char *oldname, + struct mailbox_list *newlist, + const char *newname); static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx, const char *dir, const char *fname, const char *mailbox_name, @@ -605,47 +608,59 @@ dbox_list_delete_mailbox(struct mailbox_list *list, const char *name) return -1; } -static bool -dbox_list_rename_get_alt_paths(struct mailbox_list *list, - const char *oldname, const char *newname, +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) { - struct dbox_storage *storage = DBOX_LIST_CONTEXT(list); + struct dbox_storage *oldstorage = DBOX_LIST_CONTEXT(oldlist); + struct dbox_storage *newstorage = DBOX_LIST_CONTEXT(newlist); const char *path; - if (storage->alt_dir == NULL) - return FALSE; - - path = mailbox_list_get_path(list, oldname, MAILBOX_LIST_PATH_TYPE_DIR); - *oldpath_r = dbox_get_alt_path(storage, path); + path = mailbox_list_get_path(oldlist, oldname, path_type); + *oldpath_r = dbox_get_alt_path(oldstorage, path); if (*oldpath_r == NULL) - return FALSE; + return 0; - path = mailbox_list_get_path(list, newname, MAILBOX_LIST_PATH_TYPE_DIR); - *newpath_r = dbox_get_alt_path(storage, path); - i_assert(*newpath_r != NULL); - return TRUE; + path = mailbox_list_get_path(newlist, newname, path_type); + *newpath_r = dbox_get_alt_path(newstorage, 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; } static int -dbox_list_rename_mailbox_pre(struct mailbox_list *list, - const char *oldname, const char *newname) +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; - if (!dbox_list_rename_get_alt_paths(list, oldname, newname, - &alt_oldpath, &alt_newpath)) - return 0; + 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(list, MAIL_ERROR_EXISTS, + mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS, "Target mailbox already exists"); return -1; } else if (errno != ENOENT) { - mailbox_list_set_critical(list, "stat(%s) failed: %m", + mailbox_list_set_critical(oldlist, "stat(%s) failed: %m", alt_newpath); return -1; } @@ -653,25 +668,42 @@ dbox_list_rename_mailbox_pre(struct mailbox_list *list, } static int -dbox_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname) +dbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children) { - struct dbox_storage *storage = DBOX_LIST_CONTEXT(list); - const char *alt_oldpath, *alt_newpath; + struct dbox_storage *oldstorage = DBOX_LIST_CONTEXT(oldlist); + enum mailbox_list_path_type path_type; + const char *alt_oldpath, *alt_newpath, *path; + int ret; - if (storage->list_module_ctx.super.rename_mailbox(list, oldname, - newname) < 0) + if (oldstorage->list_module_ctx.super. + rename_mailbox(oldlist, oldname, newlist, newname, + rename_children) < 0) return -1; - if (!dbox_list_rename_get_alt_paths(list, oldname, newname, - &alt_oldpath, &alt_newpath)) - return 0; + 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(list, "rename(%s, %s) failed: %m", + mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", alt_oldpath, alt_newpath); } return 0; diff --git a/src/lib-storage/index/maildir/maildir-storage.c b/src/lib-storage/index/maildir/maildir-storage.c index 0f7d63fda0..711f445dd2 100644 --- a/src/lib-storage/index/maildir/maildir-storage.c +++ b/src/lib-storage/index/maildir/maildir-storage.c @@ -43,8 +43,9 @@ static const char *maildir_subdirs[] = { "cur", "new", "tmp" }; static int maildir_list_delete_mailbox(struct mailbox_list *list, const char *name); static int -maildir_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname); +maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children); static int maildir_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx, const char *dir, const char *fname, @@ -829,28 +830,31 @@ maildir_list_delete_mailbox(struct mailbox_list *list, const char *name) return 0; } -static int maildir_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname) +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_storage *storage = MAILDIR_LIST_CONTEXT(list); + struct maildir_storage *oldstorage = 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(list, oldname, + path1 = mailbox_list_get_path(oldlist, oldname, MAILBOX_LIST_PATH_TYPE_MAILBOX); - path2 = mailbox_list_get_path(list, NULL, + path2 = mailbox_list_get_path(oldlist, NULL, MAILBOX_LIST_PATH_TYPE_MAILBOX); if (strcmp(path1, path2) == 0) { - mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, + mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE, "Renaming INBOX isn't supported."); return -1; } } - return storage->list_module_ctx.super. - rename_mailbox(list, oldname, newname); + return oldstorage->list_module_ctx.super. + rename_mailbox(oldlist, oldname, newlist, newname, + rename_children); } static int maildir_storage_mailbox_close(struct mailbox *box) diff --git a/src/lib-storage/index/shared/shared-list.c b/src/lib-storage/index/shared/shared-list.c index ea70e1ef26..52ec9a7772 100644 --- a/src/lib-storage/index/shared/shared-list.c +++ b/src/lib-storage/index/shared/shared-list.c @@ -245,52 +245,62 @@ shared_list_delete_mailbox(struct mailbox_list *list, const char *name) return ret; } -static int shared_list_rename_get_ns(struct mailbox_list *list, - const char **oldname, const char **newname, +static int shared_list_rename_get_ns(struct mailbox_list *oldlist, + const char **oldname, + struct mailbox_list *newlist, + const char **newname, struct mail_namespace **ns_r) { struct mail_namespace *old_ns, *new_ns; - if (shared_storage_get_namespace(list->ns->storage, + if (shared_storage_get_namespace(oldlist->ns->storage, oldname, &old_ns) < 0 || - shared_storage_get_namespace(list->ns->storage, + shared_storage_get_namespace(newlist->ns->storage, newname, &new_ns) < 0) return -1; if (old_ns != new_ns) { - mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, - "Can't rename mailboxes across storages"); + mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename shared mailboxes across storages."); return -1; } *ns_r = old_ns; return 0; } -static int shared_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname) +static int +shared_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children) { struct mail_namespace *ns; int ret; - if (shared_list_rename_get_ns(list, &oldname, &newname, &ns) < 0) + if (shared_list_rename_get_ns(oldlist, &oldname, + newlist, &newname, &ns) < 0) return -1; - ret = mailbox_list_rename_mailbox(ns->list, oldname, newname); + ret = mailbox_list_rename_mailbox(ns->list, oldname, ns->list, newname, + rename_children); if (ret < 0) - shared_list_copy_error(list, ns); + shared_list_copy_error(oldlist, ns); return ret; } static int -shared_list_rename_mailbox_pre(struct mailbox_list *list, - const char *oldname, const char *newname) +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(list, &oldname, &newname, &ns) < 0) + if (shared_list_rename_get_ns(oldlist, &oldname, + newlist, &newname, &ns) < 0) return -1; - ret = ns->list->v.rename_mailbox_pre(ns->list, oldname, newname); + ret = ns->list->v.rename_mailbox_pre(ns->list, oldname, + ns->list, newname); if (ret < 0) - shared_list_copy_error(list, ns); + shared_list_copy_error(oldlist, ns); return ret; } diff --git a/src/lib-storage/list/mailbox-list-fs.c b/src/lib-storage/list/mailbox-list-fs.c index 39f5be7b19..0cabff94ce 100644 --- a/src/lib-storage/list/mailbox-list-fs.c +++ b/src/lib-storage/list/mailbox-list-fs.c @@ -4,6 +4,7 @@ #include "hostpid.h" #include "mkdir-parents.h" #include "subscription-file.h" +#include "mail-storage.h" #include "mailbox-list-fs.h" #include @@ -279,50 +280,73 @@ static int fs_list_delete_mailbox(struct mailbox_list *list, const char *name) return mailbox_list_delete_index_control(list, name); } -static int rename_dir(struct mailbox_list *list, - enum mailbox_list_path_type type, - const char *oldname, const char *newname) +static int rename_dir(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + enum mailbox_list_path_type type, bool rmdir_parent) { - const char *oldpath, *newpath; + const char *oldpath, *newpath, *p; - oldpath = mailbox_list_get_path(list, oldname, type); - newpath = mailbox_list_get_path(list, newname, type); + 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(list, "rename(%s, %s) failed: %m", + mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); return -1; } + if (rmdir_parent && (p = strrchr(oldpath, '/')) != NULL) { + oldpath = t_strdup_until(oldpath, p); + if (rmdir(oldpath) < 0 && + errno != ENOENT && errno != ENOTEMPTY) { + mailbox_list_set_critical(oldlist, + "rmdir(%s) failed: %m", oldpath); + } + } return 0; } -static int fs_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname) +static int fs_list_rename_mailbox(struct mailbox_list *oldlist, + const char *oldname, + struct mailbox_list *newlist, + const char *newname, bool rename_children) { const char *oldpath, *newpath, *p; + enum mailbox_list_path_type path_type; struct stat st; mode_t mode; gid_t gid; + bool isfile, rmdir_parent = FALSE; + + (void)mail_storage_get_mailbox_path(oldlist->ns->storage, + oldname, &isfile); + if (rename_children) + path_type = MAILBOX_LIST_PATH_TYPE_DIR; + else if (isfile || *oldlist->set.maildir_name != '\0') + path_type = MAILBOX_LIST_PATH_TYPE_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."); + return -1; + } - oldpath = mailbox_list_get_path(list, oldname, - MAILBOX_LIST_PATH_TYPE_DIR); - newpath = mailbox_list_get_path(list, newname, - MAILBOX_LIST_PATH_TYPE_DIR); + oldpath = mailbox_list_get_path(oldlist, oldname, path_type); + newpath = mailbox_list_get_path(newlist, newname, path_type); /* create the hierarchy */ p = strrchr(newpath, '/'); if (p != NULL) { - mailbox_list_get_dir_permissions(list, NULL, &mode, &gid); + mailbox_list_get_dir_permissions(newlist, NULL, &mode, &gid); p = t_strdup_until(newpath, p); if (mkdir_parents_chown(p, mode, (uid_t)-1, gid) < 0 && errno != EEXIST) { - if (mailbox_list_set_error_from_errno(list)) + if (mailbox_list_set_error_from_errno(oldlist)) return -1; - mailbox_list_set_critical(list, + mailbox_list_set_critical(oldlist, "mkdir_parents(%s) failed: %m", p); return -1; } @@ -333,21 +357,22 @@ static int fs_list_rename_mailbox(struct mailbox_list *list, possibility that someone actually tries to rename two mailboxes to same new one */ if (lstat(newpath, &st) == 0) { - mailbox_list_set_error(list, MAIL_ERROR_EXISTS, + mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS, "Target mailbox already exists"); return -1; } else if (errno == ENOTDIR) { - mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, + mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE, "Target mailbox doesn't allow inferior mailboxes"); return -1; } else if (errno != ENOENT && errno != EACCES) { - mailbox_list_set_critical(list, "lstat(%s) failed: %m", + mailbox_list_set_critical(oldlist, "lstat(%s) failed: %m", newpath); return -1; } - if (list->v.rename_mailbox_pre != NULL) { - if (list->v.rename_mailbox_pre(list, oldname, newname) < 0) + if (oldlist->v.rename_mailbox_pre != NULL) { + if (oldlist->v.rename_mailbox_pre(oldlist, oldname, + newlist, newname) < 0) return -1; } @@ -355,18 +380,32 @@ static int fs_list_rename_mailbox(struct mailbox_list *list, the next time it's needed. */ if (rename(oldpath, newpath) < 0) { if (ENOTFOUND(errno)) { - mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, + mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname)); - } else if (!mailbox_list_set_error_from_errno(list)) { - mailbox_list_set_critical(list, + } else if (!mailbox_list_set_error_from_errno(oldlist)) { + mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); } return -1; } - (void)rename_dir(list, MAILBOX_LIST_PATH_TYPE_CONTROL, - oldname, newname); - (void)rename_dir(list, MAILBOX_LIST_PATH_TYPE_INDEX, oldname, newname); + if (!rename_children) { + /* if there are no child mailboxes, get rid of the mailbox + directory entirely. */ + oldpath = mailbox_list_get_path(oldlist, oldname, + MAILBOX_LIST_PATH_TYPE_DIR); + if (rmdir(oldpath) == 0) + rmdir_parent = TRUE; + else if (errno != ENOENT && errno != ENOTEMPTY) { + mailbox_list_set_critical(oldlist, + "rmdir(%s) failed: %m", oldpath); + } + } + + (void)rename_dir(oldlist, oldname, newlist, newname, + MAILBOX_LIST_PATH_TYPE_CONTROL, rmdir_parent); + (void)rename_dir(oldlist, oldname, newlist, newname, + MAILBOX_LIST_PATH_TYPE_INDEX, rmdir_parent); return 0; } diff --git a/src/lib-storage/list/mailbox-list-maildir.c b/src/lib-storage/list/mailbox-list-maildir.c index 3c45d17785..6179556214 100644 --- a/src/lib-storage/list/mailbox-list-maildir.c +++ b/src/lib-storage/list/mailbox-list-maildir.c @@ -277,29 +277,29 @@ static int maildir_list_set_subscribed(struct mailbox_list *_list, name, set); } -static int rename_dir(struct mailbox_list *list, - enum mailbox_list_path_type type, - const char *oldname, const char *newname) +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(list, oldname, type); - newpath = mailbox_list_get_path(list, newname, type); + 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(list, "rename(%s, %s) failed: %m", + mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); return -1; } - return 0; } -static int rename_children(struct mailbox_list *list, - const char *oldname, const char *newname) +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; @@ -309,6 +309,7 @@ static int rename_children(struct mailbox_list *list, unsigned int i, count; size_t oldnamelen; pool_t pool; + char old_sep; int ret; ret = 0; @@ -321,9 +322,9 @@ static int rename_children(struct mailbox_list *list, pool = pool_alloconly_create("Maildir++ children list", 1024); i_array_init(&names_arr, 64); - pattern = t_strdup_printf("%s%c*", oldname, - mailbox_list_get_hierarchy_sep(list)); - iter = mailbox_list_iter_init(list, pattern, + 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); while ((info = mailbox_list_iter_next(iter)) != NULL) { const char *name; @@ -331,8 +332,7 @@ static int rename_children(struct mailbox_list *list, /* 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] == - mailbox_list_get_hierarchy_sep(list)) { + info->name[oldnamelen] == old_sep) { name = p_strdup(pool, info->name + oldnamelen); array_append(&names_arr, &name, 1); } @@ -353,9 +353,9 @@ static int rename_children(struct mailbox_list *list, } new_listname = t_strconcat(newname, names[i], NULL); - oldpath = mailbox_list_get_path(list, old_listname, + oldpath = mailbox_list_get_path(oldlist, old_listname, MAILBOX_LIST_PATH_TYPE_MAILBOX); - newpath = mailbox_list_get_path(list, new_listname, + 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 @@ -369,16 +369,16 @@ static int rename_children(struct mailbox_list *list, if (rename(oldpath, newpath) == 0 || EDESTDIREXISTS(errno)) ret = 1; else { - mailbox_list_set_critical(list, + mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); ret = -1; break; } - (void)rename_dir(list, MAILBOX_LIST_PATH_TYPE_CONTROL, - old_listname, new_listname); - (void)rename_dir(list, MAILBOX_LIST_PATH_TYPE_INDEX, - old_listname, new_listname); + (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); @@ -393,8 +393,10 @@ maildir_list_delete_mailbox(struct mailbox_list *list, const char *name) return mailbox_list_delete_index_control(list, name); } -static int maildir_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname) +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; int ret; @@ -402,26 +404,29 @@ static int maildir_list_rename_mailbox(struct mailbox_list *list, /* NOTE: it's possible to rename a nonexisting mailbox which has children. In that case we should ignore the rename() error. */ - oldpath = mailbox_list_get_path(list, oldname, + oldpath = mailbox_list_get_path(oldlist, oldname, MAILBOX_LIST_PATH_TYPE_MAILBOX); - newpath = mailbox_list_get_path(list, newname, + newpath = mailbox_list_get_path(newlist, newname, MAILBOX_LIST_PATH_TYPE_MAILBOX); ret = rename(oldpath, newpath); if (ret == 0 || errno == ENOENT) { - (void)rename_dir(list, MAILBOX_LIST_PATH_TYPE_CONTROL, - oldname, newname); - (void)rename_dir(list, MAILBOX_LIST_PATH_TYPE_INDEX, - oldname, newname); + (void)rename_dir(oldlist, oldname, newlist, newname, + MAILBOX_LIST_PATH_TYPE_CONTROL); + (void)rename_dir(oldlist, oldname, newlist, newname, + MAILBOX_LIST_PATH_TYPE_INDEX); found = ret == 0; - T_BEGIN { - ret = rename_children(list, oldname, newname); + if (!rename_children) + ret = 0; + else T_BEGIN { + ret = maildir_rename_children(oldlist, oldname, + newlist, newname); } T_END; if (ret < 0) return -1; if (!found && ret == 0) { - mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, + mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname)); return -1; } @@ -430,10 +435,10 @@ static int maildir_list_rename_mailbox(struct mailbox_list *list, } if (EDESTDIREXISTS(errno)) { - mailbox_list_set_error(list, MAIL_ERROR_EXISTS, + mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS, "Target mailbox already exists"); } else { - mailbox_list_set_critical(list, "rename(%s, %s) failed: %m", + mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m", oldpath, newpath); } return -1; diff --git a/src/lib-storage/mailbox-list-private.h b/src/lib-storage/mailbox-list-private.h index 7c37315abd..1dae0fa6b0 100644 --- a/src/lib-storage/mailbox-list-private.h +++ b/src/lib-storage/mailbox-list-private.h @@ -51,11 +51,14 @@ struct mailbox_list_vfuncs { int (*set_subscribed)(struct mailbox_list *list, const char *name, bool set); int (*delete_mailbox)(struct mailbox_list *list, const char *name); - int (*rename_mailbox)(struct mailbox_list *list, const char *oldname, - const char *newname); + 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 *list, - const char *oldname, const char *newname); + int (*rename_mailbox_pre)(struct mailbox_list *oldlist, + const char *oldname, + struct mailbox_list *newlist, + const char *newname); }; struct mailbox_list_module_register { diff --git a/src/lib-storage/mailbox-list.c b/src/lib-storage/mailbox-list.c index cbd4eb8328..d4f1785616 100644 --- a/src/lib-storage/mailbox-list.c +++ b/src/lib-storage/mailbox-list.c @@ -9,6 +9,7 @@ #include "imap-match.h" #include "imap-utf7.h" #include "mailbox-tree.h" +#include "mail-storage-private.h" #include "mailbox-list-private.h" #include @@ -572,17 +573,43 @@ int mailbox_list_delete_mailbox(struct mailbox_list *list, const char *name) return list->v.delete_mailbox(list, name); } -int mailbox_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname) +static bool nullequals(const void *p1, const void *p2) { - if (!mailbox_list_is_valid_existing_name(list, oldname) || - !mailbox_list_is_valid_create_name(list, newname)) { - mailbox_list_set_error(list, MAIL_ERROR_PARAMS, + 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) +{ + if (!mailbox_list_is_valid_existing_name(oldlist, oldname) || + !mailbox_list_is_valid_create_name(newlist, newname)) { + mailbox_list_set_error(oldlist, MAIL_ERROR_PARAMS, "Invalid mailbox name"); return -1; } + if (strcmp(oldlist->ns->storage->name, + newlist->ns->storage->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; + } - return list->v.rename_mailbox(list, oldname, newname); + return oldlist->v.rename_mailbox(oldlist, oldname, newlist, newname, + rename_children); } static int mailbox_list_try_delete(struct mailbox_list *list, const char *dir) diff --git a/src/lib-storage/mailbox-list.h b/src/lib-storage/mailbox-list.h index d6cf93a491..804cccd20d 100644 --- a/src/lib-storage/mailbox-list.h +++ b/src/lib-storage/mailbox-list.h @@ -212,15 +212,13 @@ int mailbox_list_set_subscribed(struct mailbox_list *list, /* Delete the given mailbox. If it has children, they aren't deleted. */ int mailbox_list_delete_mailbox(struct mailbox_list *list, const char *name); -/* If the name has inferior hierarchical names, then the inferior - hierarchical names MUST also be renamed (ie. foo -> bar renames - also foo/bar -> bar/bar). newname may contain multiple new - hierarchies. - - If oldname is case-insensitively "INBOX", the mails are moved - into new mailbox but the INBOX mailbox must not be deleted. */ -int mailbox_list_rename_mailbox(struct mailbox_list *list, - const char *oldname, const char *newname); +/* 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, diff --git a/src/plugins/acl/acl-mailbox-list.c b/src/plugins/acl/acl-mailbox-list.c index 69c7df482c..d0405a868d 100644 --- a/src/plugins/acl/acl-mailbox-list.c +++ b/src/plugins/acl/acl-mailbox-list.c @@ -455,24 +455,26 @@ acl_mailbox_list_delete(struct mailbox_list *list, const char *name) } static int -acl_mailbox_list_rename(struct mailbox_list *list, - const char *oldname, const char *newname) +acl_mailbox_list_rename(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children) { - struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list); + struct acl_mailbox_list *old_alist = ACL_LIST_CONTEXT(oldlist); + struct acl_mailbox_list *new_alist = ACL_LIST_CONTEXT(newlist); bool can_see; int ret; /* renaming requires rights to delete the old mailbox */ - ret = acl_mailbox_list_have_right(list, oldname, + ret = acl_mailbox_list_have_right(oldlist, oldname, ACL_STORAGE_RIGHT_DELETE, &can_see); if (ret <= 0) { if (ret < 0) return -1; if (can_see) { - mailbox_list_set_error(list, MAIL_ERROR_PERM, + mailbox_list_set_error(oldlist, MAIL_ERROR_PERM, MAIL_ERRSTR_NO_PERMISSION); } else { - mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, + mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname)); } return 0; @@ -480,8 +482,8 @@ acl_mailbox_list_rename(struct mailbox_list *list, /* and create the new one under the parent mailbox */ T_BEGIN { - ret = acl_storage_rights_ctx_have_right(&alist->rights, newname, - TRUE, ACL_STORAGE_RIGHT_CREATE, NULL); + ret = acl_storage_rights_ctx_have_right(&new_alist->rights, + newname, TRUE, ACL_STORAGE_RIGHT_CREATE, NULL); } T_END; if (ret <= 0) { @@ -489,15 +491,17 @@ acl_mailbox_list_rename(struct mailbox_list *list, /* Note that if the mailbox didn't have LOOKUP permission, this not reveals to user the mailbox's existence. Can't help it. */ - mailbox_list_set_error(list, MAIL_ERROR_PERM, + mailbox_list_set_error(oldlist, MAIL_ERROR_PERM, MAIL_ERRSTR_NO_PERMISSION); } else { - mailbox_list_set_internal_error(list); + mailbox_list_set_internal_error(oldlist); } return -1; } - return alist->module_ctx.super.rename_mailbox(list, oldname, newname); + return old_alist->module_ctx.super. + rename_mailbox(oldlist, oldname, newlist, newname, + rename_children); } static void acl_mailbox_list_init_shared(struct mailbox_list *list) diff --git a/src/plugins/listescape/listescape-plugin.c b/src/plugins/listescape/listescape-plugin.c index ad54edade5..5770d696da 100644 --- a/src/plugins/listescape/listescape-plugin.c +++ b/src/plugins/listescape/listescape-plugin.c @@ -204,14 +204,18 @@ listescape_delete_mailbox(struct mailbox_list *list, const char *name) } static int -listescape_rename_mailbox(struct mailbox_list *list, const char *oldname, - const char *newname) +listescape_rename_mailbox(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children) { - struct listescape_mailbox_list *mlist = LIST_ESCAPE_LIST_CONTEXT(list); - - oldname = list_escape(list->ns, oldname, TRUE); - newname = list_escape(list->ns, newname, TRUE); - return mlist->module_ctx.super.rename_mailbox(list, oldname, newname); + struct listescape_mailbox_list *old_mlist = + LIST_ESCAPE_LIST_CONTEXT(oldlist); + + oldname = list_escape(oldlist->ns, oldname, TRUE); + newname = list_escape(newlist->ns, newname, TRUE); + return old_mlist->module_ctx.super. + rename_mailbox(oldlist, oldname, newlist, newname, + rename_children); } static int listescape_set_subscribed(struct mailbox_list *list, diff --git a/src/plugins/mail-log/mail-log-plugin.c b/src/plugins/mail-log/mail-log-plugin.c index 56fdc2a5b1..90d86df074 100644 --- a/src/plugins/mail-log/mail-log-plugin.c +++ b/src/plugins/mail-log/mail-log-plugin.c @@ -597,13 +597,16 @@ mail_log_mailbox_list_delete(struct mailbox_list *list, const char *name) } static int -mail_log_mailbox_list_rename(struct mailbox_list *list, const char *oldname, - const char *newname) +mail_log_mailbox_list_rename(struct mailbox_list *oldlist, const char *oldname, + struct mailbox_list *newlist, const char *newname, + bool rename_children) { - union mailbox_list_module_context *llist = MAIL_LOG_LIST_CONTEXT(list); - struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(list->ns->user); + union mailbox_list_module_context *llist = + MAIL_LOG_LIST_CONTEXT(oldlist); + struct mail_log_user *muser = MAIL_LOG_USER_CONTEXT(oldlist->ns->user); - if (llist->super.rename_mailbox(list, oldname, newname) < 0) + if (llist->super.rename_mailbox(oldlist, oldname, newlist, newname, + rename_children) < 0) return -1; if ((muser->events & MAIL_LOG_EVENT_MAILBOX_RENAME) == 0)