]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
quota: Create facilities for determining which quota root limit is exceeded
authorStephan Bosch <stephan.bosch@dovecot.fi>
Tue, 13 Nov 2018 20:31:11 +0000 (21:31 +0100)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 26 Feb 2025 10:45:00 +0000 (10:45 +0000)
src/plugins/quota/quota-private.h
src/plugins/quota/quota-status.c
src/plugins/quota/quota-storage.c
src/plugins/quota/quota-util.c
src/plugins/quota/quota.c
src/plugins/quota/quota.h
src/plugins/trash/trash-plugin.c

index 694f7674fd7826275779ae02810a38c05dfe22cf..ec53087a0818f10a6929caef32ca0a812977c660 100644 (file)
@@ -25,7 +25,8 @@ struct quota {
 
        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;
 };
 
@@ -95,6 +96,21 @@ struct quota_root {
        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;
 
@@ -103,6 +119,8 @@ struct quota_transaction_context {
 
        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).
@@ -150,6 +168,10 @@ bool quota_warning_match(const struct quota_root_settings *w,
 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);
index 03e1cb4cdc8098e80e78a8a561924d108146e768..b663b3ac0c214e170a4361e0864eb0419576bc87 100644 (file)
@@ -119,7 +119,7 @@ quota_check(struct mail_user *user, uoff_t mail_size, 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);
index c6bd685caa3d36510f454474ea05c24607c7dfff..ea70a323102d719adc7b62a222bc42a620ddcf07 100644 (file)
@@ -161,7 +161,8 @@ quota_get_status(struct mailbox *box, enum mailbox_status_items items,
        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;
@@ -294,7 +295,7 @@ static int quota_check(struct mail_save_context *ctx, struct mailbox *src_box)
        }
 
        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;
@@ -373,7 +374,8 @@ quota_save_begin(struct mail_save_context *ctx, struct istream *input)
                   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. */
index 0e0738c610723cdcae1747b4d5482d35adb71ce0..64c8e9eb58585b0030e11453bc9df3419a244d2c 100644 (file)
@@ -55,7 +55,8 @@ int quota_get_mail_size(struct quota_transaction_context *ctx,
 }
 
 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
@@ -73,12 +74,16 @@ quota_is_over(uoff_t alloc, int64_t used, uint64_t ceil, uint64_t over)
                        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 {
@@ -87,6 +92,8 @@ quota_is_over(uoff_t alloc, int64_t used, uint64_t ceil, uint64_t over)
                        if (alloc > deleted && (alloc - deleted) > ceil) {
                                /* The new allocation exceeds the quota limit.
                                 */
+                               if (overrun_r != NULL)
+                                       *overrun_r = (alloc - deleted) - ceil;
                                return TRUE;
                        }
                }
@@ -94,15 +101,21 @@ quota_is_over(uoff_t alloc, int64_t used, uint64_t ceil, uint64_t over)
                /* 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;
 }
 
@@ -110,10 +123,26 @@ bool quota_transaction_is_over(struct quota_transaction_context *ctx,
                               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));
+}
index 4602f5c96524d4796de51e61df169763c4e3c6fe..219d6e247387ab84cbcc82f9809d8ae1f37a76c2 100644 (file)
@@ -49,9 +49,10 @@ static const struct quota_backend *quota_internal_backends[] = {
 
 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);
@@ -666,6 +667,10 @@ struct quota_transaction_context *quota_transaction_begin(struct mailbox *box)
        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;
@@ -674,6 +679,10 @@ struct quota_transaction_context *quota_transaction_begin(struct mailbox *box)
 
        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;
 
@@ -763,17 +772,22 @@ int quota_transaction_set_limits(struct quota_transaction_context *ctx,
                        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;
                                }
@@ -795,12 +809,15 @@ int quota_transaction_set_limits(struct quota_transaction_context *ctx,
                        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;
                                }
@@ -973,6 +990,7 @@ int quota_transaction_commit(struct quota_transaction_context **_ctx)
        } T_END;
 
        settings_free(ctx->set);
+       i_free(ctx->roots);
        i_free(ctx);
        return ret;
 }
@@ -1077,24 +1095,33 @@ void quota_transaction_rollback(struct quota_transaction_context **_ctx)
 
        *_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;
@@ -1119,7 +1146,8 @@ enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
                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
@@ -1132,9 +1160,13 @@ enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
        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;
@@ -1159,17 +1191,22 @@ enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
                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;
 
@@ -1182,9 +1219,13 @@ static enum quota_alloc_result quota_default_test_alloc(
        }
 
        /* 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)
@@ -1204,7 +1245,22 @@ static enum quota_alloc_result quota_default_test_alloc(
                                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;
index 7cabca95aaea8ace50178ba84fec9ebe61d4f3e8..a42373427780f3634378a67f0933edd59b48d7bb 100644 (file)
@@ -65,6 +65,15 @@ enum quota_get_result {
        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);
 
@@ -106,12 +115,19 @@ int quota_transaction_commit(struct quota_transaction_context **ctx);
 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,
index 1c4564d24962967a52180d777236e01e646e0eeb..8681238c0fe6aa7890bf54c3f7bbabf017de3db3 100644 (file)
@@ -89,9 +89,11 @@ const char *trash_plugin_version = DOVECOT_ABI_VERSION;
 
 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,
@@ -374,8 +376,9 @@ trash_try_clean_mails(struct quota_transaction_context *ctx,
 }
 
 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;
@@ -383,7 +386,8 @@ trash_quota_test_alloc(struct quota_transaction_context *ctx,
 
        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,