]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added mailbox { autoexpunge } setting.
authorTimo Sirainen <tss@iki.fi>
Fri, 27 Nov 2015 11:59:22 +0000 (13:59 +0200)
committerTimo Sirainen <tss@iki.fi>
Fri, 27 Nov 2015 11:59:22 +0000 (13:59 +0200)
This can be used to automatically expunge mails from specified mailboxes
after they're old enough. The expunges are done when the user is being
deinitialized. mailbox_list_index=yes should be enabled to have the best
performance with this setting.

Example:

namespace inbox {
  mailbox Spam {
    auto = create
    special_use = \Junk
    autoexpunge = 30d
  }
}

src/lib-storage/Makefile.am
src/lib-storage/list/mailbox-list-index-status.c
src/lib-storage/list/mailbox-list-index.h
src/lib-storage/mail-autoexpunge.c [new file with mode: 0644]
src/lib-storage/mail-autoexpunge.h [new file with mode: 0644]
src/lib-storage/mail-storage-private.h
src/lib-storage/mail-storage-settings.c
src/lib-storage/mail-storage-settings.h
src/lib-storage/mail-storage.h
src/lib-storage/mail-user.c

index 5492b9914ce9456427e2c957c5a6e46495243d08..f04b18e871d6f58897141d50bbac946aa0e5f756 100644 (file)
@@ -24,6 +24,7 @@ libstorage_la_SOURCES = \
        fail-mailbox.c \
        fail-mail.c \
        mail.c \
+       mail-autoexpunge.c \
        mail-copy.c \
        mail-error.c \
        mail-namespace.c \
index 07a4ef95815942f010ce44acf7f1b29d9ad15338..ddd2e1107510857da30d1552e6c3abb9d1e9b80d 100644 (file)
@@ -15,11 +15,13 @@ struct index_list_changes {
        guid_128_t guid;
        uint32_t seq;
        struct mailbox_index_vsize vsize;
+       uint32_t first_uid;
 
        bool rec_changed;
        bool msgs_changed;
        bool hmodseq_changed;
        bool vsize_changed;
+       bool first_saved_changed;
 };
 
 struct index_list_storage_module index_list_storage_module =
@@ -267,6 +269,30 @@ static int index_list_get_cached_vsize(struct mailbox *box, uoff_t *vsize_r)
        return ret;
 }
 
+static int
+index_list_get_cached_first_saved(struct mailbox *box,
+                                 struct mailbox_index_first_saved *first_saved_r)
+{
+       struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
+       struct mail_index_view *view;
+       const void *data;
+       bool expunged;
+       uint32_t seq;
+       int ret;
+
+       memset(first_saved_r, 0, sizeof(*first_saved_r));
+
+       if ((ret = index_list_open_view(box, &view, &seq)) <= 0)
+               return ret;
+
+       mail_index_lookup_ext(view, seq, ilist->first_saved_ext_id,
+                             &data, &expunged);
+       if (data != NULL)
+               memcpy(first_saved_r, data, sizeof(*first_saved_r));
+       mail_index_view_close(&view);
+       return first_saved_r->timestamp != 0 ? 1 : 0;
+}
+
 static int
 index_list_try_get_metadata(struct mailbox *box,
                            enum mailbox_metadata_items items,
@@ -286,7 +312,8 @@ index_list_try_get_metadata(struct mailbox *box,
        /* see if we have a chance of fulfilling this without opening
           the mailbox. */
        noncached_items = items & ~(MAILBOX_METADATA_GUID |
-                                   MAILBOX_METADATA_VIRTUAL_SIZE);
+                                   MAILBOX_METADATA_VIRTUAL_SIZE |
+                                   MAILBOX_METADATA_FIRST_SAVE_DATE);
        if ((noncached_items & MAILBOX_METADATA_PHYSICAL_SIZE) != 0 &&
            box->mail_vfuncs->get_physical_size ==
            box->mail_vfuncs->get_virtual_size)
@@ -306,6 +333,15 @@ index_list_try_get_metadata(struct mailbox *box,
                if ((items & MAILBOX_METADATA_PHYSICAL_SIZE) != 0)
                        metadata_r->physical_size = metadata_r->virtual_size;
        }
+       if ((items & MAILBOX_METADATA_FIRST_SAVE_DATE) != 0) {
+               struct mailbox_index_first_saved first_saved;
+
+               if ((ret = index_list_get_cached_first_saved(box, &first_saved)) <= 0)
+                       return ret;
+               metadata_r->first_save_date =
+                       first_saved.timestamp == (uint32_t)-1 ? (time_t)-1 :
+                       first_saved.timestamp;
+       }
        return 1;
 }
 
