summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/mail-autoexpunge.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/mail-autoexpunge.c')
-rw-r--r--src/lib-storage/mail-autoexpunge.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/lib-storage/mail-autoexpunge.c b/src/lib-storage/mail-autoexpunge.c
new file mode 100644
index 0000000..73e764b
--- /dev/null
+++ b/src/lib-storage/mail-autoexpunge.c
@@ -0,0 +1,267 @@
+/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mailbox-list-iter.h"
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "mail-user.h"
+#include "mail-autoexpunge.h"
+
+#define AUTOEXPUNGE_LOCK_FNAME "dovecot.autoexpunge.lock"
+#define AUTOEXPUNGE_BATCH_SIZE 1000
+
+static bool
+mailbox_autoexpunge_lock(struct mail_user *user, struct file_lock **lock)
+{
+ const char *error;
+ int ret;
+
+ if (*lock != NULL)
+ return TRUE;
+
+ /* Try to lock the autoexpunging. If the lock already exists, another
+ process is already busy with expunging, so we don't have to do it.
+ The easiest place where to store the lock file to is the home
+ directory, but allow autoexpunging to work even if we can't get
+ it. The lock isn't really required; it 1) improves performance
+ so that multiple processes won't do the same work unnecessarily,
+ and 2) it helps to avoid duplicate mails being added with
+ lazy_expunge. */
+ ret = mail_user_lock_file_create(user, AUTOEXPUNGE_LOCK_FNAME,
+ 0, lock, &error);
+ if (ret < 0) {
+ e_error(user->event, "autoexpunge: Couldn't create %s lock: %s",
+ AUTOEXPUNGE_LOCK_FNAME, error);
+ /* do autoexpunging anyway */
+ return TRUE;
+ } else if (ret == 0) {
+ /* another process is autoexpunging, so we don't need to. */
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+/* returns -1 on error, 0 when done, and 1 when there is more to do */
+static int
+mailbox_autoexpunge_batch(struct mailbox *box,
+ const unsigned int interval_time,
+ const unsigned int max_mails,
+ const time_t expire_time,
+ unsigned int *expunged_count)
+{
+ struct mailbox_transaction_context *t;
+ struct mail *mail;
+ const struct mail_index_header *hdr;
+ uint32_t seq;
+ time_t timestamp, last_rename_stamp = 0;
+ const void *data;
+ size_t size;
+ unsigned int count = 0;
+ bool done = FALSE, expunges_found = FALSE;
+ int ret = 0;
+
+ mail_index_get_header_ext(box->view, box->box_last_rename_stamp_ext_id,
+ &data, &size);
+
+ if (size >= sizeof(uint32_t)) {
+ last_rename_stamp = *(const uint32_t*)data;
+ if (last_rename_stamp > ioloop_time+60) {
+ /* Seems to be corrupted, or way too far into the
+ future. Don't trust it. */
+ last_rename_stamp = 0;
+ }
+ }
+
+ t = mailbox_transaction_begin(box, 0, "autoexpunge");
+ mail = mail_alloc(t, 0, NULL);
+
+ hdr = mail_index_get_header(box->view);
+
+ for (seq = 1; seq <= I_MIN(hdr->messages_count, AUTOEXPUNGE_BATCH_SIZE); seq++) {
+ mail_set_seq(mail, seq);
+ if (max_mails > 0 && hdr->messages_count - seq + 1 > max_mails) {
+ /* max_mails is still being reached -> expunge.
+ don't even check saved-dates before we're
+ below max_mails. */
+ mail_autoexpunge(mail);
+ count++;
+ } else if (interval_time == 0) {
+ /* only max_mails is used. nothing further to do. */
+ done = TRUE;
+ break;
+ } else if (mail_get_save_date(mail, &timestamp) >= 0) {
+ if (I_MAX(last_rename_stamp, timestamp) > expire_time) {
+ done = TRUE;
+ break;
+ }
+ mail_autoexpunge(mail);
+ count++;
+ } else if (mailbox_get_last_mail_error(box) == MAIL_ERROR_EXPUNGED) {
+ /* already expunged */
+ expunges_found = TRUE;
+ } else {
+ /* failed */
+ ret = -1;
+ break;
+ }
+ }
+ mail_free(&mail);
+ if (mailbox_transaction_commit(&t) < 0)
+ ret = -1;
+ else if (count > 0 || expunges_found) {
+ if (mailbox_sync(box, 0) < 0)
+ ret = -1;
+ *expunged_count += count;
+ }
+
+ if (ret < 0)
+ return -1;
+ return (done || count == 0) ? 0 : 1;
+}
+
+static int
+mailbox_autoexpunge(struct mailbox *box, unsigned int interval_time,
+ unsigned int max_mails, unsigned int *expunged_count)
+{
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ time_t expire_time;
+ int ret;
+
+ if ((unsigned int)ioloop_time < interval_time)
+ expire_time = 0;
+ else
+ expire_time = ioloop_time - interval_time;
+
+ /* first try to check quickly from mailbox list index if we should
+ bother opening this mailbox. */
+ if (mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) {
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) {
+ /* autocreated mailbox doesn't exist yet */
+ return 0;
+ }
+ return -1;
+ }
+ if (interval_time == 0 && status.messages <= max_mails)
+ return 0;
+
+ if (max_mails == 0 || status.messages <= max_mails) {
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_FIRST_SAVE_DATE,
+ &metadata) < 0)
+ return -1;
+ 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)
+ return -1;
+
+ do {
+ ret = mailbox_autoexpunge_batch(box, interval_time, max_mails,
+ expire_time, expunged_count);
+ } while (ret > 0);
+
+ return ret;
+}
+
+static void
+mailbox_autoexpunge_set(struct mail_namespace *ns, const char *vname,
+ unsigned int autoexpunge,
+ unsigned int autoexpunge_max_mails,
+ unsigned int *expunged_count)
+{
+ struct mailbox *box;
+
+ /* autoexpunge is configured by admin, so we can safely ignore
+ any ACLs the user might normally have against expunging in
+ the mailbox. */
+ box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_IGNORE_ACLS);
+ if (mailbox_autoexpunge(box, autoexpunge, autoexpunge_max_mails,
+ expunged_count) < 0) {
+ e_error(box->event, "Failed to autoexpunge: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ mailbox_free(&box);
+}
+
+static void
+mailbox_autoexpunge_wildcards(struct mail_namespace *ns,
+ const struct mailbox_settings *set,
+ unsigned int *expunged_count)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ const char *iter_name;
+
+ iter_name = t_strconcat(ns->prefix, set->name, NULL);
+ iter = mailbox_list_iter_init(ns->list, iter_name,
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
+ mailbox_autoexpunge_set(ns, info->vname, set->autoexpunge,
+ set->autoexpunge_max_mails,
+ expunged_count);
+ } T_END;
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ e_error(ns->user->event,
+ "Failed to iterate autoexpunge mailboxes '%s': %s",
+ iter_name, mailbox_list_get_last_internal_error(ns->list, NULL));
+ }
+}
+
+static bool
+mail_namespace_autoexpunge(struct mail_namespace *ns, struct file_lock **lock,
+ unsigned int *expunged_count)
+{
+ struct mailbox_settings *box_set;
+ const char *vname;
+
+ if (!array_is_created(&ns->set->mailboxes))
+ return TRUE;
+
+ array_foreach_elem(&ns->set->mailboxes, box_set) {
+ if (box_set->autoexpunge == 0 &&
+ box_set->autoexpunge_max_mails == 0)
+ continue;
+
+ if (!mailbox_autoexpunge_lock(ns->user, lock))
+ return FALSE;
+
+ if (strpbrk(box_set->name, "*?") != NULL)
+ mailbox_autoexpunge_wildcards(ns, box_set, expunged_count);
+ else {
+ if (box_set->name[0] == '\0' && ns->prefix_len > 0 &&
+ ns->prefix[ns->prefix_len-1] == mail_namespace_get_sep(ns))
+ vname = t_strndup(ns->prefix, ns->prefix_len - 1);
+ else
+ vname = t_strconcat(ns->prefix, box_set->name, NULL);
+ mailbox_autoexpunge_set(ns, vname, box_set->autoexpunge,
+ box_set->autoexpunge_max_mails,
+ expunged_count);
+ }
+ }
+ return TRUE;
+}
+
+unsigned int mail_user_autoexpunge(struct mail_user *user)
+{
+ struct file_lock *lock = NULL;
+ struct mail_namespace *ns;
+ unsigned int expunged_count = 0;
+ struct event_reason *reason =
+ event_reason_begin("storage:autoexpunge");
+
+ for (ns = user->namespaces; ns != NULL; ns = ns->next) {
+ if (ns->alias_for == NULL) {
+ if (!mail_namespace_autoexpunge(ns, &lock, &expunged_count))
+ break;
+ }
+ }
+ event_reason_end(&reason);
+ file_lock_free(&lock);
+ return expunged_count;
+}