enum quota_alloc_result (*test_alloc)(
struct quota_transaction_context *ctx, uoff_t size,
- const char **error_r);
+ const struct quota_overrun **overruns_r, const char **error_r);
+
bool vsizes:1;
};
bool have_under_warnings:1;
};
+struct quota_transaction_root_context {
+ /* how many bytes/mails can be saved until limit is reached.
+ (set once, not updated by bytes_used/count_used).
+
+ if last_mail_max_extra_bytes>0, the bytes_ceil is initially
+ increased by that much, while bytes_ceil2 contains the real ceiling.
+ after the first allocation is done, bytes_ceil is set to
+ bytes_ceil2. */
+ uint64_t bytes_ceil, bytes_ceil2, count_ceil;
+ /* How many bytes/mails we are over quota. Like *_ceil, these are set
+ only once and not updated by bytes_used/count_used. (Either *_ceil
+ or *_over is always zero.) */
+ uint64_t bytes_over, count_over;
+};
+
struct quota_transaction_context {
union mailbox_transaction_module_context module_ctx;
const struct quota_settings *set;
+ struct quota_transaction_root_context *roots;
+
int64_t bytes_used, count_used;
/* how many bytes/mails can be saved until limit is reached.
(set once, not updated by bytes_used/count_used).
int quota_get_mail_size(struct quota_transaction_context *ctx,
struct mail *mail, uoff_t *size_r);
bool quota_transaction_is_over(struct quota_transaction_context *ctx, uoff_t size);
+bool quota_root_is_over(struct quota_transaction_context *ctx,
+ struct quota_transaction_root_context *root,
+ uoff_t count_alloc, uoff_t bytes_alloc,
+ uoff_t *count_overrun_r, uoff_t *bytes_overrun_r);
int quota_transaction_set_limits(struct quota_transaction_context *ctx,
enum quota_get_result *error_result_r,
const char **error_r);
ctx = quota_transaction_begin(box);
const char *internal_error;
- ret = quota_test_alloc(ctx, I_MAX(1, mail_size), &internal_error);
+ ret = quota_test_alloc(ctx, I_MAX(1, mail_size), NULL, &internal_error);
if (ret == QUOTA_ALLOC_RESULT_TEMPFAIL)
e_error(user->event, "quota check failed: %s", internal_error);
*error_r = quota_alloc_result_errstr(ret, ctx);
if ((items & STATUS_CHECK_OVER_QUOTA) != 0) {
qt = quota_transaction_begin(box);
const char *error;
- enum quota_alloc_result qret = quota_test_alloc(qt, 0, &error);
+ enum quota_alloc_result qret =
+ quota_test_alloc(qt, 0, NULL, &error);
if (qret != QUOTA_ALLOC_RESULT_OK) {
quota_set_storage_error(qt, box, qret, error);
ret = -1;
}
const char *error;
- ret = quota_try_alloc(qt, ctx->dest_mail, &error);
+ ret = quota_try_alloc(qt, ctx->dest_mail, NULL, &error);
switch (ret) {
case QUOTA_ALLOC_RESULT_OK:
return 0;
benefit of giving "out of quota" error before sending the
full mail. */
- enum quota_alloc_result qret = quota_test_alloc(qt, size, &error);
+ enum quota_alloc_result qret =
+ quota_test_alloc(qt, size, NULL, &error);
switch (qret) {
case QUOTA_ALLOC_RESULT_OK:
/* Great, there is space. */
}
static inline bool
-quota_is_over(uoff_t alloc, int64_t used, uint64_t ceil, uint64_t over)
+quota_is_over(uoff_t alloc, int64_t used, uint64_t ceil, uint64_t over,
+ uoff_t *overrun_r)
{
/* The over parameter is the amount by which the resource usage exceeds
the limit already. The ceil parameter is the amount by which the
if (over > deleted) {
/* We are over quota, even after deletions and
without the new allocation. */
+ if (overrun_r != NULL)
+ *overrun_r = (over - deleted) + alloc;
return TRUE;
}
if (alloc > (deleted - over)) {
/* We are under quota after deletions, but the
the new allocation exceeds the quota once
more. */
+ if (overrun_r != NULL)
+ *overrun_r = alloc - (deleted - over);
return TRUE;
}
} else {
if (alloc > deleted && (alloc - deleted) > ceil) {
/* The new allocation exceeds the quota limit.
*/
+ if (overrun_r != NULL)
+ *overrun_r = (alloc - deleted) - ceil;
return TRUE;
}
}
/* Resource usage increased in this transaction. */
if (over > 0) {
/* Resource usage is already over quota. */
+ if (overrun_r != NULL)
+ *overrun_r = over + (uoff_t)used + alloc;
return TRUE;
}
if (ceil < alloc || (ceil - alloc) < (uint64_t)used) {
/* Limit reached. */
+ if (overrun_r != NULL)
+ *overrun_r = (uoff_t)used + alloc - ceil;
return TRUE;
}
}
/* Not over quota. */
+ if (overrun_r != NULL)
+ *overrun_r = 0;
return FALSE;
}
uoff_t size)
{
if (quota_is_over(1, ctx->count_used, ctx->count_ceil,
- ctx->count_over))
+ ctx->count_over, NULL))
return TRUE;
if (quota_is_over(size, ctx->bytes_used, ctx->bytes_ceil,
- ctx->bytes_over))
+ ctx->bytes_over, NULL))
return TRUE;
return FALSE;
}
+
+bool quota_root_is_over(struct quota_transaction_context *ctx,
+ struct quota_transaction_root_context *root,
+ uoff_t count_alloc, uoff_t bytes_alloc,
+ uoff_t *count_overrun_r, uoff_t *bytes_overrun_r)
+{
+ *count_overrun_r = 0;
+ *bytes_overrun_r = 0;
+
+ return (quota_is_over(count_alloc, ctx->count_used,
+ root->count_ceil, root->count_over,
+ count_overrun_r) ||
+ quota_is_over(bytes_alloc, ctx->bytes_used,
+ root->bytes_ceil, root->bytes_over,
+ bytes_overrun_r));
+}
static ARRAY(const struct quota_backend*) quota_backends;
-static enum quota_alloc_result quota_default_test_alloc(
- struct quota_transaction_context *ctx, uoff_t size,
- const char **error_r);
+static enum quota_alloc_result
+quota_default_test_alloc(struct quota_transaction_context *ctx, uoff_t size,
+ const struct quota_overrun **overruns_r,
+ const char **error_r);
static void quota_over_status_check_root(struct quota_root *root);
static struct quota_root *
quota_root_find(struct quota *quota, const char *name);
i_assert(ctx->quota != NULL);
roots = array_get(&ctx->quota->all_roots, &roots_count);
+ if (roots_count > 0) {
+ ctx->roots = i_new(struct quota_transaction_root_context,
+ roots_count);
+ }
ctx->box = box;
ctx->bytes_ceil = (uint64_t)-1;
ctx->auto_updating = TRUE;
for (i = 0; i < roots_count; i++) {
+ ctx->roots[i].bytes_ceil = (uint64_t)-1;
+ ctx->roots[i].bytes_ceil2 = (uint64_t)-1;
+ ctx->roots[i].count_ceil = (uint64_t)-1;
+
if (!quota_root_is_visible(roots[i], ctx->box))
continue;
if (ret == QUOTA_GET_RESULT_LIMITED) {
if (limit <= current) {
/* over quota */
+ ctx->roots[i].bytes_ceil = 0;
+ ctx->roots[i].bytes_ceil2 = 0;
ctx->bytes_ceil = 0;
ctx->bytes_ceil2 = 0;
diff = current - limit;
+ ctx->roots[i].bytes_over = diff;
if (ctx->bytes_over < diff)
ctx->bytes_over = diff;
} else {
diff = limit - current;
+ ctx->roots[i].bytes_ceil2 = diff;
if (ctx->bytes_ceil2 > diff)
ctx->bytes_ceil2 = diff;
diff += !use_grace ? 0 :
roots[i]->set->quota_storage_grace;
+ ctx->roots[i].bytes_ceil = diff;
if (ctx->bytes_ceil > diff)
ctx->bytes_ceil = diff;
}
if (ret == QUOTA_GET_RESULT_LIMITED) {
if (limit <= current) {
/* over quota */
+ ctx->roots[i].count_ceil = 0;
ctx->count_ceil = 0;
diff = current - limit;
+ ctx->roots[i].count_over = diff;
if (ctx->count_over < diff)
ctx->count_over = diff;
} else {
diff = limit - current;
+ ctx->roots[i].count_ceil = diff;
if (ctx->count_ceil > diff)
ctx->count_ceil = diff;
}
} T_END;
settings_free(ctx->set);
+ i_free(ctx->roots);
i_free(ctx);
return ret;
}
*_ctx = NULL;
settings_free(ctx->set);
+ i_free(ctx->roots);
i_free(ctx);
}
static void quota_alloc_with_size(struct quota_transaction_context *ctx,
uoff_t size)
{
+ unsigned int i;
+
ctx->bytes_used += size;
ctx->bytes_ceil = ctx->bytes_ceil2;
+ for (i = 0; i < array_count(&ctx->quota->all_roots); i++)
+ ctx->roots[i].bytes_ceil = ctx->roots[i].bytes_ceil2;
ctx->count_used++;
}
-enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
- struct mail *mail, const char **error_r)
+enum quota_alloc_result
+quota_try_alloc(struct quota_transaction_context *ctx, struct mail *mail,
+ const struct quota_overrun **overruns_r, const char **error_r)
{
uoff_t size;
const char *error;
enum quota_get_result error_res;
+ if (overruns_r != NULL)
+ *overruns_r = NULL;
+
if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) {
if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
return QUOTA_ALLOC_RESULT_BACKGROUND_CALC;
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
- enum quota_alloc_result ret = quota_test_alloc(ctx, size, error_r);
+ enum quota_alloc_result ret =
+ quota_test_alloc(ctx, size, overruns_r, error_r);
if (ret != QUOTA_ALLOC_RESULT_OK)
return ret;
/* with quota_try_alloc() we want to keep track of how many bytes
return QUOTA_ALLOC_RESULT_OK;
}
-enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
- uoff_t size, const char **error_r)
+enum quota_alloc_result
+quota_test_alloc(struct quota_transaction_context *ctx, uoff_t size,
+ const struct quota_overrun **overruns_r, const char **error_r)
{
+ if (overruns_r != NULL)
+ *overruns_r = NULL;
+
if (ctx->failed) {
*error_r = "Quota transaction has failed earlier";
return QUOTA_ALLOC_RESULT_TEMPFAIL;
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->test_alloc(ctx, size, error_r);
+ return ctx->quota->test_alloc(ctx, size, overruns_r, error_r);
}
-static enum quota_alloc_result quota_default_test_alloc(
- struct quota_transaction_context *ctx, uoff_t size,
- const char **error_r)
+static enum quota_alloc_result
+quota_default_test_alloc(struct quota_transaction_context *ctx, uoff_t size,
+ const struct quota_overrun **overruns_r,
+ const char **error_r)
{
struct quota_root *const *roots;
+ ARRAY(struct quota_overrun) overruns;
unsigned int i, count;
bool ignore;
+ if (overruns_r != NULL)
+ *overruns_r = NULL;
+
if (!quota_transaction_is_over(ctx, size))
return QUOTA_ALLOC_RESULT_OK;
}
/* limit reached. */
+
+ t_array_init(&overruns, 4);
+
roots = array_get(&ctx->quota->all_roots, &count);
for (i = 0; i < count; i++) {
uint64_t bytes_limit, count_limit;
+ struct quota_overrun overrun;
if (!quota_root_is_visible(roots[i], ctx->box) ||
!roots[i]->set->quota_enforce)
size);
return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT;
}
+
+ i_zero(&overrun);
+ if (quota_root_is_over(ctx, &ctx->roots[i], 1, size,
+ &overrun.resource.count,
+ &overrun.resource.bytes)) {
+ overrun.root = roots[i];
+ array_append(&overruns, &overrun, 1);
+ }
}
+
+ i_assert(array_count(&overruns) > 0);
+ if (overruns_r != NULL) {
+ array_append_zero(&overruns);
+ *overruns_r = array_front(&overruns);
+ }
+
*error_r = t_strdup_printf(
"Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
QUOTA_GET_RESULT_UNLIMITED,
};
+struct quota_overrun {
+ struct quota_root *root;
+
+ struct {
+ uoff_t count;
+ uoff_t bytes;
+ } resource;
+};
+
const char *quota_alloc_result_errstr(enum quota_alloc_result res,
struct quota_transaction_context *qt);
void quota_transaction_rollback(struct quota_transaction_context **ctx);
/* 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, const char **error_r);
+ QUOTA_ALLOC_RESULT_OK. overruns_r (if not NULL) is set when result is
+ QUOTA_ALLOC_RESULT_OVER_QUOTA. This is a NULL-terminated array of struct
+ quota_overrun which indicates which roots have overruns and how much is used.
+ */
+enum quota_alloc_result
+quota_try_alloc(struct quota_transaction_context *ctx, struct mail *mail,
+ const struct quota_overrun **overruns_r, const char **error_r)
+ ATTR_NULL(3);
/* 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, const char **error_r);
+enum quota_alloc_result
+quota_test_alloc(struct quota_transaction_context *ctx, uoff_t size,
+ const struct quota_overrun **overruns_r, const char **error_r)
+ ATTR_NULL(3);
/* 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,
- const char **error_r);
+static enum quota_alloc_result
+(*trash_next_quota_test_alloc)(struct quota_transaction_context *ctx,
+ uoff_t size,
+ const struct quota_overrun **overruns_r,
+ const char **error_r);
static void
trash_clean_init(struct trash_clean *tclean,
}
static enum quota_alloc_result
-trash_quota_test_alloc(struct quota_transaction_context *ctx,
- uoff_t size, const char **error_r)
+trash_quota_test_alloc(struct quota_transaction_context *ctx, uoff_t size,
+ const struct quota_overrun **overruns_r,
+ 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, error_r);
+ ret = trash_next_quota_test_alloc(ctx, size,
+ overruns_r, error_r);
if (ret != QUOTA_ALLOC_RESULT_OVER_QUOTA) {
if (ret == QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT) {
e_debug(ctx->quota->user->event,