@@ -386,6 +422,33 @@ index_list_update_fill_changes(struct mailbox *box,
        return TRUE;
 }
 
+static void
+index_list_first_saved_update_changes(struct mailbox *box,
+                                     struct mail_index_view *list_view,
+                                     struct index_list_changes *changes)
+{
+       struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
+       struct mailbox_index_first_saved first_saved;
+       const void *data;
+       bool expunged;
+
+       mail_index_lookup_ext(list_view, changes->seq,
+                             ilist->first_saved_ext_id, &data, &expunged);
+       if (data == NULL)
+               memset(&first_saved, 0, sizeof(first_saved));
+       else
+               memcpy(&first_saved, data, sizeof(first_saved));
+       if (mail_index_view_get_messages_count(box->view) > 0)
+               mail_index_lookup_uid(box->view, 1, &changes->first_uid);
+       if (first_saved.uid == 0 && first_saved.timestamp == 0) {
+               /* first time setting this */
+               changes->first_saved_changed = TRUE;
+       } else {
+               changes->first_saved_changed =
+                       changes->first_uid != first_saved.uid;
+       }
+}
+
 static bool
 index_list_has_changed(struct mailbox *box, struct mail_index_view *list_view,
                       struct index_list_changes *changes)
@@ -428,11 +491,56 @@ index_list_has_changed(struct mailbox *box, struct mail_index_view *list_view,
        }
        if (memcmp(&old_vsize, &changes->vsize, sizeof(old_vsize)) != 0)
                changes->vsize_changed = TRUE;
+       index_list_first_saved_update_changes(box, list_view, changes);
 
        return changes->rec_changed || changes->msgs_changed ||
-               changes->hmodseq_changed || changes->vsize_changed;
+               changes->hmodseq_changed || changes->vsize_changed ||
+               changes->first_saved_changed;
 }
 
+static void
+index_list_update_first_saved(struct mailbox *box,
+                             struct mail_index_transaction *list_trans,
+                             const struct index_list_changes *changes)
+{
+       struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
+       struct mailbox_transaction_context *t;
+       struct mail *mail;
+       struct mailbox_index_first_saved first_saved;
+       uint32_t seq, messages_count;
+       time_t save_date;
+       int ret = 0;
+
+       memset(&first_saved, 0, sizeof(first_saved));
+       first_saved.uid = changes->first_uid;
+       first_saved.timestamp = (uint32_t)-1;
+
+       if (changes->first_uid != 0) {
+               t = mailbox_transaction_begin(box, 0);
+               mail = mail_alloc(t, MAIL_FETCH_SAVE_DATE, NULL);
+               messages_count = mail_index_view_get_messages_count(box->view);
+               for (seq = 1; seq <= messages_count; seq++) {
+                       mail_set_seq(mail, seq);
+                       if (mail_get_save_date(mail, &save_date) == 0) {
+                               first_saved.timestamp = save_date;
+                               break;
+                       }
+                       if (mailbox_get_last_mail_error(box) != MAIL_ERROR_EXPUNGED) {
+                               ret = -1;
+                               break;
+                       }
+               }
+               mail_free(&mail);
+               (void)mailbox_transaction_commit(&t);
+       }
+       if (ret == 0) {
+               mail_index_update_ext(list_trans, changes->seq,
+                                     ilist->first_saved_ext_id,
+                                     &first_saved, NULL);
+       }
+}
+
+
 static void
 index_list_update(struct mailbox *box, struct mail_index_view *list_view,
                  struct mail_index_transaction *list_trans,
@@ -480,6 +588,8 @@ index_list_update(struct mailbox *box, struct mail_index_view *list_view,
                                      ilist->vsize_ext_id,
                                      &changes->vsize, NULL);
        }
+       if (changes->first_saved_changed)
+               index_list_update_first_saved(box, list_trans, changes);
 }
 
 static int index_list_update_mailbox(struct mailbox *box)
@@ -705,4 +815,7 @@ void mailbox_list_index_status_init_finish(struct mailbox_list *list)
        ilist->vsize_ext_id =
                mail_index_ext_register(ilist->index, "vsize", 0,
                        sizeof(struct mailbox_index_vsize), sizeof(uint64_t));
