ARRAY(struct quota_root_settings *) root_sets;
enum quota_alloc_result (*test_alloc)(
- struct quota_transaction_context *ctx, uoff_t size);
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r);
uoff_t max_mail_size;
const char *quota_exceeded_msg;
mailbox_set_reason(box, "quota status");
ctx = quota_transaction_begin(box);
- ret = quota_test_alloc(ctx, I_MAX(1, mail_size));
+ const char *internal_error;
+ ret = quota_test_alloc(ctx, I_MAX(1, mail_size), &internal_error);
+ if (ret == QUOTA_ALLOC_RESULT_TEMPFAIL)
+ i_error("quota check failed: %s", internal_error);
*error_r = quota_alloc_result_errstr(ret, ctx);
quota_transaction_rollback(&ctx);
if ((items & STATUS_CHECK_OVER_QUOTA) != 0) {
qt = quota_transaction_begin(box);
- enum quota_alloc_result qret = quota_test_alloc(qt, 0);
+ const char *error;
+ enum quota_alloc_result qret = quota_test_alloc(qt, 0, &error);
if (qret != QUOTA_ALLOC_RESULT_OK) {
+ if (qret == QUOTA_ALLOC_RESULT_TEMPFAIL)
+ i_error("quota check failed: %s", error);
quota_set_storage_error(qt, box->storage, qret);
ret = -1;
}
return 0;
}
- ret = quota_try_alloc(qt, ctx->dest_mail);
+ const char *error;
+ ret = quota_try_alloc(qt, ctx->dest_mail, &error);
switch (ret) {
case QUOTA_ALLOC_RESULT_OK:
return 0;
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) */
+ /* Log the error, but allow saving anyway. */
+ i_error("quota check failed: %s", error);
return 0;
default:
quota_set_storage_error(qt, t->box->storage, ret);
struct mailbox_transaction_context *t = ctx->transaction;
struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
+ const char *error;
uoff_t size;
if (!ctx->moving && i_stream_get_size(input, TRUE, &size) > 0) {
benefit of giving "out of quota" error before sending the
full mail. */
- enum quota_alloc_result qret = quota_test_alloc(qt, size);
+ enum quota_alloc_result qret = quota_test_alloc(qt, size, &error);
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. */
+ /* Log the error, but allow saving anyway. */
+ i_error("quota allocation failed: %s", error);
break;
default:
quota_set_storage_error(qt, t->box->storage, qret);
struct quota_param_parser quota_param_ns = {.param_name = "ns=", .param_handler = ns_param_handler};
static enum quota_alloc_result quota_default_test_alloc(
- struct quota_transaction_context *ctx, uoff_t size);
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r);
static void quota_over_flag_check_root(struct quota_root *root);
static const struct quota_backend *quota_backend_find(const char *name)
}
enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
- struct mail *mail)
+ struct mail *mail, const char **error_r)
{
uoff_t size;
- if (quota_transaction_set_limits(ctx) < 0)
+ if (quota_transaction_set_limits(ctx) < 0) {
+ *error_r = "Failed to set quota transaction limits";
return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
if (ctx->no_quota_updates)
return QUOTA_ALLOC_RESULT_OK;
so just return success for the quota allocated. */
return QUOTA_ALLOC_RESULT_OK;
}
- i_error("quota: Failed to get mail size (box=%s, uid=%u): %s",
+ *error_r = t_strdup_printf(
+ "Failed to get mail size (box=%s, uid=%u): %s",
mail->box->vname, mail->uid, errstr);
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
- enum quota_alloc_result ret = quota_test_alloc(ctx, size);
+ enum quota_alloc_result ret = quota_test_alloc(ctx, size, error_r);
if (ret != QUOTA_ALLOC_RESULT_OK)
return ret;
/* with quota_try_alloc() we want to keep track of how many bytes
}
enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
- uoff_t size)
+ uoff_t size, const char **error_r)
{
- if (ctx->failed)
+ if (ctx->failed) {
+ *error_r = "Quota transaction has failed earlier";
return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
- if (quota_transaction_set_limits(ctx) < 0)
+ if (quota_transaction_set_limits(ctx) < 0) {
+ *error_r = "Failed to set quota transaction limits";
return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
uoff_t max_size = ctx->quota->set->max_mail_size;
- if (max_size > 0 && size > max_size)
+ if (max_size > 0 && size > max_size) {
+ *error_r = t_strdup_printf(
+ "Requested allocation size %"PRIuUOFF_T" exceeds max "
+ "mail size %"PRIuUOFF_T, size, max_size);
return QUOTA_ALLOC_RESULT_OVER_MAXSIZE;
+ }
if (ctx->no_quota_updates)
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);
+ return ctx->quota->set->test_alloc(ctx, size, error_r);
}
static enum quota_alloc_result quota_default_test_alloc(
- struct quota_transaction_context *ctx, uoff_t size)
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r)
{
struct quota_root *const *roots;
unsigned int i, count;
mailbox_get_vname(ctx->box),
&bytes_limit, &count_limit,
&ignore);
- if (ret < 0)
+ if (ret < 0) {
+ *error_r = "Failed to get quota root rule limits";
return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
/* if size is bigger than any limit, then
it is bigger than the lowest limit */
- if (bytes_limit > 0 && size > bytes_limit)
+ if (bytes_limit > 0 && size > bytes_limit) {
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota limit",
+ size);
return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT;
+ }
}
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
}
/* Rollback quota transaction changes. */
void quota_transaction_rollback(struct quota_transaction_context **ctx);
-/* Allocate from quota if there's space. */
+/* Allocate from quota if there's space. error_r is set when result is not
+ * QUOTA_ALLOC_RESULT_OK. */
enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
- struct mail *mail);
+ struct mail *mail, const char **error_r);
/* Like quota_try_alloc(), but don't actually allocate anything. */
enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
- uoff_t size);
+ uoff_t size, const char **error_r);
/* Update quota by allocating/freeing space used by mail. */
void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail);
void quota_free_bytes(struct quota_transaction_context *ctx,
static MODULE_CONTEXT_DEFINE_INIT(trash_user_module,
&mail_user_module_register);
static enum quota_alloc_result (*trash_next_quota_test_alloc)(
- struct quota_transaction_context *, uoff_t);
+ struct quota_transaction_context *, uoff_t,
+ const char **error_r);
static int trash_clean_mailbox_open(struct trash_mailbox *trash)
{
static enum quota_alloc_result
trash_quota_test_alloc(struct quota_transaction_context *ctx,
- uoff_t size)
+ uoff_t size, const char **error_r)
{
int i;
uint64_t size_needed = 0;
for (i = 0; ; i++) {
enum quota_alloc_result ret;
- ret = trash_next_quota_test_alloc(ctx, size);
+ ret = trash_next_quota_test_alloc(ctx, size, error_r);
if (ret != QUOTA_ALLOC_RESULT_OVER_QUOTA) {
if (ret == QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT &&
ctx->quota->user->mail_debug)
count_needed = 1 + ctx->count_over - ctx->count_ceil;
/* not enough space. try deleting some from mailbox. */
- if (trash_try_clean_mails(ctx, size_needed, count_needed) <= 0)
+ if (trash_try_clean_mails(ctx, size_needed, count_needed) <= 0) {
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
+ }
}
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
}