quota-storage.c revision bda9a6d9b021c122a01a85cb3cee2f996263d8f0
02c335c23bf5fa225a467c19f2c063fb0dc7b8c3Timo Sirainen/* Copyright (C) 2005 Timo Sirainen */
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "lib.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "array.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "istream.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "mail-search.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "mail-storage-private.h"
a2fdfd2efdbb2d912aad23900a466cf74114920bTimo Sirainen#include "quota-private.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "quota-plugin.h"
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include <sys/stat.h>
c6be98b5270900746f35ebe28bd636019976e29eTimo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#define QUOTA_CONTEXT(obj) \
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen *((void **)array_idx_modifiable(&(obj)->module_contexts, \
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen quota_storage_module_id))
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstruct quota_mail_storage {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mail_storage_vfuncs super;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen};
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstruct quota_mailbox {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mailbox_vfuncs super;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen unsigned int save_hack:1;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen};
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainenstruct quota_mail {
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen struct mail_vfuncs super;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen};
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic unsigned int quota_storage_module_id = 0;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic bool quota_storage_module_id_set = FALSE;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic int quota_mail_expunge(struct mail *_mail)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mail_private *mail = (struct mail_private *)_mail;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_mail *qmail = QUOTA_CONTEXT(mail);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_transaction_context *qt =
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen QUOTA_CONTEXT(_mail->transaction);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (qmail->super.expunge(_mail) < 0)
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi return -1;
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi quota_free(qt, _mail);
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi return 0;
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi}
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic struct mailbox_transaction_context *
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenquota_mailbox_transaction_begin(struct mailbox *box,
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen enum mailbox_transaction_flags flags)
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen{
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(box);
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen struct mailbox_transaction_context *t;
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen struct quota_transaction_context *qt;
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi t = qbox->super.transaction_begin(box, flags);
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi qt = quota_transaction_begin(quota_set, box);
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi array_idx_set(&t->module_contexts, quota_storage_module_id, &qt);
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi return t;
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic int
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenquota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen enum mailbox_sync_flags flags)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (qbox->super.transaction_commit(ctx, flags) < 0) {
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen quota_transaction_rollback(qt);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return -1;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen } else {
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen (void)quota_transaction_commit(qt);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (qt->tmp_mail != NULL)
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi mail_free(&qt->tmp_mail);
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen return 0;
a13b1245bee0b6524b4aeb3c8fd9e34af648b746Aki Tuomi }
c6be98b5270900746f35ebe28bd636019976e29eTimo Sirainen}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
a2fdfd2efdbb2d912aad23900a466cf74114920bTimo Sirainenstatic void
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenquota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen qbox->super.transaction_rollback(ctx);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (qt->tmp_mail != NULL)
c6be98b5270900746f35ebe28bd636019976e29eTimo Sirainen mail_free(&qt->tmp_mail);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen quota_transaction_rollback(qt);
a2fdfd2efdbb2d912aad23900a466cf74114920bTimo Sirainen}
237a6211c7fc4d6dbb58dd0467da6dba1b8f21f6Timo Sirainen
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainenstatic struct mail *
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomiquota_mail_alloc(struct mailbox_transaction_context *t,
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen enum mail_fetch_field wanted_fields,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mailbox_header_lookup_ctx *wanted_headers)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct quota_mail *qmail;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mail *_mail;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mail_private *mail;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen _mail = qbox->super.mail_alloc(t, wanted_fields, wanted_headers);
2d1892aaeb63b9774237b6e60d6bb04bf6f8259cTimo Sirainen mail = (struct mail_private *)_mail;
2d1892aaeb63b9774237b6e60d6bb04bf6f8259cTimo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen qmail = p_new(mail->pool, struct quota_mail, 1);
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen qmail->super = mail->v;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
2d1892aaeb63b9774237b6e60d6bb04bf6f8259cTimo Sirainen mail->v.expunge = quota_mail_expunge;
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen array_idx_set(&mail->module_contexts, quota_storage_module_id, &qmail);
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen return _mail;
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen}
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainenstatic int quota_check(struct mailbox_transaction_context *t, struct mail *mail)
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen{
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen int ret;
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen bool too_large;
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen ret = quota_try_alloc(qt, mail, &too_large);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (ret > 0)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return 0;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen else if (ret == 0) {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen mail_storage_set_error(t->box->storage, "Quota exceeded");
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return -1;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen } else {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen mail_storage_set_error(t->box->storage,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen "Internal quota calculation error");
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return -1;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen }
d6b3cfd855c0eebed68be50d3111de1b5a6afeb0Timo Sirainen}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic int
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenquota_copy(struct mailbox_transaction_context *t, struct mail *mail,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen enum mail_flags flags, struct mail_keywords *keywords,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen struct mail *dest_mail)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
if (dest_mail == NULL) {
/* we always want to know the mail size */
if (qt->tmp_mail == NULL) {
qt->tmp_mail = mail_alloc(t, MAIL_FETCH_PHYSICAL_SIZE,
NULL);
}
dest_mail = qt->tmp_mail;
}
qbox->save_hack = FALSE;
if (qbox->super.copy(t, mail, flags, keywords, dest_mail) < 0)
return -1;
/* if copying used saving internally, we already checked the quota
and set qbox->save_hack = TRUE. */
return qbox->save_hack ? 0 : quota_check(t, dest_mail);
}
static int
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,
struct mail *dest_mail, struct mail_save_context **ctx_r)
{
struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
const struct stat *st;
int ret;
st = i_stream_stat(input, TRUE);
if (st != NULL && st->st_size != -1) {
/* Input size is known, check for quota immediately. This
check isn't perfect, especially because input stream's
linefeeds may contain CR+LFs while physical message would
only contain LFs. With mbox some headers might be skipped
entirely.
I think these don't really matter though compared to the
benefit of giving "out of quota" error before sending the
full mail. */
bool too_large;
ret = quota_test_alloc(qt, st->st_size, &too_large);
if (ret == 0) {
mail_storage_set_error(t->box->storage,
"Quota exceeded");
return -1;
} else if (ret < 0) {
mail_storage_set_error(t->box->storage,
"Internal quota calculation error");
return -1;
}
}
if (dest_mail == NULL) {
/* we always want to know the mail size */
if (qt->tmp_mail == NULL) {
qt->tmp_mail = mail_alloc(t, MAIL_FETCH_PHYSICAL_SIZE,
NULL);
}
dest_mail = qt->tmp_mail;
}
return qbox->super.save_init(t, flags, keywords, received_date,
timezone_offset, from_envelope,
input, dest_mail, ctx_r);
}
static int quota_save_finish(struct mail_save_context *ctx)
{
struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx->transaction);
struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->transaction->box);
if (qbox->super.save_finish(ctx) < 0)
return -1;
qbox->save_hack = TRUE;
return quota_check(ctx->transaction, ctx->dest_mail != NULL ?
ctx->dest_mail : qt->tmp_mail);
}
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;
}
static int quota_mailbox_delete(struct mail_storage *storage, const char *name)
{
struct quota_mail_storage *qstorage = QUOTA_CONTEXT(storage);
struct mailbox *box;
struct mail_search_context *ctx;
struct mailbox_transaction_context *t;
struct quota_transaction_context *qt;
struct mail *mail;
struct mail_search_arg search_arg;
int ret;
/* This is a bit annoying to handle. We'll have to open the mailbox
and free the quota for all the messages existing in it. Open the
mailbox locked so that other processes can't mess up the quota
calculations by adding/removing mails while we're doing this. */
box = mailbox_open(storage, name, NULL, MAILBOX_OPEN_FAST |
MAILBOX_OPEN_KEEP_RECENT | MAILBOX_OPEN_KEEP_LOCKED);
if (box == NULL)
return -1;
memset(&search_arg, 0, sizeof(search_arg));
search_arg.type = SEARCH_ALL;
t = mailbox_transaction_begin(box, 0);
qt = QUOTA_CONTEXT(t);
ctx = mailbox_search_init(t, NULL, &search_arg, NULL);
mail = mail_alloc(t, 0, NULL);
while (mailbox_search_next(ctx, mail) > 0)
quota_free(qt, mail);
mail_free(&mail);
ret = mailbox_search_deinit(&ctx);
if (ret < 0)
mailbox_transaction_rollback(&t);
else
ret = mailbox_transaction_commit(&t, 0);
mailbox_close(&box);
/* FIXME: here's an unfortunate race condition */
return ret < 0 ? -1 :
qstorage->super.mailbox_delete(storage, name);
}
static void quota_storage_destroy(struct mail_storage *storage)
{
struct quota_mail_storage *qstorage = QUOTA_CONTEXT(storage);
quota_remove_user_storage(quota_set, storage);
qstorage->super.destroy(storage);
}
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.destroy = quota_storage_destroy;
storage->v.mailbox_open = quota_mailbox_open;
storage->v.mailbox_delete = quota_mailbox_delete;
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);
if ((storage->flags & MAIL_STORAGE_FLAG_SHARED_NAMESPACE) == 0) {
/* register to user's quota roots */
quota_add_user_storage(quota_set, storage);
}
}