+       ilist->first_saved_ext_id =
+               mail_index_ext_register(ilist->index, "1saved", 0,
+                       sizeof(struct mailbox_index_first_saved), sizeof(uint32_t));
 }
index 21ed1296a29343ce3b2ac691cf4d9cce3a081cbb..6fe9ccd2985d53a7a6e834ec58762f38103afa4a 100644 (file)
@@ -89,7 +89,7 @@ struct mailbox_list_index {
        const char *path;
        struct mail_index *index;
        uint32_t ext_id, msgs_ext_id, hmodseq_ext_id, subs_hdr_ext_id;
-       uint32_t vsize_ext_id;
+       uint32_t vsize_ext_id, first_saved_ext_id;
        struct timeval last_refresh_timeval;
 
        pool_t mailbox_pool;
diff --git a/src/lib-storage/mail-autoexpunge.c b/src/lib-storage/mail-autoexpunge.c
new file mode 100644 (file)
index 0000000..a724142
--- /dev/null
@@ -0,0 +1,93 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "mail-user.h"
+#include "mail-autoexpunge.h"
+
+static int mailbox_autoexpunge(struct mailbox *box, time_t expire_time)
+{
+       struct mailbox_transaction_context *t;
+       struct mail *mail;
+       struct mailbox_metadata metadata;
+       const struct mail_index_header *hdr;
+       uint32_t seq;
+       time_t timestamp;
+       int ret = 0;
+
+       /* first try to check quickly from mailbox list index if we should
+          bother opening this mailbox. */
+       if (mailbox_get_metadata(box, MAILBOX_METADATA_FIRST_SAVE_DATE,
+                                &metadata) == 0) {
+               if (metadata.first_save_date == (time_t)-1 ||
+                   metadata.first_save_date > expire_time)
+                       return 0;
+       }
+
+       if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0) {
+               if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) {
+                       /* autocreated mailbox doesn't exist yet */
+                       return 0;
+               }
+               return -1;
+       }
+
+       t = mailbox_transaction_begin(box, 0);
+       mail = mail_alloc(t, 0, NULL);
+
+       hdr = mail_index_get_header(box->view);
+       for (seq = 1; seq <= hdr->messages_count; seq++) {
+               mail_set_seq(mail, seq);
+               if (mail_get_save_date(mail, &timestamp) == 0) {
+                       if (timestamp > expire_time)
+                               break;
+                       mail_expunge(mail);
+               } else if (mailbox_get_last_mail_error(box) == MAIL_ERROR_EXPUNGED) {
+                       /* already expunged */
+               } else {
+                       /* failed */
+                       ret = -1;
+                       break;
+               }
+       }
+       mail_free(&mail);
+       if (mailbox_transaction_commit(&t) < 0)
+               ret = -1;
+       return ret;
+}
+
+static void mail_namespace_autoexpunge(struct mail_namespace *ns)
+{
+       struct mailbox_settings *const *box_set;
+       struct mailbox *box;
+       time_t expire_time;
+
+       if (!array_is_created(&ns->set->mailboxes))
+               return;
+
+       array_foreach(&ns->set->mailboxes, box_set) {
+               if ((*box_set)->autoexpunge == 0 ||
+                   ioloop_time < (*box_set)->autoexpunge)
+                       continue;
+               expire_time = ioloop_time - (*box_set)->autoexpunge;
+               box = mailbox_alloc(ns->list, (*box_set)->name, 0);
+               if (mailbox_autoexpunge(box, expire_time) < 0) {
+                       i_error("Failed to autoexpunge mailbox '%s': %s",
+                               mailbox_get_vname(box),
+                               mailbox_get_last_error(box, NULL));
+               }
+               mailbox_free(&box);
+       }
+}
+
+void mail_user_autoexpunge(struct mail_user *user)
+{
+       struct mail_namespace *ns;
+
+       for (ns = user->namespaces; ns != NULL; ns = ns->next) {
+               if (ns->alias_for == NULL)
+                       mail_namespace_autoexpunge(ns);
+       }
+}
diff --git a/src/lib-storage/mail-autoexpunge.h b/src/lib-storage/mail-autoexpunge.h
new file mode 100644 (file)
index 0000000..f1b9b13
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef MAIL_AUTOEXPUNGE_H
+#define MAIL_AUTOEXPUNGE_H
+
+void mail_user_autoexpunge(struct mail_user *user);
+
+#endif
index acce8216dce349ca978a4600d145931c1132c765..983666e62da42adf9c9402050be44e0f5fa48864 100644 (file)
@@ -271,6 +271,11 @@ struct mailbox_index_vsize {
        uint32_t message_count;
 };
 
