summaryrefslogtreecommitdiffstats
path: root/src/plugins/quota/quota-count.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/plugins/quota/quota-count.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/plugins/quota/quota-count.c')
-rw-r--r--src/plugins/quota/quota-count.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/src/plugins/quota/quota-count.c b/src/plugins/quota/quota-count.c
new file mode 100644
index 0000000..00e25e6
--- /dev/null
+++ b/src/plugins/quota/quota-count.c
@@ -0,0 +1,400 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mailbox-list-iter.h"
+#include "quota-private.h"
+
+struct count_quota_root {
+ struct quota_root root;
+
+ struct timeval cache_timeval;
+ uint64_t cached_bytes, cached_count;
+};
+
+struct quota_mailbox_iter {
+ struct quota_root *root;
+ struct mail_namespace *ns;
+ unsigned int ns_idx;
+ struct mailbox_list_iterate_context *iter;
+ struct mailbox_info info;
+ const char *error;
+};
+
+extern struct quota_backend quota_backend_count;
+
+static int
+quota_count_mailbox(struct quota_root *root, struct mail_namespace *ns,
+ const char *vname, uint64_t *bytes, uint64_t *count,
+ enum quota_get_result *error_result_r,
+ const char **error_r)
+{
+ struct quota_rule *rule;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ enum mail_error error;
+ const char *errstr;
+ int ret;
+
+ rule = quota_root_rule_find(root->set, vname);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ return 0;
+ }
+
+ box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY);
+ if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) {
+ /* quota doesn't exist for this mailbox/storage */
+ ret = 0;
+ } else if (mailbox_get_metadata(box, root->quota->set->vsizes ?
+ MAILBOX_METADATA_VIRTUAL_SIZE :
+ MAILBOX_METADATA_PHYSICAL_SIZE,
+ &metadata) < 0 ||
+ mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error == MAIL_ERROR_TEMP) {
+ *error_r = t_strdup_printf(
+ "Couldn't get size of mailbox %s: %s",
+ vname, errstr);
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ ret = -1;
+ } else if (error == MAIL_ERROR_INUSE) {
+ /* started on background. don't log an error. */
+ *error_r = t_strdup_printf(
+ "Ongoing quota calculation blocked getting size of %s: %s",
+ vname, errstr);
+ *error_result_r = QUOTA_GET_RESULT_BACKGROUND_CALC;
+ ret = -1;
+ } else {
+ /* non-temporary error, e.g. ACLs denied access. */
+ ret = 0;
+ }
+ } else {
+ ret = 0;
+ *bytes += root->quota->set->vsizes ?
+ metadata.virtual_size : metadata.physical_size;
+ *count += status.messages;
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static struct quota_mailbox_iter *
+quota_mailbox_iter_begin(struct quota_root *root)
+{
+ struct quota_mailbox_iter *iter;
+
+ iter = i_new(struct quota_mailbox_iter, 1);
+ iter->root = root;
+ iter->error = "";
+ return iter;
+}
+
+static int
+quota_mailbox_iter_deinit(struct quota_mailbox_iter **_iter,
+ const char **error_r)
+{
+ struct quota_mailbox_iter *iter = *_iter;
+ int ret = *iter->error != '\0' ? -1 : 0;
+
+ *_iter = NULL;
+
+ const char *error2 = "";
+ if (iter->iter != NULL) {
+ if (mailbox_list_iter_deinit(&iter->iter) < 0) {
+ error2 = t_strdup_printf(
+ "Listing namespace '%s' failed: %s",
+ iter->ns->prefix,
+ mailbox_list_get_last_internal_error(iter->ns->list, NULL));
+ ret = -1;
+ }
+ }
+ if (ret < 0) {
+ const char *separator =
+ *iter->error != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strdup_printf("%s%s%s",
+ iter->error, separator, error2);
+ }
+ i_free(iter);
+ return ret;
+}
+
+static const struct mailbox_info *
+quota_mailbox_iter_next(struct quota_mailbox_iter *iter)
+{
+ struct mail_namespace *const *namespaces;
+ const struct mailbox_info *info;
+ unsigned int count;
+
+ if (iter->iter == NULL) {
+ namespaces = array_get(&iter->root->quota->namespaces, &count);
+ do {
+ if (iter->ns_idx >= count)
+ return NULL;
+
+ iter->ns = namespaces[iter->ns_idx++];
+ } while (!quota_root_is_namespace_visible(iter->root, iter->ns));
+ iter->iter = mailbox_list_iter_init(iter->ns->list, "*",
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES);
+ }
+ while ((info = mailbox_list_iter_next(iter->iter)) != NULL) {
+ if ((info->flags & (MAILBOX_NONEXISTENT |
+ MAILBOX_NOSELECT)) == 0)
+ return info;
+ }
+ if (mailbox_list_iter_deinit(&iter->iter) < 0) {
+ iter->error = t_strdup_printf(
+ "Listing namespace '%s' failed: %s",
+ iter->ns->prefix,
+ mailbox_list_get_last_internal_error(iter->ns->list, NULL));
+ }
+ if (iter->ns->prefix_len > 0 &&
+ (iter->ns->prefix_len != 6 ||
+ strncasecmp(iter->ns->prefix, "INBOX", 5) != 0)) {
+ /* if the namespace prefix itself exists, count it also */
+ iter->info.ns = iter->ns;
+ iter->info.vname = t_strndup(iter->ns->prefix,
+ iter->ns->prefix_len-1);
+ return &iter->info;
+ }
+ /* try the next namespace */
+ return quota_mailbox_iter_next(iter);
+}
+
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r,
+ enum quota_get_result *error_result_r, const char **error_r)
+{
+ struct quota_mailbox_iter *iter;
+ const struct mailbox_info *info;
+ const char *error1 = "", *error2 = "";
+ int ret = 1;
+
+ *bytes_r = *count_r = 0;
+ if (root->recounting)
+ return 0;
+ root->recounting = TRUE;
+
+ struct event_reason *reason = event_reason_begin("quota:count");
+
+ iter = quota_mailbox_iter_begin(root);
+ while ((info = quota_mailbox_iter_next(iter)) != NULL) {
+ if (quota_count_mailbox(root, info->ns, info->vname,
+ bytes_r, count_r, error_result_r,
+ &error1) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+ if (quota_mailbox_iter_deinit(&iter, &error2) < 0) {
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ ret = -1;
+ }
+ if (ret < 0) {
+ const char *separator =
+ *error1 != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strconcat(error1, separator, error2, NULL);
+ }
+ event_reason_end(&reason);
+ root->recounting = FALSE;
+ return ret;
+}
+
+static enum quota_get_result
+quota_count_cached(struct count_quota_root *root,
+ uint64_t *bytes_r, uint64_t *count_r,
+ const char **error_r)
+{
+ int ret;
+
+ if (root->cache_timeval.tv_usec == ioloop_timeval.tv_usec &&
+ root->cache_timeval.tv_sec == ioloop_timeval.tv_sec &&
+ ioloop_timeval.tv_sec != 0) {
+ *bytes_r = root->cached_bytes;
+ *count_r = root->cached_count;
+ return QUOTA_GET_RESULT_LIMITED;
+ }
+
+ enum quota_get_result error_res;
+ ret = quota_count(&root->root, bytes_r, count_r, &error_res, error_r);
+ if (ret < 0) {
+ return error_res;
+ } else if (ret > 0) {
+ root->cache_timeval = ioloop_timeval;
+ root->cached_bytes = *bytes_r;
+ root->cached_count = *count_r;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static struct quota_root *count_quota_alloc(void)
+{
+ struct count_quota_root *root;
+
+ root = i_new(struct count_quota_root, 1);
+ return &root->root;
+}
+
+static int count_quota_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ if (!root->quota->set->vsizes) {
+ *error_r = "quota count backend requires quota_vsizes=yes";
+ return -1;
+ }
+ event_set_append_log_prefix(root->backend.event, "quota-count: ");
+
+ root->auto_updating = TRUE;
+ return quota_root_default_init(root, args, error_r);
+}
+
+static void count_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static const char *const *
+count_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL
+ };
+ return resources;
+}
+
+static enum quota_get_result
+count_quota_get_resource(struct quota_root *_root,
+ const char *name, uint64_t *value_r,
+ const char **error_r)
+{
+ struct count_quota_root *root = (struct count_quota_root *)_root;
+ uint64_t bytes, count;
+ enum quota_get_result ret;
+
+ ret = quota_count_cached(root, &bytes, &count, error_r);
+ if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ return ret;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = bytes;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *value_r = count;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int quota_count_recalculate_box(struct mailbox *box,
+ const char **error_r)
+{
+ struct mail_index_transaction *trans;
+ struct mailbox_metadata metadata;
+ struct mailbox_index_vsize vsize_hdr;
+ const char *errstr;
+ enum mail_error error;
+
+ if (mailbox_open(box) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error != MAIL_ERROR_TEMP) {
+ /* non-temporary error, e.g. ACLs denied access. */
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "Couldn't open mailbox %s: %s", box->vname, errstr);
+ return -1;
+ }
+
+ /* reset the vsize header first */
+ trans = mail_index_transaction_begin(box->view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ i_zero(&vsize_hdr);
+ mail_index_update_header_ext(trans, box->vsize_hdr_ext_id,
+ 0, &vsize_hdr, sizeof(vsize_hdr));
+ if (mail_index_transaction_commit(&trans) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't commit mail index transaction for %s: %s",
+ box->vname,
+ mail_index_get_error_message(box->view->index));
+ return -1;
+ }
+ /* getting the vsize now forces its recalculation */
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_VIRTUAL_SIZE,
+ &metadata) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't get mailbox %s vsize: %s", box->vname,
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ /* call sync to write the change to mailbox list index */
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't sync mailbox %s: %s", box->vname,
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+static int quota_count_recalculate(struct quota_root *root,
+ const char **error_r)
+{
+ struct event_reason *reason;
+ struct quota_mailbox_iter *iter;
+ const struct mailbox_info *info;
+ struct mailbox *box;
+ int ret = 0;
+ const char *error1 = "", *error2 = "";
+
+ reason = event_reason_begin("quota:recalculate");
+
+ iter = quota_mailbox_iter_begin(root);
+ while ((info = quota_mailbox_iter_next(iter)) != NULL) {
+ box = mailbox_alloc(info->ns->list, info->vname, 0);
+ if (quota_count_recalculate_box(box, &error1) < 0)
+ ret = -1;
+ mailbox_free(&box);
+ }
+ if (quota_mailbox_iter_deinit(&iter, &error2) < 0)
+ ret = -1;
+ if (ret < 0) {
+ const char *separator =
+ *error1 != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strdup_printf(
+ "quota-count: recalculate failed: %s%s%s",
+ error1, separator, error2);
+ }
+ event_reason_end(&reason);
+ return ret;
+}
+
+static int
+count_quota_update(struct quota_root *root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct count_quota_root *croot = (struct count_quota_root *)root;
+
+ croot->cache_timeval.tv_sec = 0;
+ if (ctx->recalculate == QUOTA_RECALCULATE_FORCED) {
+ if (quota_count_recalculate(root, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+struct quota_backend quota_backend_count = {
+ .name = "count",
+
+ .v = {
+ .alloc = count_quota_alloc,
+ .init = count_quota_init,
+ .deinit = count_quota_deinit,
+ .get_resources = count_quota_root_get_resources,
+ .get_resource = count_quota_get_resource,
+ .update = count_quota_update,
+ }
+};