quota-storage.c revision 3ccfcf0856958cb9208a9fc51c3bdf13c58ad52a
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen/* Copyright (C) 2005 Timo Sirainen */
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen#include "lib.h"
dbb1fb1c51727e2050792f8c333b212e22a36d69Timo Sirainen#include "array.h"
6789ed17e7ca4021713507baf0dcf6979bb42e0cTimo Sirainen#include "istream.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "mail-search.h"
dbb1fb1c51727e2050792f8c333b212e22a36d69Timo Sirainen#include "mail-storage-private.h"
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen#include "quota-private.h"
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen#include "quota-plugin.h"
e667602217af55105d44d8d9b75f09a8a9ac2f14Timo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen#include <sys/stat.h>
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen#define QUOTA_CONTEXT(obj) \
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen *((void **)array_idx_modifiable(&(obj)->module_contexts, \
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen quota_storage_module_id))
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstruct quota_mail_storage {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mail_storage_vfuncs super;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen};
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstruct quota_mailbox {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mailbox_vfuncs super;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
bcd8a160ce32e5afe3566793b5a144028063b2fbTimo Sirainen unsigned int save_hack:1;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen};
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstruct quota_mail {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mail_vfuncs super;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen};
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e667602217af55105d44d8d9b75f09a8a9ac2f14Timo Sirainenstatic unsigned int quota_storage_module_id = 0;
e667602217af55105d44d8d9b75f09a8a9ac2f14Timo Sirainenstatic bool quota_storage_module_id_set = FALSE;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstatic int quota_mail_expunge(struct mail *_mail)
e667602217af55105d44d8d9b75f09a8a9ac2f14Timo Sirainen{
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mail_private *mail = (struct mail_private *)_mail;
e667602217af55105d44d8d9b75f09a8a9ac2f14Timo Sirainen struct quota_mail *qmail = QUOTA_CONTEXT(mail);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct quota_transaction_context *qt =
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen QUOTA_CONTEXT(_mail->transaction);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen if (qmail->super.expunge(_mail) < 0)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen return -1;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen quota_free(qt, _mail);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen return 0;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen}
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstatic struct mailbox_transaction_context *
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenquota_mailbox_transaction_begin(struct mailbox *box,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen enum mailbox_transaction_flags flags)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen{
dbb1fb1c51727e2050792f8c333b212e22a36d69Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(box);
dbb1fb1c51727e2050792f8c333b212e22a36d69Timo Sirainen struct mailbox_transaction_context *t;
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen struct quota_transaction_context *qt;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen t = qbox->super.transaction_begin(box, flags);
6789ed17e7ca4021713507baf0dcf6979bb42e0cTimo Sirainen qt = quota_transaction_begin(quota, box);
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen array_idx_set(&t->module_contexts, quota_storage_module_id, &qt);
6789ed17e7ca4021713507baf0dcf6979bb42e0cTimo Sirainen return t;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen}
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
6789ed17e7ca4021713507baf0dcf6979bb42e0cTimo Sirainenstatic int
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainenquota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx,
33d63688ed8b26dc333e3c2edbfb2fe6e412604dTimo Sirainen enum mailbox_sync_flags flags)
645f258ea29afaf09b673fc65d1bd788dfec8db8Timo Sirainen{
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
e667602217af55105d44d8d9b75f09a8a9ac2f14Timo Sirainen if (qbox->super.transaction_commit(ctx, flags) < 0) {
6389aeec8c26b585e583c364b48ad12adf741898Timo Sirainen quota_transaction_rollback(qt);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen return -1;
96541d31299bb40b5a6efdbf9b4cb3d4f4b4a069Timo Sirainen } else {
96541d31299bb40b5a6efdbf9b4cb3d4f4b4a069Timo Sirainen (void)quota_transaction_commit(qt);
644268f7848a7c4221146d0b11feb8ed5bbed233Timo Sirainen if (qt->tmp_mail != NULL)
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen mail_free(&qt->tmp_mail);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen return 0;
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen }
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen}
1bdda5c0c30463160c47151537e6bb2c6c994841Timo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenstatic void
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainenquota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx)
a58e4aec412a30352d9d45e63726cac044aa6aa5Timo Sirainen{
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx);
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen qbox->super.transaction_rollback(ctx);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen if (qt->tmp_mail != NULL)
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen mail_free(&qt->tmp_mail);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen quota_transaction_rollback(qt);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen}
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainenstatic struct mail *
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenquota_mail_alloc(struct mailbox_transaction_context *t,
33d63688ed8b26dc333e3c2edbfb2fe6e412604dTimo Sirainen enum mail_fetch_field wanted_fields,
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen struct mailbox_header_lookup_ctx *wanted_headers)
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen{
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct quota_mail *qmail;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen struct mail *_mail;
98dd8e6e81f11f1e6040ca72f4916242d246c863Timo Sirainen struct mail_private *mail;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen _mail = qbox->super.mail_alloc(t, wanted_fields, wanted_headers);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen mail = (struct mail_private *)_mail;
98dd8e6e81f11f1e6040ca72f4916242d246c863Timo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen qmail = p_new(mail->pool, struct quota_mail, 1);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen qmail->super = mail->v;
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen mail->v.expunge = quota_mail_expunge;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen array_idx_set(&mail->module_contexts, quota_storage_module_id, &qmail);
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen return _mail;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen}
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainenstatic int quota_check(struct mailbox_transaction_context *t, struct mail *mail)
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen{
6789ed17e7ca4021713507baf0dcf6979bb42e0cTimo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen int ret;
644268f7848a7c4221146d0b11feb8ed5bbed233Timo Sirainen bool too_large;
644268f7848a7c4221146d0b11feb8ed5bbed233Timo Sirainen
644268f7848a7c4221146d0b11feb8ed5bbed233Timo Sirainen ret = quota_try_alloc(qt, mail, &too_large);
2aecf7be5834e7f6520f8deaad683a6fa1de4d61Timo Sirainen if (ret > 0)
2aecf7be5834e7f6520f8deaad683a6fa1de4d61Timo Sirainen return 0;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen else if (ret == 0) {
1bdda5c0c30463160c47151537e6bb2c6c994841Timo Sirainen mail_storage_set_error(t->box->storage, "Quota exceeded");
1bdda5c0c30463160c47151537e6bb2c6c994841Timo Sirainen return -1;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen } else {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen mail_storage_set_error(t->box->storage,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen "Internal quota calculation error");
1bdda5c0c30463160c47151537e6bb2c6c994841Timo Sirainen return -1;
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen }
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen}
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainenstatic int
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenquota_copy(struct mailbox_transaction_context *t, struct mail *mail,
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen enum mail_flags flags, struct mail_keywords *keywords,
d67fde1a8ebc1d85704c5986d8f93aae97eccef3Timo Sirainen struct mail *dest_mail)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (dest_mail == NULL) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* we always want to know the mail size */
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen if (qt->tmp_mail == NULL) {
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen qt->tmp_mail = mail_alloc(t, MAIL_FETCH_PHYSICAL_SIZE,
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen NULL);
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen dest_mail = qt->tmp_mail;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen }
1171f0abf442638bac1827bb24a0b6b8eb682a82Timo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen qbox->save_hack = FALSE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (qbox->super.copy(t, mail, flags, keywords, dest_mail) < 0)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen return -1;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* if copying used saving internally, we already checked the quota
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen and set qbox->save_hack = TRUE. */
e8a59a1671127f87e2d22f42e84c572f28299d81Timo Sirainen 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, 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, storage);
}
}