+struct mailbox_index_first_saved {
+       uint32_t uid;
+       uint32_t timestamp;
+};
+
 struct mailbox {
        const char *name;
        /* mailbox's virtual name (from mail_namespace_get_vname()) */
index 13e265315c6c548bbae9b78d02ec31912454fab5..a35de74b85675c890d77f74697c34a3dee435577 100644 (file)
@@ -126,6 +126,7 @@ static const struct setting_define mailbox_setting_defines[] = {
        DEF(SET_STR, special_use),
        DEF(SET_STR, driver),
        DEF(SET_STR, comment),
+       DEF(SET_TIME, autoexpunge),
 
        SETTING_DEFINE_LIST_END
 };
@@ -137,7 +138,8 @@ const struct mailbox_settings mailbox_default_settings = {
                MAILBOX_SET_AUTO_SUBSCRIBE,
        .special_use = "",
        .driver = "",
-       .comment = ""
+       .comment = "",
+       .autoexpunge = 0
 };
 
 const struct setting_parser_info mailbox_setting_parser_info = {
index fd23a2ab7d6387671b055e3dbddb49774ffafe89..44196a2147d4b03937efb098ddc4218160e997da 100644 (file)
@@ -81,6 +81,7 @@ struct mailbox_settings {
        const char *special_use;
        const char *driver;
        const char *comment;
+       unsigned int autoexpunge;
 };
 
 struct mail_user_settings {
index e2b1ee15d23616f555d9700f5228eddf757d13ff..daec406fe151287d0a273bf18b9955e14aaaabca 100644 (file)
@@ -91,10 +91,12 @@ enum mailbox_metadata_items {
        MAILBOX_METADATA_CACHE_FIELDS           = 0x04,
        MAILBOX_METADATA_PRECACHE_FIELDS        = 0x08,
        MAILBOX_METADATA_BACKEND_NAMESPACE      = 0x10,
-       MAILBOX_METADATA_PHYSICAL_SIZE          = 0x20
+       MAILBOX_METADATA_PHYSICAL_SIZE          = 0x20,
+       MAILBOX_METADATA_FIRST_SAVE_DATE        = 0x40
        /* metadata items that require mailbox to be synced at least once. */
 #define MAILBOX_METADATA_SYNC_ITEMS \
-       (MAILBOX_METADATA_VIRTUAL_SIZE | MAILBOX_METADATA_PHYSICAL_SIZE)
+       (MAILBOX_METADATA_VIRTUAL_SIZE | MAILBOX_METADATA_PHYSICAL_SIZE | \
+        MAILBOX_METADATA_FIRST_SAVE_DATE)
 };
 
 enum mailbox_search_result_flags {
@@ -275,10 +277,15 @@ struct mailbox_metadata {
        uint64_t virtual_size;
        /* sum of physical size of all messages in mailbox */
        uint64_t physical_size;
+       /* timestamp of when the first message was saved.
+          (time_t)-1 if there are no mails in the mailbox. */
+       time_t first_save_date;
+
        /* Fields that have "temp" or "yes" caching decision. */
        const ARRAY_TYPE(mailbox_cache_field) *cache_fields;
        /* Fields that should be precached */
        enum mail_fetch_field precache_fields;
+
        /* imapc backend returns this based on the remote NAMESPACE reply,
           while currently other backends return "" and type the same as the
           mailbox's real namespace type */
index dc37285a93d02281f67bad5bad7063e4de5a4070..0d14d9f15a93ca5043a122936b5f88db04c67b3e 100644 (file)
@@ -21,6 +21,7 @@
 #include "mail-storage-service.h"
 #include "mail-namespace.h"
 #include "mail-storage.h"
+#include "mail-autoexpunge.h"
 #include "mail-user.h"
 
 
@@ -163,6 +164,8 @@ void mail_user_unref(struct mail_user **_user)
                return;
        }
 
+       mail_user_autoexpunge(user);
+
        user->deinitializing = TRUE;
 
        /* call deinit() with refcount=1, otherwise we may assert-crash in