Mercurial > dovecot > original-hg > dovecot-1.2
changeset 4913:dea1c8fa53f4 HEAD
Added lazy expunge plugin.
author | Timo Sirainen <timo.sirainen@movial.fi> |
---|---|
date | Sat, 16 Dec 2006 02:09:19 +0200 |
parents | b08e63f6dcfd |
children | 25597644067a |
files | configure.in dovecot-example.conf src/plugins/Makefile.am src/plugins/lazy-expunge/.cvsignore src/plugins/lazy-expunge/Makefile.am src/plugins/lazy-expunge/lazy-expunge-plugin.c src/plugins/lazy-expunge/lazy-expunge-plugin.h |
diffstat | 7 files changed, 561 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Sat Dec 16 01:50:45 2006 +0200 +++ b/configure.in Sat Dec 16 02:09:19 2006 +0200 @@ -1881,6 +1881,7 @@ src/plugins/fts/Makefile src/plugins/fts-lucene/Makefile src/plugins/fts-squat/Makefile +src/plugins/lazy-expunge/Makefile src/plugins/quota/Makefile src/plugins/imap-quota/Makefile src/plugins/trash/Makefile
--- a/dovecot-example.conf Sat Dec 16 01:50:45 2006 +0200 +++ b/dovecot-example.conf Sat Dec 16 02:09:19 2006 +0200 @@ -1044,4 +1044,12 @@ # dovecot --exec-mail ext /usr/libexec/dovecot/expire-tool #expire = Trash 7 Spam 30 #expire_dict = db:/var/lib/dovecot/expire.db + + # Lazy expunge plugin. Currently works only with maildirs. When a user + # expunges mails, the mails are moved to a mailbox in another namespace + # (1st). When a mailbox is deleted, the mailbox is moved to another namespace + # (2nd) as well. Also if the deleted mailbox had any expunged messages, + # they're moved to a 3rd namespace. The mails won't be counted in quota, + # and they're not deleted automatically (use a cronjob or something). + #lazy_expunge = .EXPUNGED/ .DELETED/ .DELETED/.EXPUNGED/ }
--- a/src/plugins/Makefile.am Sat Dec 16 01:50:45 2006 +0200 +++ b/src/plugins/Makefile.am Sat Dec 16 02:09:19 2006 +0200 @@ -6,4 +6,7 @@ FTS_LUCENE = fts-lucene endif -SUBDIRS = acl convert expire fts fts-squat quota imap-quota trash $(ZLIB) $(FTS_LUCENE) +SUBDIRS = \ + acl convert expire fts fts-squat lazy-expunge \ + quota imap-quota trash \ + $(ZLIB) $(FTS_LUCENE)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/lazy-expunge/.cvsignore Sat Dec 16 02:09:19 2006 +0200 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/lazy-expunge/Makefile.am Sat Dec 16 02:09:19 2006 +0200 @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-storage/index/maildir \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/quota + +lib02_lazy_expunge_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib02_lazy_expunge_plugin.la + +lib02_lazy_expunge_plugin_la_SOURCES = \ + lazy-expunge-plugin.c + +noinst_HEADERS = \ + lazy-expunge-plugin.h + +install-exec-local: + for d in imap pop3; do \ + $(mkdir_p) $(DESTDIR)$(moduledir)/$$d; \ + rm -f $(DESTDIR)$(moduledir)/$$d/lib02_lazy_expunge_plugin.so; \ + $(LN_S) ../lib02_lazy_expunge_plugin.so $(DESTDIR)$(moduledir)/$$d; \ + done +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.c Sat Dec 16 02:09:19 2006 +0200 @@ -0,0 +1,504 @@ +/* Copyright (C) 2006 Timo Sirainen */ + +#include "common.h" +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "seq-range-array.h" +#include "maildir-storage.h" +#include "client.h" +#include "namespace.h" +#include "lazy-expunge-plugin.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <dirent.h> +#include <time.h> + +#define LAZY_EXPUNGE_CONTEXT(obj) \ + *((void **)array_idx_modifiable(&(obj)->module_contexts, \ + lazy_expunge_storage_module_id)) + +enum lazy_namespace { + LAZY_NAMESPACE_EXPUNGE, + LAZY_NAMESPACE_DELETE, + LAZY_NAMESPACE_DELETE_EXPUNGE, + + LAZY_NAMESPACE_COUNT +}; + +struct lazy_expunge_mail_storage { + struct mail_storage_vfuncs super; +}; + +struct lazy_expunge_mailbox { + struct mailbox_vfuncs super; +}; + +struct lazy_expunge_transaction { + ARRAY_TYPE(seq_range) expunge_seqs; + struct mailbox *expunge_box; +}; + +struct lazy_expunge_mail { + struct mail_vfuncs super; +}; + +static void (*lazy_expunge_next_hook_mail_storage_created) + (struct mail_storage *storage); +static void (*lazy_expunge_next_hook_client_created)(struct client **client); + +static unsigned int lazy_expunge_storage_module_id = 0; +static bool lazy_expunge_storage_module_id_set = FALSE; + +static struct namespace *lazy_namespaces[LAZY_NAMESPACE_COUNT]; + +static struct mailbox * +mailbox_open_or_create(struct mail_storage *storage, const char *name) +{ + struct mailbox *box; + bool syntax, temp; + + box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST | + MAILBOX_OPEN_KEEP_RECENT); + if (box != NULL) + return box; + + (void)mail_storage_get_last_error(storage, &syntax, &temp); + if (syntax || temp) + return NULL; + + /* probably the mailbox just doesn't exist. try creating it. */ + if (mail_storage_mailbox_create(storage, name, FALSE) < 0) + return NULL; + + /* and try opening again */ + box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST | + MAILBOX_OPEN_KEEP_RECENT); + return box; +} + +static int lazy_expunge_mail_expunge(struct mail *_mail) +{ + struct lazy_expunge_transaction *lt = + LAZY_EXPUNGE_CONTEXT(_mail->transaction); + struct mail_storage *deststorage; + + if (lt->expunge_box == NULL) { + deststorage = lazy_namespaces[LAZY_NAMESPACE_EXPUNGE]->storage; + lt->expunge_box = mailbox_open_or_create(deststorage, + _mail->box->name); + if (lt->expunge_box == NULL) { + mail_storage_set_critical(_mail->box->storage, + "lazy_expunge: Couldn't open expunge mailbox"); + return -1; + } + } + + seq_range_array_add(<->expunge_seqs, 32, _mail->seq); + return 0; +} + +static struct mailbox_transaction_context * +lazy_expunge_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags) +{ + struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(box); + struct mailbox_transaction_context *t; + struct lazy_expunge_transaction *lt; + + t = qbox->super.transaction_begin(box, flags); + lt = i_new(struct lazy_expunge_transaction, 1); + + array_idx_set(&t->module_contexts, lazy_expunge_storage_module_id, <); + return t; +} + +struct lazy_expunge_move_context { + string_t *path; + unsigned int dir_len; +}; + +static int lazy_expunge_move(struct maildir_mailbox *mbox __attr_unused__, + const char *path, void *context) +{ + struct lazy_expunge_move_context *ctx = context; + const char *p; + + str_truncate(ctx->path, ctx->dir_len); + p = strrchr(path, '/'); + str_append(ctx->path, p == NULL ? path : p + 1); + + if (rename(path, str_c(ctx->path)) == 0) + return 1; + return errno == ENOENT ? 0 : -1; +} + +static int lazy_expunge_move_expunges(struct mailbox *srcbox, + struct lazy_expunge_transaction *lt) +{ + struct maildir_mailbox *msrcbox = (struct maildir_mailbox *)srcbox; + struct mailbox_transaction_context *trans; + struct index_transaction_context *itrans; + struct lazy_expunge_move_context ctx; + const struct seq_range *range; + unsigned int i, count; + const char *dir; + uint32_t seq; + bool is_file; + int ret = 0; + + dir = mail_storage_get_mailbox_path(lt->expunge_box->storage, + lt->expunge_box->name, &is_file); + dir = t_strconcat(dir, "/cur/", NULL); + + ctx.path = str_new(default_pool, 256); + str_append(ctx.path, dir); + ctx.dir_len = str_len(ctx.path); + + trans = mailbox_transaction_begin(srcbox, + MAILBOX_TRANSACTION_FLAG_EXTERNAL); + itrans = (struct index_transaction_context *)trans; + + range = array_get(<->expunge_seqs, &count); + for (i = 0; i < count && ret == 0; i++) { + for (seq = range[i].seq1; seq <= range[i].seq2; seq++) { + ret = maildir_file_do(msrcbox, seq, lazy_expunge_move, + &ctx); + if (ret < 0) + break; + + mail_index_expunge(itrans->trans, seq); + } + } + + if (mailbox_transaction_commit(&trans, 0) < 0) + ret = -1; + + str_free(&ctx.path); + return ret; +} + +static void lazy_expunge_transaction_free(struct lazy_expunge_transaction *lt) +{ + if (lt->expunge_box != NULL) + mailbox_close(<->expunge_box); + if (array_is_created(<->expunge_seqs)) + array_free(<->expunge_seqs); + i_free(lt); +} + +static int +lazy_expunge_transaction_commit(struct mailbox_transaction_context *ctx, + enum mailbox_sync_flags flags) +{ + struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(ctx->box); + struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT(ctx); + struct mailbox *srcbox = ctx->box; + int ret; + + ret = qbox->super.transaction_commit(ctx, flags); + + if (ret == 0 && array_is_created(<->expunge_seqs)) + ret = lazy_expunge_move_expunges(srcbox, lt); + + lazy_expunge_transaction_free(lt); + return ret; +} + +static void +lazy_expunge_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(ctx->box); + struct lazy_expunge_transaction *lt = LAZY_EXPUNGE_CONTEXT(ctx); + + qbox->super.transaction_rollback(ctx); + lazy_expunge_transaction_free(lt); +} + +static struct mail * +lazy_expunge_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct lazy_expunge_mailbox *qbox = LAZY_EXPUNGE_CONTEXT(t->box); + struct lazy_expunge_mail *lmail; + struct mail *_mail; + struct mail_private *mail; + + _mail = qbox->super.mail_alloc(t, wanted_fields, wanted_headers); + mail = (struct mail_private *)_mail; + + lmail = p_new(mail->pool, struct lazy_expunge_mail, 1); + lmail->super = mail->v; + + mail->v.expunge = lazy_expunge_mail_expunge; + array_idx_set(&mail->module_contexts, + lazy_expunge_storage_module_id, &lmail); + return _mail; +} + +static struct mailbox * +lazy_expunge_mailbox_open(struct mail_storage *storage, const char *name, + struct istream *input, enum mailbox_open_flags flags) +{ + struct lazy_expunge_mail_storage *lstorage = + LAZY_EXPUNGE_CONTEXT(storage); + struct mailbox *box; + struct lazy_expunge_mailbox *qbox; + + box = lstorage->super.mailbox_open(storage, name, input, flags); + if (box == NULL) + return NULL; + + qbox = p_new(box->pool, struct lazy_expunge_mailbox, 1); + qbox->super = box->v; + + box->v.transaction_begin = lazy_expunge_transaction_begin; + box->v.transaction_commit = lazy_expunge_transaction_commit; + box->v.transaction_rollback = lazy_expunge_transaction_rollback; + box->v.mail_alloc = lazy_expunge_mail_alloc; + array_idx_set(&box->module_contexts, + lazy_expunge_storage_module_id, &qbox); + return box; +} + +static int dir_move_or_merge(struct mail_storage *storage, + const char *srcdir, const char *destdir) +{ + DIR *dir; + struct dirent *dp; + string_t *src_path, *dest_path; + unsigned int src_dirlen, dest_dirlen; + int ret = 0; + + if (rename(srcdir, destdir) == 0 || errno == ENOENT) + return 0; + + if (!EDESTDIREXISTS(errno)) { + mail_storage_set_critical(storage, + "rename(%s, %s) failed: %m", srcdir, destdir); + } + + /* rename all the files separately */ + dir = opendir(srcdir); + if (dir == NULL) { + mail_storage_set_critical(storage, + "opendir(%s) failed: %m", srcdir); + return -1; + } + + src_path = t_str_new(512); + dest_path = t_str_new(512); + + str_append(src_path, srcdir); + str_append(dest_path, destdir); + src_dirlen = str_len(src_path); + dest_dirlen = str_len(dest_path); + + while ((dp = readdir(dir)) != NULL) { + str_truncate(src_path, src_dirlen); + str_append(src_path, dp->d_name); + str_truncate(dest_path, dest_dirlen); + str_append(dest_path, dp->d_name); + + if (rename(str_c(src_path), str_c(dest_path)) < 0 && + errno != ENOENT) { + mail_storage_set_critical(storage, + "rename(%s, %s) failed: %m", + str_c(src_path), str_c(dest_path)); + ret = -1; + } + } + if (closedir(dir) < 0) { + mail_storage_set_critical(storage, + "closedir(%s) failed: %m", srcdir); + ret = -1; + } + if (ret == 0) { + if (rmdir(srcdir) < 0) { + mail_storage_set_critical(storage, + "rmdir(%s) failed: %m", srcdir); + ret = -1; + } + } + return ret; +} + +static int +mailbox_move(struct mail_storage *src_storage, const char *src_name, + struct mail_storage *dest_storage, const char **_dest_name) +{ + const char *dest_name = *_dest_name; + const char *srcdir, *src2dir, *src3dir, *destdir; + bool is_file; + + srcdir = mail_storage_get_mailbox_path(src_storage, src_name, &is_file); + destdir = mail_storage_get_mailbox_path(dest_storage, dest_name, + &is_file); + while (rename(srcdir, destdir) < 0) { + if (errno == ENOENT) + return 0; + + if (!EDESTDIREXISTS(errno)) { + mail_storage_set_critical(src_storage, + "rename(%s, %s) failed: %m", srcdir, destdir); + return -1; + } + + /* mailbox is being deleted multiple times per second. + update the filename. */ + dest_name = t_strdup_printf("%s-%04u", *_dest_name, + (uint32_t)random()); + destdir = mail_storage_get_mailbox_path(dest_storage, dest_name, + &is_file); + } + + t_push(); + src2dir = mail_storage_get_mailbox_control_dir(src_storage, src_name); + if (strcmp(src2dir, srcdir) != 0) { + destdir = mail_storage_get_mailbox_control_dir(dest_storage, + dest_name); + (void)dir_move_or_merge(src_storage, srcdir, destdir); + } + src3dir = mail_storage_get_mailbox_index_dir(src_storage, src_name); + if (strcmp(src3dir, srcdir) != 0 && strcmp(src3dir, src2dir) != 0) { + destdir = mail_storage_get_mailbox_index_dir(dest_storage, + dest_name); + (void)dir_move_or_merge(src_storage, srcdir, destdir); + } + t_pop(); + + *_dest_name = dest_name; + return 1; +} + +static int +lazy_expunge_mailbox_delete(struct mail_storage *storage, const char *name) +{ + struct mail_storage *dest_storage; + enum mailbox_name_status status; + const char *destname; + struct tm *tm; + char timestamp[256]; + int ret; + + mail_storage_clear_error(storage); + + /* first do the normal sanity checks */ + if (strcmp(name, "INBOX") == 0) { + mail_storage_set_error(storage, "INBOX can't be deleted."); + return -1; + } + + if (mailbox_list_get_mailbox_name_status(storage->list, name, + &status) < 0) + return -1; + if (status == MAILBOX_NAME_INVALID) { + mail_storage_set_error(storage, "Invalid mailbox name"); + return -1; + } + + /* destination mailbox name needs to contain a timestamp */ + tm = localtime(&ioloop_time); + if (strftime(timestamp, sizeof(timestamp), "%Y%m%d-%H%M%S", tm) == 0) + strocpy(timestamp, dec2str(ioloop_time), sizeof(timestamp)); + destname = t_strconcat(name, "-", timestamp, NULL); + + /* first move the actual mailbox */ + dest_storage = lazy_namespaces[LAZY_NAMESPACE_DELETE]->storage; + if ((ret = mailbox_move(storage, name, dest_storage, &destname)) < 0) + return -1; + if (ret == 0) { + mail_storage_set_error(storage, + MAIL_STORAGE_ERR_MAILBOX_NOT_FOUND, name); + return -1; + } + + /* next move the expunged messages mailbox, if it exists */ + storage = lazy_namespaces[LAZY_NAMESPACE_EXPUNGE]->storage; + dest_storage = lazy_namespaces[LAZY_NAMESPACE_DELETE_EXPUNGE]->storage; + (void)mailbox_move(storage, name, dest_storage, &destname); + return 0; +} + +static void lazy_expunge_mail_storage_created(struct mail_storage *storage) +{ + struct lazy_expunge_mail_storage *lstorage; + + /* only maildir supported for now */ + if (strcmp(storage->name, "maildir") != 0) + return; + + lstorage = p_new(storage->pool, struct lazy_expunge_mail_storage, 1); + lstorage->super = storage->v; + storage->v.mailbox_open = lazy_expunge_mailbox_open; + storage->v.mailbox_delete = lazy_expunge_mailbox_delete; + + if (!lazy_expunge_storage_module_id_set) { + lazy_expunge_storage_module_id = mail_storage_module_id++; + lazy_expunge_storage_module_id_set = TRUE; + } + + array_idx_set(&storage->module_contexts, + lazy_expunge_storage_module_id, &lstorage); +} + +static void lazy_expunge_hook_client_created(struct client **client) +{ + struct lazy_expunge_mail_storage *lstorage; + const char *const *p; + int i; + + if (lazy_expunge_next_hook_client_created != NULL) + lazy_expunge_next_hook_client_created(client); + + /* FIXME: this works only as long as there's only one client. */ + t_push(); + p = t_strsplit(getenv("LAZY_EXPUNGE"), " "); + for (i = 0; i < LAZY_NAMESPACE_COUNT; i++, p++) { + const char *name = *p; + + if (name == NULL) + i_fatal("lazy_expunge: Missing namespace #%d", i + 1); + + lazy_namespaces[i] = + namespace_find_prefix((*client)->namespaces, name); + if (lazy_namespaces[i] == NULL) + i_fatal("lazy_expunge: Unknown namespace: '%s'", name); + if (strcmp(lazy_namespaces[i]->storage->name, "maildir") != 0) { + i_fatal("lazy_expunge: Namespace must be in maildir " + "format: %s", name); + } + + /* we don't want to override these namespaces' expunge/delete + operations. */ + lstorage = LAZY_EXPUNGE_CONTEXT(lazy_namespaces[i]->storage); + lazy_namespaces[i]->storage->v = lstorage->super; + } + t_pop(); +} + +void lazy_expunge_plugin_init(void) +{ + if (getenv("LAZY_EXPUNGE") == NULL) + return; + + lazy_expunge_next_hook_client_created = hook_client_created; + hook_client_created = lazy_expunge_hook_client_created; + + lazy_expunge_next_hook_mail_storage_created = + hook_mail_storage_created; + hook_mail_storage_created = lazy_expunge_mail_storage_created; +} + +void lazy_expunge_plugin_deinit(void) +{ + if (lazy_expunge_storage_module_id_set) { + hook_client_created = lazy_expunge_hook_client_created; + + hook_mail_storage_created = + lazy_expunge_next_hook_mail_storage_created; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/lazy-expunge/lazy-expunge-plugin.h Sat Dec 16 02:09:19 2006 +0200 @@ -0,0 +1,7 @@ +#ifndef __LAZY_EXPUNGE_PLUGIN_H +#define __TLAZY_EXPUNGE_PLUGIN_H + +void lazy_expunge_plugin_init(void); +void lazy_expunge_plugin_deinit(void); + +#endif