# HG changeset patch # User Timo Sirainen # Date 1134243885 -7200 # Node ID 732b62dc19763204c62c63b8cac532f2f0738ffd # Parent d67092398377fa7033b7752797b58da4e09ce834 Added beginnings of plugin infrastructure. TODO: These could be optionally compiled into binaries with some configure options. Added quota plugin and a new trash plugin. Not very well tested. diff -r d67092398377 -r 732b62dc1976 configure.in --- a/configure.in Sat Dec 10 20:58:59 2005 +0200 +++ b/configure.in Sat Dec 10 21:44:45 2005 +0200 @@ -1499,6 +1499,10 @@ src/pop3-login/Makefile src/deliver/Makefile src/util/Makefile +src/plugins/Makefile +src/plugins/quota/Makefile +src/plugins/imap-quota/Makefile +src/plugins/trash/Makefile stamp.h dovecot.spec dovecot-config]) diff -r d67092398377 -r 732b62dc1976 src/Makefile.am --- a/src/Makefile.am Sat Dec 10 20:58:59 2005 +0200 +++ b/src/Makefile.am Sat Dec 10 21:44:45 2005 +0200 @@ -25,4 +25,5 @@ imap \ $(POP3D) \ $(DELIVER) \ - util + util \ + plugins diff -r d67092398377 -r 732b62dc1976 src/plugins/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/.cvsignore Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff -r d67092398377 -r 732b62dc1976 src/plugins/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/Makefile.am Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,1 @@ +SUBDIRS = quota imap-quota trash diff -r d67092398377 -r 732b62dc1976 src/plugins/imap-quota/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-quota/.cvsignore Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations diff -r d67092398377 -r 732b62dc1976 src/plugins/imap-quota/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-quota/Makefile.am Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,17 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/quota + +imap_moduledir = $(moduledir)/imap + +libimap_quota_plugin_la_LDFLAGS = -module -avoid-version + +imap_module_LTLIBRARIES = \ + libimap_quota_plugin.la + +libimap_quota_plugin_la_SOURCES = \ + imap-quota-plugin.c diff -r d67092398377 -r 732b62dc1976 src/plugins/imap-quota/imap-quota-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-quota/imap-quota-plugin.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,198 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "common.h" +#include "str.h" +#include "imap-quote.h" +#include "commands.h" +#include "quota.h" +#include "quota-plugin.h" +#include "imap-quota-plugin.h" + +#include + +static void +quota_send(struct client_command_context *cmd, struct quota_root *root) +{ + const char *const *list; + string_t *str; + unsigned int i; + uint64_t value, limit; + int ret; + + t_push(); + + str = t_str_new(128); + str_append(str, "* QUOTA "); + imap_quote_append_string(str, quota_root_get_name(root), FALSE); + + str_append(str, " ("); + list = quota_root_get_resources(root); + for (i = 0; *list != NULL; list++, i++) { + ret = quota_get_resource(root, *list, &value, &limit); + if (ret > 0) { + if (i > 0) + str_append_c(str, ' '); + str_printfa(str, "%s %llu %llu", *list, + (unsigned long long)value, + (unsigned long long)limit); + } else if (ret < 0) { + client_send_line(cmd->client, t_strconcat( + "* BAD ", quota_last_error(quota), NULL)); + } + } + str_append_c(str, ')'); + client_send_line(cmd->client, str_c(str)); + + t_pop(); +} + +static int cmd_getquotaroot(struct client_command_context *cmd) +{ + struct mail_storage *storage; + struct mailbox *box; + struct quota_root_iter *iter; + struct quota_root *root; + const char *mailbox; + string_t *str; + + /* */ + if (!client_read_string_args(cmd, 1, &mailbox)) + return FALSE; + + storage = client_find_storage(cmd, &mailbox); + if (storage == NULL) + return TRUE; + + box = mailbox_open(storage, mailbox, NULL, (MAILBOX_OPEN_READONLY | + MAILBOX_OPEN_FAST | + MAILBOX_OPEN_KEEP_RECENT)); + if (box == NULL) { + client_send_storage_error(cmd, storage); + return TRUE; + } + + if (quota == NULL) { + client_send_tagline(cmd, "OK No quota."); + return TRUE; + } + + /* send QUOTAROOT reply */ + str = t_str_new(128); + str_append(str, "* QUOTAROOT "); + imap_quote_append_string(str, mailbox, FALSE); + + iter = quota_root_iter_init(quota, box); + while ((root = quota_root_iter_next(iter)) != NULL) { + str_append_c(str, ' '); + imap_quote_append_string(str, quota_root_get_name(root), FALSE); + } + if (quota_root_iter_deinit(iter) < 0) { + /* some failure, send as untagged error */ + client_send_line(cmd->client, t_strconcat( + "* BAD ", quota_last_error(quota), NULL)); + } + client_send_line(cmd->client, str_c(str)); + + /* send QUOTA reply for each quotaroot */ + iter = quota_root_iter_init(quota, box); + while ((root = quota_root_iter_next(iter)) != NULL) + quota_send(cmd, root); + if (quota_root_iter_deinit(iter) < 0) { + /* some failure, send as untagged error */ + client_send_line(cmd->client, t_strconcat( + "* BAD ", quota_last_error(quota), NULL)); + } + + mailbox_close(box); + + client_send_tagline(cmd, "OK Getquotaroot completed."); + return TRUE; +} + +static int cmd_getquota(struct client_command_context *cmd) +{ + const char *root_name; + struct quota_root *root; + + /* */ + if (!client_read_string_args(cmd, 1, &root_name)) + return FALSE; + + if (quota == NULL) { + client_send_tagline(cmd, "OK No quota."); + return TRUE; + } + + root = quota_root_lookup(quota, root_name); + if (root == NULL) { + client_send_tagline(cmd, "NO Quota root doesn't exist."); + return TRUE; + } + + quota_send(cmd, root); + client_send_tagline(cmd, "OK Getquota completed."); + return TRUE; +} + +static int cmd_setquota(struct client_command_context *cmd) +{ + struct quota_root *root; + struct imap_arg *args, *arg; + const char *root_name, *name; + uint64_t value; + + /* */ + if (!client_read_args(cmd, 2, 0, &args)) + return FALSE; + + root_name = imap_arg_string(&args[0]); + if (args[1].type != IMAP_ARG_LIST || root_name == NULL) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + if (quota == NULL) { + client_send_tagline(cmd, "OK No quota."); + return TRUE; + } + + root = quota_root_lookup(quota, root_name); + if (root == NULL) { + client_send_tagline(cmd, "NO Quota root doesn't exist."); + return TRUE; + } + + arg = IMAP_ARG_LIST(&args[1])->args; + for (; arg->type != IMAP_ARG_EOL; arg += 2) { + name = imap_arg_string(arg); + if (name == NULL || arg[1].type != IMAP_ARG_ATOM || + !is_numeric(IMAP_ARG_STR(&arg[1]), '\0')) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + value = strtoull(IMAP_ARG_STR_NONULL(&arg[1]), NULL, 10); + if (quota_set_resource(root, name, value) < 0) { + client_send_command_error(cmd, + quota_last_error(quota)); + return TRUE; + } + } + + client_send_tagline(cmd, "OK Setquota completed."); + return TRUE; +} + +void imap_quota_init(void) +{ + command_register("GETQUOTAROOT", cmd_getquotaroot); + command_register("GETQUOTA", cmd_getquota); + command_register("SETQUOTA", cmd_setquota); +} + +void imap_quota_deinit(void) +{ + command_unregister("GETQUOTAROOT"); + command_unregister("GETQUOTA"); + command_unregister("SETQUOTA"); +} diff -r d67092398377 -r 732b62dc1976 src/plugins/imap-quota/imap-quota-plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-quota/imap-quota-plugin.h Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,7 @@ +#ifndef __IMAP_QUOTA_PLUGIN_H +#define __IMAP_QUOTA_PLUGIN_H + +void imap_quota_init(void); +void imap_quota_deinit(void); + +#endif diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/.cvsignore Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/Makefile.am Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,24 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-storage + +libquota_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + libquota_plugin.la + +libquota_plugin_la_SOURCES = \ + quota.c \ + quota-dict.c \ + quota-dirsize.c \ + quota-plugin.c \ + quota-storage.c + +install-exec-local: + $(mkdir_p) $(moduledir)/imap $(moduledir)/lda + for d in imap lda; do \ + rm -f $(moduledir)/$$d/libquota_plugin.so; \ + $(LN_S) ../libquota_plugin.so $(moduledir)/$$d; \ + done diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota-dict.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota-dict.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,309 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "lib.h" +#include "str.h" +#include "dict.h" +#include "quota-private.h" + +#include + +#define DICT_QUOTA_LIMIT_PATH DICT_PATH_PRIVATE"quota/limit/" +#define DICT_QUOTA_CURRENT_PATH DICT_PATH_PRIVATE"quota/current/" + +struct dict_quota { + struct quota quota; + + pool_t pool; + const char *error; + struct quota_root root; + + struct dict *dict; +}; + +struct dict_quota_root_iter { + struct quota_root_iter iter; + + int sent; +}; + +struct dict_quota_transaction_context { + struct quota_transaction_context ctx; + + uint64_t storage_limit; + uint64_t storage_current; +}; + +extern struct quota dict_quota; + +static struct quota *dict_quota_init(const char *data) +{ + struct dict_quota *quota; + struct dict *dict; + pool_t pool; + + if (getenv("DEBUG") != NULL) + i_info("dict quota uri = %s", data); + + dict = dict_init(data); + if (dict == NULL) + return NULL; + + pool = pool_alloconly_create("quota", 1024); + quota = p_new(pool, struct dict_quota, 1); + quota->pool = pool; + quota->quota = dict_quota; + quota->dict = dict; + + quota->root.quota = "a->quota; + return "a->quota; +} + +static void dict_quota_deinit(struct quota *_quota) +{ + struct dict_quota *quota = (struct dict_quota *)_quota; + + pool_unref(quota->pool); +} + +static struct quota_root_iter * +dict_quota_root_iter_init(struct quota *quota, + struct mailbox *box __attr_unused__) +{ + struct dict_quota_root_iter *iter; + + iter = i_new(struct dict_quota_root_iter, 1); + iter->iter.quota = quota; + return &iter->iter; +} + +static struct quota_root * +dict_quota_root_iter_next(struct quota_root_iter *_iter) +{ + struct dict_quota_root_iter *iter = + (struct dict_quota_root_iter *)_iter; + struct dict_quota *quota = (struct dict_quota *)_iter->quota; + + if (iter->sent) + return NULL; + + iter->sent = TRUE; + return "a->root; +} + +static int dict_quota_root_iter_deinit(struct quota_root_iter *iter) +{ + i_free(iter); + return 0; +} + +static struct quota_root * +dict_quota_root_lookup(struct quota *_quota, const char *name) +{ + struct dict_quota *quota = (struct dict_quota *)_quota; + + if (*name == '\0') + return "a->root; + else + return NULL; +} + +static const char * +dict_quota_root_get_name(struct quota_root *root __attr_unused__) +{ + return ""; +} + +static const char *const * +dict_quota_root_get_resources(struct quota_root *root __attr_unused__) +{ + static const char *resources[] = { QUOTA_NAME_STORAGE, NULL }; + + return resources; +} + +static int +dict_quota_root_create(struct quota *_quota, + const char *name __attr_unused__, + struct quota_root **root_r __attr_unused__) +{ + struct dict_quota *quota = (struct dict_quota *)_quota; + + quota->error = "Permission denied"; + return -1; +} + +static int +dict_quota_get_resource(struct quota_root *root, const char *name, + uint64_t *value_r, uint64_t *limit_r) +{ + struct dict_quota *quota = (struct dict_quota *)root->quota; + const char *value; + + if (quota->dict == NULL) + return 0; + + t_push(); + value = dict_lookup(quota->dict, unsafe_data_stack_pool, + t_strconcat(DICT_QUOTA_LIMIT_PATH, name, NULL)); + *limit_r = value == NULL ? 0 : strtoull(value, NULL, 10); + + if (value == NULL) { + /* resource doesn't exist */ + *value_r = 0; + } else { + value = dict_lookup(quota->dict, unsafe_data_stack_pool, + t_strconcat(DICT_QUOTA_CURRENT_PATH, + name, NULL)); + *value_r = value == NULL ? 0 : strtoull(value, NULL, 10); + } + t_pop(); + + *limit_r /= 1024; + *value_r /= 1024; + + return value == NULL; +} + +static int +dict_quota_set_resource(struct quota_root *root, + const char *name __attr_unused__, + uint64_t value __attr_unused__) +{ + struct dict_quota *quota = (struct dict_quota *)root->quota; + + quota->error = "Permission denied"; + return -1; +} + +static struct quota_transaction_context * +dict_quota_transaction_begin(struct quota *_quota) +{ + struct dict_quota *quota = (struct dict_quota *)_quota; + struct dict_quota_transaction_context *ctx; + const char *value; + + ctx = i_new(struct dict_quota_transaction_context, 1); + ctx->ctx.quota = _quota; + + if (quota->dict != NULL) { + t_push(); + value = dict_lookup(quota->dict, unsafe_data_stack_pool, + DICT_QUOTA_LIMIT_PATH"storage"); + ctx->storage_limit = value == NULL ? 0 : + strtoull(value, NULL, 10); + + value = dict_lookup(quota->dict, unsafe_data_stack_pool, + DICT_QUOTA_CURRENT_PATH"storage"); + ctx->storage_current = value == NULL ? 0 : + strtoull(value, NULL, 10); + t_pop(); + } else { + ctx->storage_limit = (uint64_t)-1; + } + + return &ctx->ctx; +} + +static int +dict_quota_transaction_commit(struct quota_transaction_context *_ctx) +{ + struct dict_quota_transaction_context *ctx = + (struct dict_quota_transaction_context *)_ctx; + struct dict_quota *quota = (struct dict_quota *)_ctx->quota; + + if (quota->dict != NULL) { + struct dict_transaction_context *dt; + + dt = dict_transaction_begin(quota->dict); + dict_atomic_inc(dt, DICT_QUOTA_CURRENT_PATH"storage", + _ctx->bytes_diff); + if (dict_transaction_commit(dt) < 0) + i_error("dict_quota: Couldn't update quota"); + } + + i_free(ctx); + return 0; +} + +static void +dict_quota_transaction_rollback(struct quota_transaction_context *ctx) +{ + i_free(ctx); +} + +static int +dict_quota_try_alloc(struct quota_transaction_context *_ctx, + struct mail *mail, int *too_large_r) +{ + struct dict_quota_transaction_context *ctx = + (struct dict_quota_transaction_context *)_ctx; + uoff_t size; + + size = mail_get_physical_size(mail); + *too_large_r = size > ctx->storage_limit; + + if (ctx->storage_current + _ctx->bytes_diff + size > ctx->storage_limit) + return 0; + + _ctx->bytes_diff += size; + return 1; +} + +static void +dict_quota_alloc(struct quota_transaction_context *ctx, struct mail *mail) +{ + uoff_t size; + + size = mail_get_physical_size(mail); + if (size != (uoff_t)-1) + ctx->bytes_diff += size; +} + +static void +dict_quota_free(struct quota_transaction_context *ctx, struct mail *mail) +{ + uoff_t size; + + size = mail_get_physical_size(mail); + if (size != (uoff_t)-1) + ctx->bytes_diff -= size; +} + +static const char *dict_quota_last_error(struct quota *_quota) +{ + struct dict_quota *quota = (struct dict_quota *)_quota; + + return quota->error; +} + +struct quota dict_quota = { + "dict", + + dict_quota_init, + dict_quota_deinit, + + dict_quota_root_iter_init, + dict_quota_root_iter_next, + dict_quota_root_iter_deinit, + + dict_quota_root_lookup, + + dict_quota_root_get_name, + dict_quota_root_get_resources, + + dict_quota_root_create, + dict_quota_get_resource, + dict_quota_set_resource, + + dict_quota_transaction_begin, + dict_quota_transaction_commit, + dict_quota_transaction_rollback, + + dict_quota_try_alloc, + dict_quota_alloc, + dict_quota_free, + + dict_quota_last_error, + + ARRAY_INIT +}; diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota-dirsize.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota-dirsize.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,329 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +/* Quota reporting based on simply summing sizes of all files in mailbox + together. */ + +#include "lib.h" +#include "str.h" +#include "quota-private.h" + +#include +#include +#include +#include + +struct dirsize_quota { + struct quota quota; + + pool_t pool; + const char *path; + const char *error; + struct quota_root root; + + uint64_t storage_limit; +}; + +struct dirsize_quota_root_iter { + struct quota_root_iter iter; + + int sent; +}; + +extern struct quota dirsize_quota; + +static struct quota *dirsize_quota_init(const char *data) +{ + struct dirsize_quota *quota; + const char *const *args; + pool_t pool; + + pool = pool_alloconly_create("quota", 1024); + quota = p_new(pool, struct dirsize_quota, 1); + quota->pool = pool; + quota->quota = dirsize_quota; + + args = t_strsplit(data, ":"); + quota->path = p_strdup(pool, args[0]); + + for (args++; *args != '\0'; args++) { + if (strncmp(*args, "storage=", 8) == 0) + quota->storage_limit = strtoull(*args + 8, NULL, 10); + } + + if (getenv("DEBUG") != NULL) { + i_info("dirsize quota path = %s", quota->path); + i_info("dirsize quota limit = %llukB", + (unsigned long long)quota->storage_limit); + } + + quota->root.quota = "a->quota; + return "a->quota; +} + +static void dirsize_quota_deinit(struct quota *_quota) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)_quota; + + pool_unref(quota->pool); +} + +static struct quota_root_iter * +dirsize_quota_root_iter_init(struct quota *quota, + struct mailbox *box __attr_unused__) +{ + struct dirsize_quota_root_iter *iter; + + iter = i_new(struct dirsize_quota_root_iter, 1); + iter->iter.quota = quota; + return &iter->iter; +} + +static struct quota_root * +dirsize_quota_root_iter_next(struct quota_root_iter *_iter) +{ + struct dirsize_quota_root_iter *iter = + (struct dirsize_quota_root_iter *)_iter; + struct dirsize_quota *quota = (struct dirsize_quota *)_iter->quota; + + if (iter->sent) + return NULL; + + iter->sent = TRUE; + return "a->root; +} + +static int dirsize_quota_root_iter_deinit(struct quota_root_iter *iter) +{ + i_free(iter); + return 0; +} + +static struct quota_root * +dirsize_quota_root_lookup(struct quota *_quota, const char *name) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)_quota; + + if (*name == '\0') + return "a->root; + else + return NULL; +} + +static const char * +dirsize_quota_root_get_name(struct quota_root *root __attr_unused__) +{ + return ""; +} + +static const char *const * +dirsize_quota_root_get_resources(struct quota_root *root __attr_unused__) +{ + static const char *resources[] = { QUOTA_NAME_STORAGE, NULL }; + + return resources; +} + +static int +dirsize_quota_root_create(struct quota *_quota, + const char *name __attr_unused__, + struct quota_root **root_r __attr_unused__) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)_quota; + + quota->error = "Permission denied"; + return -1; +} + +static int get_dir_usage(const char *dir, uint64_t *value) +{ + DIR *dirp; + string_t *path; + struct dirent *d; + struct stat st; + unsigned int path_pos; + int ret; + + dirp = opendir(dir); + if (dirp == NULL) { + if (errno == ENOENT) + return 0; + + i_error("opendir(%s) failed: %m", dir); + return -1; + } + + path = t_str_new(128); + str_append(path, dir); + str_append_c(path, '/'); + path_pos = str_len(path); + + ret = 0; + while ((d = readdir(dirp)) != NULL) { + if (d->d_name[0] == '.' && + (d->d_name[1] == '\0' || + (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { + /* skip . and .. */ + continue; + } + + str_truncate(path, path_pos); + str_append(path, d->d_name); + + if (lstat(str_c(path), &st) < 0) { + if (errno == ENOENT) + continue; + + i_error("lstat(%s) failed: %m", dir); + ret = -1; + break; + } else if (S_ISDIR(st.st_mode)) { + if (get_dir_usage(str_c(path), value) < 0) { + ret = -1; + break; + } + } else { + *value += st.st_size; + } + } + + (void)closedir(dirp); + return ret; +} + +static int +dirsize_quota_get_resource(struct quota_root *root, const char *name, + uint64_t *value_r, uint64_t *limit_r) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)root->quota; + + *value_r = 0; + *limit_r = 0; + + if (strcasecmp(name, QUOTA_NAME_STORAGE) != 0) + return 0; + + if (get_dir_usage(quota->path, value_r) < 0) { + quota->error = "Internal quota calculation error"; + return -1; + } + *value_r /= 1024; + *limit_r = quota->storage_limit; + return 1; +} + +static int +dirsize_quota_set_resource(struct quota_root *root, + const char *name __attr_unused__, + uint64_t value __attr_unused__) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)root->quota; + + quota->error = "Permission denied"; + return -1; +} + +static struct quota_transaction_context * +dirsize_quota_transaction_begin(struct quota *quota) +{ + struct quota_transaction_context *ctx; + + ctx = i_new(struct quota_transaction_context, 1); + ctx->quota = quota; + return ctx; +} + +static int +dirsize_quota_transaction_commit(struct quota_transaction_context *ctx) +{ + i_free(ctx); + return 0; +} + +static void +dirsize_quota_transaction_rollback(struct quota_transaction_context *ctx) +{ + i_free(ctx); +} + +static int +dirsize_quota_try_alloc(struct quota_transaction_context *ctx, + struct mail *mail, int *too_large_r) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)ctx->quota; + uint64_t value = 0; + uoff_t size; + + size = mail_get_physical_size(mail); + *too_large_r = size / 1024 > quota->storage_limit; + + if (get_dir_usage(quota->path, &value) < 0 || size == (uoff_t)-1) { + quota->error = "Internal quota calculation error"; + return -1; + } + value += ctx->bytes_diff; + + if ((value + size) / 1024 > quota->storage_limit) + return 0; + + ctx->bytes_diff += size; + return 1; +} + +static void +dirsize_quota_alloc(struct quota_transaction_context *ctx, struct mail *mail) +{ + uoff_t size; + + size = mail_get_physical_size(mail); + if (size != (uoff_t)-1) + ctx->bytes_diff += size; +} + +static void +dirsize_quota_free(struct quota_transaction_context *ctx, struct mail *mail) +{ + uoff_t size; + + size = mail_get_physical_size(mail); + if (size != (uoff_t)-1) + ctx->bytes_diff -= size; +} + +static const char *dirsize_quota_last_error(struct quota *_quota) +{ + struct dirsize_quota *quota = (struct dirsize_quota *)_quota; + + return quota->error; +} + +struct quota dirsize_quota = { + "dirsize", + + dirsize_quota_init, + dirsize_quota_deinit, + + dirsize_quota_root_iter_init, + dirsize_quota_root_iter_next, + dirsize_quota_root_iter_deinit, + + dirsize_quota_root_lookup, + + dirsize_quota_root_get_name, + dirsize_quota_root_get_resources, + + dirsize_quota_root_create, + dirsize_quota_get_resource, + dirsize_quota_set_resource, + + dirsize_quota_transaction_begin, + dirsize_quota_transaction_commit, + dirsize_quota_transaction_rollback, + + dirsize_quota_try_alloc, + dirsize_quota_alloc, + dirsize_quota_free, + + dirsize_quota_last_error, + + ARRAY_INIT +}; diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota-plugin.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,38 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "lib.h" +#include "str.h" +#include "mail-storage.h" +#include "quota.h" +#include "quota-plugin.h" + +#include + +/* defined by imap, pop3, lda */ +extern void (*hook_mail_storage_created)(struct mail_storage *storage); + +void (*quota_next_hook_mail_storage_created)(struct mail_storage *storage); +struct quota *quota = NULL; + +void quota_plugin_init(void) +{ + const char *env; + + env = getenv("QUOTA"); + quota = env == NULL ? NULL : quota_init(env); + + if (quota != NULL) { + quota_next_hook_mail_storage_created = + hook_mail_storage_created; + hook_mail_storage_created = quota_mail_storage_created; + } +} + +void quota_plugin_deinit(void) +{ + if (quota != NULL) { + hook_mail_storage_created = + quota_next_hook_mail_storage_created; + quota_deinit(quota); + } +} diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota-plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota-plugin.h Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,15 @@ +#ifndef __QUOTA_PLUGIN_H +#define __QUOTA_PLUGIN_H + +struct mail_storage; + +extern void (*quota_next_hook_mail_storage_created) + (struct mail_storage *storage); +extern struct quota *quota; + +void quota_mail_storage_created(struct mail_storage *storage); + +void quota_plugin_init(void); +void quota_plugin_deinit(void); + +#endif diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota-private.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota-private.h Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,66 @@ +#ifndef __QUOTA_PRIVATE_H +#define __QUOTA_PRIVATE_H + +#include "mail-storage-private.h" +#include "quota.h" + +/* Modules should use do "my_id = quota_module_id++" and + use quota_module_contexts[id] for their own purposes. */ +extern unsigned int quota_module_id; + +struct quota { + const char *name; + + struct quota *(*init)(const char *data); + void (*deinit)(struct quota *quota); + + struct quota_root_iter * + (*root_iter_init)(struct quota *quota, struct mailbox *box); + struct quota_root *(*root_iter_next)(struct quota_root_iter *iter); + int (*root_iter_deinit)(struct quota_root_iter *iter); + + struct quota_root *(*root_lookup)(struct quota *quota, + const char *name); + + const char *(*root_get_name)(struct quota_root *root); + const char *const *(*root_get_resources)(struct quota_root *root); + + int (*root_create)(struct quota *quota, const char *name, + struct quota_root **root_r); + int (*get_resource)(struct quota_root *root, const char *name, + uint64_t *value_r, uint64_t *limit_r); + int (*set_resource)(struct quota_root *root, + const char *name, uint64_t value); + + struct quota_transaction_context * + (*transaction_begin)(struct quota *quota); + int (*transaction_commit)(struct quota_transaction_context *ctx); + void (*transaction_rollback)(struct quota_transaction_context *ctx); + + int (*try_alloc)(struct quota_transaction_context *ctx, + struct mail *mail, int *too_large_r); + void (*alloc)(struct quota_transaction_context *ctx, struct mail *mail); + void (*free)(struct quota_transaction_context *ctx, struct mail *mail); + + const char *(*last_error)(struct quota *quota); + + /* Module-specific contexts. See quota_module_id. */ + array_t ARRAY_DEFINE(quota_module_contexts, void); +}; + +struct quota_root { + struct quota *quota; +}; + +struct quota_root_iter { + struct quota *quota; +}; + +struct quota_transaction_context { + struct quota *quota; + + int count_diff; + int64_t bytes_diff; +}; + +#endif diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota-storage.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota-storage.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,246 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "mail-storage-private.h" +#include "quota.h" +#include "quota-plugin.h" + +#include + +#define QUOTA_CONTEXT(obj) \ + *((void **)array_idx_modifyable(&(obj)->module_contexts, \ + quota_storage_module_id)) + +struct quota_mail_storage { + struct mail_storage_vfuncs super; +}; + +struct quota_mailbox { + struct mailbox_vfuncs super; + + unsigned int save_hack:1; +}; + +struct quota_mail { + struct mail_vfuncs super; +}; + +static unsigned int quota_storage_module_id = 0; +static int quota_storage_module_id_set = FALSE; + +static int quota_mail_expunge(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct quota_mail *qmail = QUOTA_CONTEXT(mail); + struct quota_transaction_context *qt = + QUOTA_CONTEXT(_mail->transaction); + + if (qmail->super.expunge(_mail) < 0) + return -1; + + quota_free(qt, _mail); + return 0; +} + +static struct mailbox_transaction_context * +quota_mailbox_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(box); + struct mailbox_transaction_context *t; + struct quota_transaction_context *qt; + + t = qbox->super.transaction_begin(box, flags); + qt = quota_transaction_begin(quota); + + array_idx_set(&t->module_contexts, quota_storage_module_id, &qt); + return t; +} + +static int +quota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx, + enum mailbox_sync_flags flags) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box); + struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx); + + if (qbox->super.transaction_commit(ctx, flags) < 0) { + quota_transaction_rollback(qt); + return -1; + } else { + (void)quota_transaction_commit(qt); + return 0; + } +} + +static void +quota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box); + struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx); + + qbox->super.transaction_rollback(ctx); + quota_transaction_rollback(qt); +} + +static struct mail * +quota_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box); + struct quota_mail *qmail; + struct mail *_mail; + struct mail_private *mail; + + _mail = qbox->super.mail_alloc(t, wanted_fields, wanted_headers); + mail = (struct mail_private *)_mail; + + qmail = p_new(mail->pool, struct quota_mail, 1); + qmail->super = mail->v; + + mail->v.expunge = quota_mail_expunge; + array_idx_set(&mail->module_contexts, quota_storage_module_id, &qmail); + return _mail; +} + +static int quota_check(struct mailbox_transaction_context *t, struct mail *mail) +{ + struct quota_transaction_context *qt = QUOTA_CONTEXT(t); + int ret, too_large; + + ret = quota_try_alloc(qt, mail, &too_large); + if (ret > 0) + return 0; + else if (ret == 0) { + mail_storage_set_error(t->box->storage, "Quota exceeded"); + return -1; + } else { + mail_storage_set_error(t->box->storage, "%s", + quota_last_error(quota)); + return -1; + } +} + +static int +quota_copy(struct mailbox_transaction_context *t, struct mail *mail, + enum mail_flags flags, struct mail_keywords *keywords, + struct mail *dest_mail) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box); + struct mail *copy_dest_mail; + int ret; + + if (dest_mail != NULL) + copy_dest_mail = dest_mail; + else + copy_dest_mail = mail_alloc(t, MAIL_FETCH_PHYSICAL_SIZE, NULL); + + qbox->save_hack = FALSE; + if (qbox->super.copy(t, mail, flags, keywords, copy_dest_mail) < 0) + return -1; + + /* if copying used saving internally, we already checked the quota + and set qbox->save_hack = TRUE. */ + ret = qbox->save_hack ? 0 : quota_check(t, copy_dest_mail); + + if (copy_dest_mail != dest_mail) + mail_free(copy_dest_mail); + return ret; +} + +static struct mail_save_context * +quota_save_init(struct mailbox_transaction_context *t, + enum mail_flags flags, struct mail_keywords *keywords, + time_t received_date, int timezone_offset, + const char *from_envelope, struct istream *input, + int want_mail __attr_unused__) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box); + const struct stat *st; + + st = i_stream_stat(input, TRUE); + if (st != NULL && st->st_size != -1) { + /* FIXME: input size is known, check for quota. + the API needs changing however to do this, we'd need to + return failure before "+ OK".. */ + } + + /* note that we set want_mail = TRUE in here. */ + return qbox->super.save_init(t, flags, keywords, received_date, + timezone_offset, from_envelope, + input, TRUE); +} + +static int quota_save_finish(struct mail_save_context *ctx, + struct mail *dest_mail) +{ + struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->transaction->box); + struct mail *save_dest_mail; + int ret; + + if (dest_mail != NULL) + save_dest_mail = dest_mail; + else { + save_dest_mail = mail_alloc(ctx->transaction, + MAIL_FETCH_PHYSICAL_SIZE, NULL); + } + + if (qbox->super.save_finish(ctx, save_dest_mail) < 0) + return -1; + + qbox->save_hack = TRUE; + ret = quota_check(ctx->transaction, save_dest_mail); + + if (save_dest_mail != dest_mail) + mail_free(save_dest_mail); + return ret; +} + +static struct mailbox * +quota_mailbox_open(struct mail_storage *storage, const char *name, + struct istream *input, enum mailbox_open_flags flags) +{ + struct quota_mail_storage *qstorage = QUOTA_CONTEXT(storage); + struct mailbox *box; + struct quota_mailbox *qbox; + + box = qstorage->super.mailbox_open(storage, name, input, flags); + if (box == NULL) + return NULL; + + qbox = p_new(box->pool, struct quota_mailbox, 1); + qbox->super = box->v; + + box->v.transaction_begin = quota_mailbox_transaction_begin; + box->v.transaction_commit = quota_mailbox_transaction_commit; + box->v.transaction_rollback = quota_mailbox_transaction_rollback; + box->v.mail_alloc = quota_mail_alloc; + box->v.save_init = quota_save_init; + box->v.save_finish = quota_save_finish; + box->v.copy = quota_copy; + array_idx_set(&box->module_contexts, quota_storage_module_id, &qbox); + return box; +} + +void quota_mail_storage_created(struct mail_storage *storage) +{ + struct quota_mail_storage *qstorage; + + if (quota_next_hook_mail_storage_created != NULL) + quota_next_hook_mail_storage_created(storage); + + qstorage = p_new(storage->pool, struct quota_mail_storage, 1); + qstorage->super = storage->v; + storage->v.mailbox_open = quota_mailbox_open; + + if (!quota_storage_module_id_set) { + quota_storage_module_id = mail_storage_module_id++; + quota_storage_module_id_set = TRUE; + } + + array_idx_set(&storage->module_contexts, + quota_storage_module_id, &qstorage); +} diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,136 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "quota-private.h" + +unsigned int quota_module_id = 0; + +extern struct quota dirsize_quota; +extern struct quota dict_quota; + +static struct quota *quota_classes[] = { &dirsize_quota, &dict_quota }; +#define QUOTA_CLASS_COUNT (sizeof(quota_classes)/sizeof(quota_classes[0])) + +struct quota *quota_init(const char *data) +{ + struct quota *quota; + const char *name, *p; + unsigned int i; + + t_push(); + p = strchr(data, ':'); + if (p == NULL) { + name = data; + data = ""; + } else { + name = t_strdup_until(data, p); + data = p+1; + } + for (i = 0; i < QUOTA_CLASS_COUNT; i++) { + if (strcmp(quota_classes[i]->name, name) == 0) + break; + } + t_pop(); + + quota = i == QUOTA_CLASS_COUNT ? NULL : + quota_classes[i]->init(data); + if (quota != NULL) { + array_create("a->quota_module_contexts, + default_pool, sizeof(void *), 5); + } + return quota; +} + +void quota_deinit(struct quota *quota) +{ + array_t *module_contexts = "a->quota_module_contexts; + + quota->deinit(quota); + array_free(module_contexts); +} + +struct quota_root_iter * +quota_root_iter_init(struct quota *quota, struct mailbox *box) +{ + return quota->root_iter_init(quota, box); +} + +struct quota_root *quota_root_iter_next(struct quota_root_iter *iter) +{ + return iter->quota->root_iter_next(iter); +} + +int quota_root_iter_deinit(struct quota_root_iter *iter) +{ + return iter->quota->root_iter_deinit(iter); +} + +struct quota_root *quota_root_lookup(struct quota *quota, const char *name) +{ + return quota->root_lookup(quota, name); +} + +const char *quota_root_get_name(struct quota_root *root) +{ + return root->quota->root_get_name(root); +} + +const char *const *quota_root_get_resources(struct quota_root *root) +{ + return root->quota->root_get_resources(root); +} + +int quota_root_create(struct quota *quota, const char *name, + struct quota_root **root_r) +{ + return quota->root_create(quota, name, root_r); +} + +int quota_get_resource(struct quota_root *root, const char *name, + uint64_t *value_r, uint64_t *limit_r) +{ + return root->quota->get_resource(root, name, value_r, limit_r); +} + +int quota_set_resource(struct quota_root *root, + const char *name, uint64_t value) +{ + return root->quota->set_resource(root, name, value); +} + +struct quota_transaction_context *quota_transaction_begin(struct quota *quota) +{ + return quota->transaction_begin(quota); +} + +int quota_transaction_commit(struct quota_transaction_context *ctx) +{ + return ctx->quota->transaction_commit(ctx); +} + +void quota_transaction_rollback(struct quota_transaction_context *ctx) +{ + ctx->quota->transaction_rollback(ctx); +} + +int quota_try_alloc(struct quota_transaction_context *ctx, + struct mail *mail, int *too_large_r) +{ + return ctx->quota->try_alloc(ctx, mail, too_large_r); +} + +void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail) +{ + ctx->quota->alloc(ctx, mail); +} + +void quota_free(struct quota_transaction_context *ctx, struct mail *mail) +{ + ctx->quota->free(ctx, mail); +} + +const char *quota_last_error(struct quota *quota) +{ + return quota->last_error(quota); +} diff -r d67092398377 -r 732b62dc1976 src/plugins/quota/quota.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/quota/quota.h Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,64 @@ +#ifndef __QUOTA_H +#define __QUOTA_H + +struct mail; +struct mailbox; + +/* Message storage size kilobytes. */ +#define QUOTA_NAME_STORAGE "STORAGE" +/* Number of messages. */ +#define QUOTA_NAME_MESSAGES "MESSAGES" + +struct quota; +struct quota_root; +struct quota_root_iter; +struct quota_transaction_context; + +struct quota *quota_init(const char *data); +void quota_deinit(struct quota *quota); + +/* List all quota roots. Returned quota roots are freed by quota_deinit(). */ +struct quota_root_iter * +quota_root_iter_init(struct quota *quota, struct mailbox *box); +struct quota_root *quota_root_iter_next(struct quota_root_iter *iter); +int quota_root_iter_deinit(struct quota_root_iter *iter); + +/* Return quota root or NULL. */ +struct quota_root *quota_root_lookup(struct quota *quota, const char *name); + +/* Returns name of the quota root. */ +const char *quota_root_get_name(struct quota_root *root); +/* Return a list of all resources set for the quota root. */ +const char *const *quota_root_get_resources(struct quota_root *root); + +/* Create a new quota root. Returns 0 if OK, -1 if error (eg. permission + denied). */ +int quota_root_create(struct quota *quota, const char *name, + struct quota_root **root_r); +/* Returns 1 if quota value was found, 0 if not, -1 if error. */ +int quota_get_resource(struct quota_root *root, + const char *name, uint64_t *value_r, uint64_t *limit_r); +/* Returns 0 if OK, -1 if error (eg. permission denied, invalid name). */ +int quota_set_resource(struct quota_root *root, + const char *name, uint64_t value); + +/* Start a new quota transaction. */ +struct quota_transaction_context *quota_transaction_begin(struct quota *quota); +/* Commit quota transaction. Returns 0 if ok, -1 if failed. */ +int quota_transaction_commit(struct quota_transaction_context *ctx); +/* Rollback quota transaction changes. */ +void quota_transaction_rollback(struct quota_transaction_context *ctx); + +/* Allocate from quota if there's space. Returns 1 if updated, 0 if not, + -1 if error. If mail size is larger than even maximum allowed quota, + too_large_r is set to TRUE. */ +int quota_try_alloc(struct quota_transaction_context *ctx, + struct mail *mail, int *too_large_r); +/* Update quota by allocating/freeing space used by mail. */ +void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail); +void quota_free(struct quota_transaction_context *ctx, struct mail *mail); + +/* Returns the last error message. */ +const char *quota_last_error(struct quota *quota); + +#endif diff -r d67092398377 -r 732b62dc1976 src/plugins/trash/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/trash/.cvsignore Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations diff -r d67092398377 -r 732b62dc1976 src/plugins/trash/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/trash/Makefile.am Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,21 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/plugins/quota + +libtrash_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + libtrash_plugin.la + +libtrash_plugin_la_SOURCES = \ + trash-plugin.c + +install-exec-local: + $(mkdir_p) $(moduledir)/imap $(moduledir)/lda + for d in imap lda; do \ + rm -f $(moduledir)/$$d/libtrash_plugin.so; \ + $(LN_S) ../libtrash_plugin.so $(moduledir)/$$d; \ + done + diff -r d67092398377 -r 732b62dc1976 src/plugins/trash/trash-plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/trash/trash-plugin.c Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,295 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "home-expand.h" +#include "mail-search.h" +#include "quota-private.h" +#include "quota-plugin.h" +#include "trash-plugin.h" + +#include +#include +#include + +#define LOCAL_CONFIG_FILE "~/.dovecot.trash.conf" +#define GLOBAL_CONFIG_FILE "/etc/dovecot-trash.conf" + +#define MAX_RETRY_COUNT 3 + +#define TRASH_CONTEXT(obj) \ + *((void **)array_idx_modifyable(&(obj)->quota_module_contexts, \ + trash_quota_module_id)) + +struct trash_quota { + struct quota super; +}; + +struct trash_mailbox { + const char *name; + int priority; /* lower number = higher priority */ + + struct mail_storage *storage; + + /* temporarily set while cleaning: */ + struct mailbox *box; + struct mailbox_transaction_context *trans; + struct mail_search_context *search_ctx; + struct mail *mail; + + unsigned int mail_set:1; +}; + +/* defined by imap, pop3, lda */ +extern void (*hook_mail_storage_created)(struct mail_storage *storage); + +static void (*trash_next_hook_mail_storage_created) + (struct mail_storage *storage); +static int quota_initialized; +static unsigned int trash_quota_module_id; + +static pool_t config_pool; +/* trash_boxes ordered by priority, highest first */ +static array_t ARRAY_DEFINE(trash_boxes, struct trash_mailbox); + +static int trash_clean_mailbox_open(struct trash_mailbox *trash) +{ + struct mail_search_arg search_arg; + + trash->box = mailbox_open(trash->storage, trash->name, NULL, + MAILBOX_OPEN_KEEP_RECENT); + trash->trans = mailbox_transaction_begin(trash->box, 0); + + memset(&search_arg, 0, sizeof(search_arg)); + search_arg.type = SEARCH_ALL; + + trash->search_ctx = + mailbox_search_init(trash->trans, NULL, &search_arg, NULL); + trash->mail = mail_alloc(trash->trans, MAIL_FETCH_PHYSICAL_SIZE | + MAIL_FETCH_RECEIVED_DATE, NULL); + + return mailbox_search_next(trash->search_ctx, trash->mail); +} + +static int trash_clean_mailbox_get_next(struct trash_mailbox *trash, + time_t *received_time_r) +{ + int ret; + + if (!trash->mail_set) { + if (trash->box == NULL) + ret = trash_clean_mailbox_open(trash); + else + ret = mailbox_search_next(trash->search_ctx, + trash->mail); + if (ret <= 0) + return ret; + + trash->mail_set = TRUE; + } + + *received_time_r = mail_get_received_date(trash->mail); + return 1; +} + +static int trash_try_clean_mails(uint64_t size_needed) +{ + struct trash_mailbox *trashes; + unsigned int i, j, count, oldest_idx; + time_t oldest, received; + uint64_t size; + int ret = 0; + + trashes = array_get_modifyable(&trash_boxes, &count); + for (i = 0; i < count; ) { + /* expunge oldest mails first in all trash boxes with + same priority */ + oldest_idx = count; + oldest = (time_t)-1; + for (j = i; j < count; j++) { + if (trashes[j].priority != trashes[j].priority) + break; + + ret = trash_clean_mailbox_get_next(&trashes[i], + &received); + if (ret < 0) + goto __err; + if (ret > 0) { + if (oldest == (time_t)-1 || + received < oldest) { + oldest = received; + oldest_idx = j; + } + } + } + + if (oldest_idx < count) { + if (mail_expunge(trashes[oldest_idx].mail) < 0) + break; + + size = mail_get_physical_size(trashes[oldest_idx].mail); + if (size >= size_needed) { + size_needed = 0; + break; + } + trashes[oldest_idx].mail_set = FALSE; + + size_needed -= size; + } else { + /* find more mails from next priority's mailbox */ + i = j; + } + } + +__err: + for (i = 0; i < count; i++) { + struct trash_mailbox *trash = &trashes[i]; + + mail_free(trash->mail); + trash->mail = NULL; + + (void)mailbox_search_deinit(trash->search_ctx); + trash->search_ctx = NULL; + + if (size_needed == 0) { + (void)mailbox_transaction_commit(trash->trans, + MAILBOX_SYNC_FLAG_FULL_WRITE); + } else { + /* couldn't get enough space, don't expunge anything */ + mailbox_transaction_rollback(trash->trans); + } + trash->trans = NULL; + + mailbox_close(trash->box); + trash->box = NULL; + } + return size_needed == 0; +} + +static int +trash_quota_try_alloc(struct quota_transaction_context *ctx, + struct mail *mail, int *too_large_r) +{ + struct trash_quota *tquota = TRASH_CONTEXT(quota); + int ret, i; + + for (i = 0; ; i++) { + ret = tquota->super.try_alloc(ctx, mail, too_large_r); + if (ret != 0 || *too_large_r) + return ret; + + if (i == MAX_RETRY_COUNT) { + /* trash_try_clean_mails() should have returned 0 if + it couldn't get enough space, but allow retrying + it a couple of times if there was some extra space + that was needed.. */ + break; + } + + /* not enough space. try deleting some from mailbox. */ + ret = trash_try_clean_mails(mail_get_physical_size(mail)); + if (ret <= 0) + return 0; + } + + return 0; +} + +static void trash_quota_deinit(struct quota *quota) +{ + struct trash_quota *tquota = TRASH_CONTEXT(quota); + void *null = NULL; + + array_idx_set("a->quota_module_contexts, + trash_quota_module_id, &null); + tquota->super.deinit(quota); + i_free(tquota); +} + +static void trash_mail_storage_created(struct mail_storage *storage) +{ + struct trash_quota *tquota; + + if (trash_next_hook_mail_storage_created != NULL) + trash_next_hook_mail_storage_created(storage); + + if (quota_initialized || quota == NULL) + return; + + /* initialize here because plugins could be loaded in wrong order */ + quota_initialized = TRUE; + + tquota = i_new(struct trash_quota, 1); + tquota->super = *quota; + quota->deinit = trash_quota_deinit; + quota->try_alloc = trash_quota_try_alloc; + + trash_quota_module_id = quota_module_id++; + array_idx_set("a->quota_module_contexts, + trash_quota_module_id, &tquota); +} + +static int trash_mailbox_priority_cmp(const void *p1, const void *p2) +{ + const struct trash_mailbox *t1 = p1, *t2 = p2; + + return t1->priority - t2->priority; +} + +static int read_configuration(const char *path) +{ + struct istream *input; + const char *line, *name; + struct trash_mailbox *trash; + int fd; + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno != ENOENT) + i_error("open(%s) failed: %m", path); + return -1; + } + + p_clear(config_pool); + ARRAY_CREATE(&trash_boxes, config_pool, struct trash_mailbox, 8); + + input = i_stream_create_file(fd, default_pool, (size_t)-1, FALSE); + while ((line = i_stream_read_next_line(input)) != NULL) { + /* */ + name = strchr(line, ' '); + if (name == NULL || name[1] == '\0') + continue; + + trash = array_append_space(&trash_boxes); + trash->name = p_strdup(config_pool, name+1); + trash->priority = atoi(t_strdup_until(line, name)); + } + i_stream_unref(input); + (void)close(fd); + + qsort(array_get_modifyable(&trash_boxes, NULL), + array_count(&trash_boxes), sizeof(struct trash_mailbox), + trash_mailbox_priority_cmp); + return 0; +} + +void trash_plugin_init(void) +{ + quota_initialized = FALSE; + trash_next_hook_mail_storage_created = hook_mail_storage_created; + + config_pool = pool_alloconly_create("trash config", 1024); + if (read_configuration(home_expand(LOCAL_CONFIG_FILE)) < 0) { + if (read_configuration(GLOBAL_CONFIG_FILE) < 0) + return; + } + + hook_mail_storage_created = trash_mail_storage_created; +} + +void trash_plugin_deinit(void) +{ + pool_unref(config_pool); + hook_mail_storage_created = trash_next_hook_mail_storage_created; +} diff -r d67092398377 -r 732b62dc1976 src/plugins/trash/trash-plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/trash/trash-plugin.h Sat Dec 10 21:44:45 2005 +0200 @@ -0,0 +1,7 @@ +#ifndef __TRASH_PLUGIN_H +#define __TRASH_PLUGIN_H + +void trash_plugin_init(void); +void trash_plugin_deinit(void); + +#endif