Skip to content

Commit

Permalink
quota: Introduce quota_alloc_result return type
Browse files Browse the repository at this point in the history
  • Loading branch information
mrannanj authored and sirainen committed Mar 27, 2017
1 parent 2dec373 commit 42edee2
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 66 deletions.
46 changes: 25 additions & 21 deletions src/plugins/quota/quota-status.c
Expand Up @@ -45,34 +45,29 @@ static void client_reset(struct quota_client *client)
i_free_and_null(client->recipient);
}

static int
quota_check(struct mail_user *user, uoff_t mail_size,
const char **error_r, bool *too_large_r)
static enum quota_alloc_result
quota_check(struct mail_user *user, uoff_t mail_size, const char **error_r)
{
struct quota_user *quser = QUOTA_USER_CONTEXT(user);
struct mail_namespace *ns;
struct mailbox *box;
struct quota_transaction_context *ctx;
int ret;
enum quota_alloc_result ret;

if (quser == NULL) {
/* no quota for user */
return 1;
return QUOTA_ALLOC_RESULT_OK;
}

ns = mail_namespace_find_inbox(user->namespaces);
box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_POST_SESSION);

ctx = quota_transaction_begin(box);
ret = quota_test_alloc(ctx, I_MAX(1, mail_size), too_large_r);
ret = quota_test_alloc(ctx, I_MAX(1, mail_size));
*error_r = quota_alloc_result_errstr(ret, ctx);
quota_transaction_rollback(&ctx);

mailbox_free(&box);

if (ret < 0)
*error_r = "Internal quota calculation error";
else if (ret == 0)
*error_r = quser->quota->set->quota_exceeded_msg;
return ret;
}

Expand All @@ -82,7 +77,6 @@ static void client_handle_request(struct quota_client *client)
struct mail_storage_service_user *service_user;
struct mail_user *user;
const char *value = NULL, *error;
bool too_large;
int ret;

