/* 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, } };