]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
dbox: Delete mailboxes atomically and update its messages' refcounts.
authorTimo Sirainen <tss@iki.fi>
Tue, 24 Mar 2009 22:41:52 +0000 (18:41 -0400)
committerTimo Sirainen <tss@iki.fi>
Tue, 24 Mar 2009 22:41:52 +0000 (18:41 -0400)
--HG--
branch : HEAD

src/lib-storage/index/dbox/dbox-map.c
src/lib-storage/index/dbox/dbox-storage.c
src/lib-storage/index/dbox/dbox-storage.h

index 6931a2ad81bebae17233209caceb3009a7bc30d4..218ebce6d5fcffdeff9a8ee56a8f0ffcf149e130 100644 (file)
@@ -294,7 +294,9 @@ dbox_map_transaction_begin(struct dbox_map *map, bool external)
 
        ctx = i_new(struct dbox_map_transaction_context, 1);
        ctx->map = map;
-       ctx->trans = mail_index_transaction_begin(map->view, flags);
+       if (dbox_map_open(map, FALSE) == 0 &&
+           dbox_map_refresh(map) == 0)
+               ctx->trans = mail_index_transaction_begin(map->view, flags);
        return ctx;
 }
 
@@ -376,16 +378,17 @@ int dbox_map_update_refcounts(struct dbox_map_transaction_context *ctx,
        const uint32_t *uids;
        unsigned int i, count;
        uint32_t seq;
-       int ret;
+
+       if (ctx->trans == NULL)
+               return -1;
 
        uids = array_get(map_uids, &count);
        for (i = 0; i < count; i++) {
-               if ((ret = dbox_map_get_seq(map, uids[i], &seq)) <= 0) {
-                       if (ret == 0) {
-                               dbox_map_set_corrupted(map,
-                                       "refcount update lost map_uid=%u",
-                                       uids[i]);
-                       }
+               if (!mail_index_lookup_seq(map->view, uids[i], &seq)) {
+                       /* we can't refresh map here since view has a
+                          transaction open. */
+                       dbox_map_set_corrupted(map,
+                               "refcount update lost map_uid=%u", uids[i]);
                        return -1;
                }
 
@@ -408,8 +411,6 @@ int dbox_map_remove_file_id(struct dbox_map *map, uint32_t file_id)
 
        /* make sure the map is refreshed, otherwise we might be expunging
           messages that have already been moved to other files. */
-       if (dbox_map_refresh(map) < 0)
-               return -1;
 
        /* we need a per-file transaction, otherwise we can't refresh the map */
        map_trans = dbox_map_transaction_begin(map, TRUE);
index 6a67e2b06ef34f1317d35f27c92329412135515c..cae5c076e646090797e92e88f589dcdc8e7b7562 100644 (file)
@@ -4,7 +4,10 @@
 #include "array.h"
 #include "ioloop.h"
 #include "str.h"
+#include "hex-binary.h"
+#include "randgen.h"
 #include "mkdir-parents.h"
+#include "unlink-directory.h"
 #include "unlink-old-files.h"
 #include "index-mail.h"
 #include "mail-copy.h"
@@ -75,6 +78,11 @@ dbox_get_list_settings(struct mailbox_list_settings *list_set,
        if (mailbox_list_settings_parse(data, list_set, storage->ns,
                                        layout_r, alt_dir_r, error_r) < 0)
                return -1;
+
+       if (*list_set->mailbox_dir_name == '\0') {
+               *error_r = "dbox: MAILBOXDIR must not be empty";
+               return -1;
+       }
        return 0;
 }
 
@@ -318,7 +326,7 @@ static void dbox_write_index_header(struct mailbox *box)
 }
 
 static int create_dbox(struct mail_storage *_storage, const char *path,
-                      const char *name)
+                      const char *name, bool directory)
 {
        struct dbox_storage *storage = (struct dbox_storage *)_storage;
        struct mailbox *box;
@@ -327,10 +335,13 @@ static int create_dbox(struct mail_storage *_storage, const char *path,
 
        mailbox_list_get_dir_permissions(_storage->list, &mode, &gid);
        if (mkdir_parents_chown(path, mode, (uid_t)-1, gid) == 0) {
-               /* create indexes immediately with the dbox header */
-               box = dbox_open(storage, name, MAILBOX_OPEN_KEEP_RECENT);
-               dbox_write_index_header(box);
-               mailbox_close(&box);
+               if (!directory) {
+                       /* create indexes immediately with the dbox header */
+                       box = dbox_open(storage, name,
+                                       MAILBOX_OPEN_KEEP_RECENT);
+                       dbox_write_index_header(box);
+                       mailbox_close(&box);
+               }
        } else if (errno != EEXIST) {
                if (!mail_storage_set_error_from_errno(_storage)) {
                        mail_storage_set_critical(_storage,
@@ -385,7 +396,7 @@ dbox_mailbox_open(struct mail_storage *_storage, const char *name,
                if (strcmp(name, "INBOX") == 0 &&
                    (_storage->ns->flags & NAMESPACE_FLAG_INBOX) != 0) {
                        /* INBOX always exists, create it */
-                       if (create_dbox(_storage, path, "INBOX") < 0)
+                       if (create_dbox(_storage, path, "INBOX", FALSE) < 0)
                                return NULL;
                        return dbox_open(storage, name, flags);
                }
@@ -437,78 +448,72 @@ static int dbox_mailbox_create(struct mail_storage *_storage,
                return -1;
        }
 
-       return create_dbox(_storage, path, name);
+       return create_dbox(_storage, path, name, directory);
 }
 
 static int
-dbox_delete_nonrecursive(struct mailbox_list *list, const char *path,
-                        const char *name)
+dbox_mailbox_unref_mails(struct dbox_storage *storage, const char *path)
 {
-       DIR *dir;
-       struct dirent *d;
-       string_t *full_path;
-       unsigned int dir_len;
-       bool unlinked_something = FALSE;
-
-       dir = opendir(path);
-       if (dir == NULL) {
-               if (errno == ENOENT)
-                       return 0;
-               if (!mailbox_list_set_error_from_errno(list)) {
-                       mailbox_list_set_critical(list,
-                               "opendir(%s) failed: %m", path);
-               }
-               return -1;
-       }
-
-       full_path = t_str_new(256);
-       str_append(full_path, path);
-       str_append_c(full_path, '/');
-       dir_len = str_len(full_path);
-
-       errno = 0;
-       while ((d = readdir(dir)) != NULL) {
-               if (d->d_name[0] == '.') {
-                       /* skip . and .. */
-                       if (d->d_name[1] == '\0')
-                               continue;
-                       if (d->d_name[1] == '.' && d->d_name[2] == '\0')
-                               continue;
-               }
+       struct mailbox_list *list = storage->storage.list;
+       struct mailbox *box;
+       struct dbox_mailbox *mbox;
+       const struct mail_index_header *hdr;
+       const struct dbox_mail_index_record *dbox_rec;
+       struct dbox_map_transaction_context *map_trans;
+       ARRAY_TYPE(uint32_t) map_uids;
+       const void *data;
+       bool expunged, old_fs_access;
+       uint32_t seq;
+       int ret;
 
-               str_truncate(full_path, dir_len);
-               str_append(full_path, d->d_name);
-
-               /* trying to unlink() a directory gives either EPERM or EISDIR
-                  (non-POSIX). it doesn't really work anywhere in practise,
-                  so don't bother stat()ing the file first */
-               if (unlink(str_c(full_path)) == 0)
-                       unlinked_something = TRUE;
-               else if (errno != ENOENT && errno != EISDIR && errno != EPERM) {
-                       mailbox_list_set_critical(list, "unlink(%s) failed: %m",
-                                                 str_c(full_path));
+       old_fs_access = (list->flags & MAILBOX_LIST_FLAG_FULL_FS_ACCESS) != 0;
+       list->flags |= MAILBOX_LIST_FLAG_FULL_FS_ACCESS;
+       box = dbox_open(storage, path, MAILBOX_OPEN_IGNORE_ACLS |
+                       MAILBOX_OPEN_KEEP_RECENT);
+       if (!old_fs_access)
+               list->flags &= ~MAILBOX_LIST_FLAG_FULL_FS_ACCESS;
+       if (box == NULL)
+               return -1;
+       mbox = (struct dbox_mailbox *)box;
+
+       /* get a list of all map_uids in this mailbox */
+       i_array_init(&map_uids, 128);
+       hdr = mail_index_get_header(mbox->ibox.view);
+       for (seq = 1; seq <= hdr->messages_count; hdr++) {
+               mail_index_lookup_ext(mbox->ibox.view, seq, mbox->dbox_ext_id,
+                                     &data, &expunged);
+               dbox_rec = data;
+               if (dbox_rec == NULL) {
+                       /* no multi-mails */
+                       break;
                }
+               if (dbox_rec->map_uid != 0)
+                       array_append(&map_uids, &dbox_rec->map_uid, 1);
        }
 
-       if (closedir(dir) < 0) {
-               mailbox_list_set_critical(list, "closedir(%s) failed: %m",
-                                         path);
-       }
+       /* unreference the map_uids */
+       map_trans = dbox_map_transaction_begin(storage->map, FALSE);
+       ret = dbox_map_update_refcounts(map_trans, &map_uids, -1);
+       if (ret == 0)
+               ret = dbox_map_transaction_commit(map_trans);
+       dbox_map_transaction_free(&map_trans);
+       array_free(&map_uids);
+       mailbox_close(&box);
+       return ret;
+}
 
-       if (rmdir(path) == 0)
-               unlinked_something = TRUE;
-       else if (errno != ENOENT && errno != ENOTEMPTY) {
-               mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path);
-               return -1;
-       }
+static const char *dbox_get_trash_dest(const char *trash_dir)
+{
+       const char *path;
+       unsigned char randbuf[16];
+       struct stat st;
 
-       if (!unlinked_something) {
-               mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
-                       t_strdup_printf("Directory %s isn't empty, "
-                                       "can't delete it.", name));
-               return -1;
-       }
-       return 1;
+       do {
+               random_fill_weak(randbuf, sizeof(randbuf));
+               path = t_strconcat(trash_dir, "/",
+                       binary_to_hex(randbuf, sizeof(randbuf)), NULL);
+       } while (lstat(path, &st) == 0);
+       return path;
 }
 
 static int
@@ -516,7 +521,7 @@ dbox_list_delete_mailbox(struct mailbox_list *list, const char *name)
 {
        struct dbox_storage *storage = DBOX_LIST_CONTEXT(list);
        struct stat st;
-       const char *path, *alt_path;
+       const char *path, *alt_path, *trash_dir, *trash_dest;
        bool deleted = FALSE;
        int ret;
 
@@ -530,27 +535,51 @@ dbox_list_delete_mailbox(struct mailbox_list *list, const char *name)
        if (storage->list_module_ctx.super.delete_mailbox(list, name) < 0)
                return -1;
 
-       if (*list_set->mailbox_dir_name == '\0') {
-               *error_r = "dbox: MAILBOXDIR must not be empty";
-               return -1;
-       }
-
-       /* check if the mailbox actually exists */
        path = mailbox_list_get_path(list, name,
                                     MAILBOX_LIST_PATH_TYPE_MAILBOX);
-       if ((ret = dbox_delete_nonrecursive(list, path, name)) > 0) {
-               /* delete the mailbox first */
+       trash_dir = mailbox_list_get_path(list, NULL,
+                                         MAILBOX_LIST_PATH_TYPE_DIR);
+       trash_dir = t_strconcat(trash_dir, "/"DBOX_TRASH_DIR_NAME, NULL);
+       trash_dest = dbox_get_trash_dest(trash_dir);
+
+       /* first try renaming the actual mailbox to trash directory */
+       ret = rename(path, trash_dest);
+       if (ret < 0 && errno == ENOENT) {
+               /* either source mailbox doesn't exist or trash directory
+                  doesn't exist. try creating the trash and retrying. */
+               mode_t mode;
+               gid_t gid;
+
+               mailbox_list_get_dir_permissions(list, &mode, &gid);
+               if (mkdir_parents_chown(trash_dir, mode, (uid_t)-1, gid) < 0 &&
+                   errno != EEXIST) {
+                       mailbox_list_set_critical(list,
+                               "mkdir(%s) failed: %m", trash_dir);
+                       return -1;
+               }
+               ret = rename(path, trash_dest);
+       }
+       if (ret == 0) {
+               if (dbox_mailbox_unref_mails(storage, trash_dest) < 0) {
+                       /* we've already renamed it. there's no going back. */
+                       mailbox_list_set_internal_error(list);
+                       ret = -1;
+               }
+               if (unlink_directory(trash_dest, TRUE) < 0) {
+                       mailbox_list_set_critical(list,
+                               "unlink_directory(%s) failed: %m", trash_dest);
+                       ret = -1;
+               }
+               /* if there's an alt path, delete it too */
                alt_path = dbox_get_alt_path(storage, path);
                if (alt_path != NULL) {
-                       if (dbox_delete_nonrecursive(list, alt_path, name) < 0)
-                               return -1;
-               }
-               if (*list->set.maildir_name == '\0') {
-                       /* everything was in the one directory that was
-                          already deleted succesfully. */
-                       return 0;
+                       if (unlink_directory(alt_path, TRUE) < 0) {
+                               mailbox_list_set_critical(list,
+                                       "unlink_directory(%s) failed: %m", alt_path);
+                               ret = -1;
+                       }
                }
-               /* try to delete the directory also */
+               /* try to delete the parent directory also */
                deleted = TRUE;
                path = mailbox_list_get_path(list, name,
                                             MAILBOX_LIST_PATH_TYPE_DIR);
@@ -572,6 +601,7 @@ dbox_list_delete_mailbox(struct mailbox_list *list, const char *name)
                                                  path);
                        return -1;
                }
+               ret = 0;
        }
 
        alt_path = dbox_get_alt_path(storage, path);
@@ -579,10 +609,10 @@ dbox_list_delete_mailbox(struct mailbox_list *list, const char *name)
                (void)rmdir(alt_path);
 
        if (rmdir(path) == 0)
-               return 0;
+               return ret;
        else if (errno == ENOTEMPTY) {
                if (deleted)
-                       return 0;
+                       return ret;
                mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
                        t_strdup_printf("Directory %s isn't empty, "
                                        "can't delete it.", name));
index 349b1d676d882ce549945fa75467b264f94d3175..da0fa134e1848b439d6be4f9af20b3e5e7a2c9d3 100644 (file)
@@ -10,6 +10,7 @@
 #define DBOX_INDEX_PREFIX "dovecot.index"
 
 #define DBOX_MAILBOX_DIR_NAME "mailboxes"
+#define DBOX_TRASH_DIR_NAME "trash"
 #define DBOX_MAILDIR_NAME "dbox-Mails"
 #define DBOX_GLOBAL_INDEX_PREFIX "dovecot.map.index"
 #define DBOX_GLOBAL_DIR_NAME "storage"