if (client->recipient == NULL) {
Expand All @@ -99,20 +93,30 @@ static void client_handle_request(struct quota_client *client)
if (ret == 0) {
value = nouser_reply;
} else if (ret > 0) {
if ((ret = quota_check(user, client->size, &error, &too_large)) > 0) {
/* under quota */
value = mail_user_plugin_getenv(user, "quota_status_success");
enum quota_alloc_result qret = quota_check(user, client->size,
&error);
switch (qret) {
case QUOTA_ALLOC_RESULT_OK: /* under quota */
value = mail_user_plugin_getenv(user,
"quota_status_success");
if (value == NULL)
value = "OK";
} else if (ret == 0) {
if (too_large) {
/* even over maximum quota */
value = mail_user_plugin_getenv(user, "quota_status_toolarge");
}
break;
/* even over maximum quota */
case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
value = mail_user_plugin_getenv(user,
"quota_status_toolarge");
/* fall through */
case QUOTA_ALLOC_RESULT_OVER_QUOTA:
if (value == NULL)
value = mail_user_plugin_getenv(user, "quota_status_overquota");
value = mail_user_plugin_getenv(user,
"quota_status_overquota");
if (value == NULL)
value = t_strdup_printf("554 5.2.2 %s", error);
break;
case QUOTA_ALLOC_RESULT_TEMPFAIL:
ret = -1;
break;
}
value = t_strdup(value); /* user's pool is being freed */
mail_user_unref(&user);
Expand Down
61 changes: 39 additions & 22 deletions src/plugins/quota/quota-storage.c
Expand Up @@ -46,6 +46,24 @@ static MODULE_CONTEXT_DEFINE_INIT(quota_mail_module, &mail_module_register);
static MODULE_CONTEXT_DEFINE_INIT(quota_mailbox_list_module,
&mailbox_list_module_register);

static void quota_set_storage_error(struct quota_transaction_context *qt,
struct mail_storage *storage,
enum quota_alloc_result res)
{
const char *errstr = quota_alloc_result_errstr(res, qt);
switch (res) {
case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
case QUOTA_ALLOC_RESULT_OVER_QUOTA:
mail_storage_set_error(storage, MAIL_ERROR_NOQUOTA, errstr);
break;
case QUOTA_ALLOC_RESULT_TEMPFAIL:
mail_storage_set_internal_error(storage);
break;
case QUOTA_ALLOC_RESULT_OK:
i_unreached();
}
}

static void quota_mail_expunge(struct mail *_mail)
{
struct mail_private *mail = (struct mail_private *)_mail;
Expand Down Expand Up @@ -95,14 +113,13 @@ quota_get_status(struct mailbox *box, enum mailbox_status_items items,
{
struct quota_mailbox *qbox = QUOTA_CONTEXT(box);
struct quota_transaction_context *qt;
bool too_large;
int ret = 0;

if ((items & STATUS_CHECK_OVER_QUOTA) != 0) {
qt = quota_transaction_begin(box);
if ((ret = quota_test_alloc(qt, 0, &too_large)) == 0) {
mail_storage_set_error(box->storage, MAIL_ERROR_NOQUOTA,
qt->quota->set->quota_exceeded_msg);
enum quota_alloc_result qret = quota_test_alloc(qt, 0);
if (qret != QUOTA_ALLOC_RESULT_OK) {
quota_set_storage_error(qt, box->storage, qret);
ret = -1;
}
quota_transaction_rollback(&qt);
Expand Down Expand Up @@ -216,8 +233,7 @@ static int quota_check(struct mail_save_context *ctx, struct mailbox *src_box)
{
struct mailbox_transaction_context *t = ctx->transaction;
struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
int ret;
bool too_large;
enum quota_alloc_result ret;

i_assert(!ctx->moving || src_box != NULL);

Expand All @@ -230,20 +246,20 @@ static int quota_check(struct mail_save_context *ctx, struct mailbox *src_box)
return 0;
}

ret = quota_try_alloc(qt, ctx->dest_mail, &too_large);
if (ret > 0)
ret = quota_try_alloc(qt, ctx->dest_mail);
switch (ret) {
case QUOTA_ALLOC_RESULT_OK:
return 0;
else if (ret == 0) {
mail_storage_set_error(t->box->storage, MAIL_ERROR_NOQUOTA,
qt->quota->set->quota_exceeded_msg);
return -1;
} else {
case QUOTA_ALLOC_RESULT_TEMPFAIL:
/* allow saving anyway. don't log an error, because at this
point we can't give very informative error without API
changes. the real error should have been logged already
(except if this was due to quota calculation on background,
then we intentionally don't want to log anything) */
return 0;
default:
quota_set_storage_error(qt, t->box->storage, ret);
return -1;
}
}

Expand Down Expand Up @@ -279,7 +295,6 @@ quota_save_begin(struct mail_save_context *ctx, struct istream *input)
struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
uoff_t size;
int ret;

if (!ctx->moving && i_stream_get_size(input, TRUE, &size) > 0) {
/* Input size is known, check for quota immediately. This
Expand All @@ -291,17 +306,19 @@ quota_save_begin(struct mail_save_context *ctx, struct istream *input)
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, size, &too_large);
if (ret == 0) {
mail_storage_set_error(t->box->storage,
MAIL_ERROR_NOQUOTA,
qt->quota->set->quota_exceeded_msg);
return -1;
} else if (ret < 0) {
enum quota_alloc_result qret = quota_test_alloc(qt, size);
switch (qret) {
case QUOTA_ALLOC_RESULT_OK:
/* Great, there is space. */
break;
case QUOTA_ALLOC_RESULT_TEMPFAIL:
/* allow saving anyway. don't log an error - see
quota_check() for reasons. */
break;
default:
quota_set_storage_error(qt, t->box->storage, qret);
return -1;
}
}

Expand Down
56 changes: 40 additions & 16 deletions src/plugins/quota/quota.c
Expand Up @@ -218,6 +218,21 @@ quota_root_add(struct quota_settings *quota_set, struct mail_user *user,
return 0;
}

const char *quota_alloc_result_errstr(enum quota_alloc_result res,
struct quota_transaction_context *qt)
{
switch (res) {
case QUOTA_ALLOC_RESULT_OK:
return "OK";
case QUOTA_ALLOC_RESULT_TEMPFAIL:
return "Internal quota calculation error";
case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
case QUOTA_ALLOC_RESULT_OVER_QUOTA:
return qt->quota->set->quota_exceeded_msg;
}
i_unreached();
}

int quota_user_read_settings(struct mail_user *user,
struct quota_settings **set_r,
const char **error_r)
Expand Down Expand Up @@ -1150,17 +1165,16 @@ void quota_transaction_rollback(struct quota_transaction_context **_ctx)
i_free(ctx);
}

int quota_try_alloc(struct quota_transaction_context *ctx,
struct mail *mail, bool *too_large_r)
enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
struct mail *mail)
{
uoff_t size;
int ret;

if (quota_transaction_set_limits(ctx) < 0)
return -1;
return QUOTA_ALLOC_RESULT_TEMPFAIL;

if (ctx->no_quota_updates)
return 1;
return QUOTA_ALLOC_RESULT_OK;

if (mail_get_physical_size(mail, &size) < 0) {
enum mail_error error;
Expand All @@ -1169,15 +1183,15 @@ int quota_try_alloc(struct quota_transaction_context *ctx,
if (error == MAIL_ERROR_EXPUNGED) {
/* mail being copied was already expunged. it'll fail,
so just return success for the quota allocated. */
return 1;
return QUOTA_ALLOC_RESULT_OK;
}
i_error("quota: Failed to get mail size (box=%s, uid=%u): %s",
mail->box->vname, mail->uid, errstr);
return -1;
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}

ret = quota_test_alloc(ctx, size, too_large_r);
if (ret <= 0)
enum quota_alloc_result ret = quota_test_alloc(ctx, size);
if (ret != QUOTA_ALLOC_RESULT_OK)
return ret;
/* with quota_try_alloc() we want to keep track of how many bytes
we've been adding/removing, so disable auto_updating=TRUE
Expand All @@ -1186,22 +1200,32 @@ int quota_try_alloc(struct quota_transaction_context *ctx,
transaction, but that doesn't normally happen. */
ctx->auto_updating = FALSE;
quota_alloc(ctx, mail);
return 1;
return QUOTA_ALLOC_RESULT_OK;
}

int quota_test_alloc(struct quota_transaction_context *ctx,
uoff_t size, bool *too_large_r)
enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
uoff_t size)
{
if (ctx->failed)
return -1;
return QUOTA_ALLOC_RESULT_TEMPFAIL;

if (quota_transaction_set_limits(ctx) < 0)
return -1;
return QUOTA_ALLOC_RESULT_TEMPFAIL;
if (ctx->no_quota_updates)
return 1;
return QUOTA_ALLOC_RESULT_OK;
/* this is a virtual function mainly for trash plugin and similar,
which may automatically delete mails to stay under quota. */
return ctx->quota->set->test_alloc(ctx, size, too_large_r);
bool too_large = FALSE;
int ret = ctx->quota->set->test_alloc(ctx, size, &too_large);
if (ret < 0) {
return QUOTA_ALLOC_RESULT_TEMPFAIL;
} else if (ret == 0 && too_large) {
return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT;
} else if (ret == 0 && !too_large) {
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
} else { /* (ret > 0) */
return QUOTA_ALLOC_RESULT_OK;
}
}

static int quota_default_test_alloc(struct quota_transaction_context *ctx,
Expand Down
23 changes: 16 additions & 7 deletions src/plugins/quota/quota.h
Expand Up @@ -30,6 +30,17 @@ enum quota_recalculate {
QUOTA_RECALCULATE_FORCED
};

enum quota_alloc_result {
QUOTA_ALLOC_RESULT_OK,
QUOTA_ALLOC_RESULT_TEMPFAIL,
QUOTA_ALLOC_RESULT_OVER_QUOTA,
/* Mail size is larger than even the maximum allowed quota. */
QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT,
};

const char *quota_alloc_result_errstr(enum quota_alloc_result res,
struct quota_transaction_context *qt);

int quota_user_read_settings(struct mail_user *user,
struct quota_settings **set_r,
const char **error_r);
Expand Down Expand Up @@ -78,14 +89,12 @@ 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, bool *too_large_r);
/* Allocate from quota if there's space. */
enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
struct mail *mail);
/* Like quota_try_alloc(), but don't actually allocate anything. */
int quota_test_alloc(struct quota_transaction_context *ctx,
uoff_t size, bool *too_large_r);
enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
uoff_t size);
/* 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);
Expand Down

0 comments on commit 42edee2

Please sign in to comment.