diff options
Diffstat (limited to 'src/plugins/quota/quota-maildir.c')
-rw-r--r-- | src/plugins/quota/quota-maildir.c | 953 |
1 files changed, 953 insertions, 0 deletions
diff --git a/src/plugins/quota/quota-maildir.c b/src/plugins/quota/quota-maildir.c new file mode 100644 index 0000000..f4fd3a7 --- /dev/null +++ b/src/plugins/quota/quota-maildir.c @@ -0,0 +1,953 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "nfs-workarounds.h" +#include "safe-mkstemp.h" +#include "mkdir-parents.h" +#include "read-full.h" +#include "write-full.h" +#include "str.h" +#include "maildir-storage.h" +#include "mailbox-list-private.h" +#include "quota-private.h" + +#include <stdio.h> +#include <dirent.h> +#include <sys/stat.h> + +#define MAILDIRSIZE_FILENAME "maildirsize" +#define MAILDIRSIZE_STALE_SECS (60*15) + +struct maildir_quota_root { + struct quota_root root; + + struct mail_namespace *maildirsize_ns; + const char *maildirsize_path; + + uint64_t total_bytes; + uint64_t total_count; + + int fd; + time_t recalc_last_stamp; + off_t last_size; + + bool limits_initialized:1; +}; + +struct maildir_list_context { + struct mailbox_list *list; + struct maildir_quota_root *root; + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + + string_t *path; + int state; +}; + +extern struct quota_backend quota_backend_maildir; + +static struct dotlock_settings dotlock_settings = { + .timeout = 0, + .stale_timeout = 30 +}; + +static int maildir_sum_dir(const char *dir, uint64_t *total_bytes, + uint64_t *total_count, const char **error_r) +{ + DIR *dirp; + struct dirent *dp; + string_t *path; + const char *p; + size_t len; + uoff_t num; + int ret = 0; + + dirp = opendir(dir); + if (dirp == NULL) { + if (errno == ENOENT || errno == ESTALE) + return 0; + *error_r = t_strdup_printf("opendir(%s) failed: %m", dir); + return -1; + } + + path = t_str_new(256); + str_append(path, dir); + str_append_c(path, '/'); + + len = str_len(path); + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && + (dp->d_name[1] == '\0' || dp->d_name[1] == '.')) + continue; + + p = strstr(dp->d_name, ",S="); + num = UOFF_T_MAX; + if (p != NULL) { + /* ,S=nnnn[:,] */ + p += 3; + for (num = 0; *p >= '0' && *p <= '9'; p++) + num = num * 10 + (*p - '0'); + + if (*p != ':' && *p != '\0' && *p != ',') { + /* not in expected format, fallback to stat() */ + num = UOFF_T_MAX; + } else { + *total_bytes += num; + *total_count += 1; + } + } + if (num == UOFF_T_MAX) { + struct stat st; + + str_truncate(path, len); + str_append(path, dp->d_name); + if (stat(str_c(path), &st) == 0) { + *total_bytes += st.st_size; + *total_count += 1; + } else if (errno != ENOENT && errno != ESTALE) { + *error_r = t_strdup_printf( + "stat(%s) failed: %m", str_c(path)); + ret = -1; + } + } + } + + if (closedir(dirp) < 0) { + *error_r = t_strdup_printf("closedir(%s) failed: %m", dir); + return -1; + } + return ret; +} + +static struct maildir_list_context * +maildir_list_init(struct maildir_quota_root *root, struct mailbox_list *list) +{ + struct maildir_list_context *ctx; + + ctx = i_new(struct maildir_list_context, 1); + ctx->root = root; + ctx->path = str_new(default_pool, 512); + ctx->list = list; + ctx->iter = mailbox_list_iter_init(list, "*", + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + return ctx; +} + +static bool maildir_set_next_path(struct maildir_list_context *ctx) +{ + const char *path, *storage_name; + + str_truncate(ctx->path, 0); + + storage_name = mailbox_list_get_storage_name( + ctx->info->ns->list, ctx->info->vname); + if (mailbox_list_get_path(ctx->list, storage_name, + MAILBOX_LIST_PATH_TYPE_MAILBOX, + &path) > 0) { + str_append(ctx->path, path); + str_append(ctx->path, ctx->state == 0 ? + "/new" : "/cur"); + } + + return str_len(ctx->path) > 0; +} + +static const char * +maildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r) +{ + struct quota_rule *rule; + struct stat st; + + for (;;) { + if (ctx->state == 0) { + ctx->info = mailbox_list_iter_next(ctx->iter); + if (ctx->info == NULL) + return NULL; + + rule = quota_root_rule_find(ctx->root->root.set, + ctx->info->vname); + if (rule != NULL && rule->ignore) { + /* mailbox not included in quota */ + continue; + } + } + + if (!maildir_set_next_path(ctx)) { + ctx->state = 0; + continue; + } + + if (++ctx->state == 2) + ctx->state = 0; + + if (stat(str_c(ctx->path), &st) == 0) + break; + /* ignore if the directory got lost, stale or if it was + actually a file and not a directory */ + if (errno != ENOENT && errno != ESTALE && errno != ENOTDIR) { + e_error(ctx->root->root.backend.event, + "stat(%s) failed: %m", str_c(ctx->path)); + ctx->state = 0; + } + } + + *mtime_r = st.st_mtime; + return str_c(ctx->path); +} + +static int maildir_list_deinit(struct maildir_list_context *ctx, + const char **error_r) +{ + int ret = mailbox_list_iter_deinit(&ctx->iter); + if (ret < 0) + *error_r = t_strdup_printf( + "Listing mailboxes failed: %s", + mailbox_list_get_last_internal_error(ctx->list, NULL)); + + str_free(&ctx->path); + i_free(ctx); + return ret; +} + +static int +maildirs_check_have_changed(struct maildir_quota_root *root, + struct mail_namespace *ns, time_t latest_mtime, + const char **error_r) +{ + struct maildir_list_context *ctx; + time_t mtime; + int ret = 0; + + ctx = maildir_list_init(root, ns->list); + while (maildir_list_next(ctx, &mtime) != NULL) { + if (mtime > latest_mtime) { + ret = 1; + break; + } + } + if (maildir_list_deinit(ctx, error_r) < 0) + return -1; + return ret; +} + +static int maildirsize_write(struct maildir_quota_root *root, const char *path) +{ + const struct mail_storage_settings *set = + root->maildirsize_ns->mail_set; + struct quota_root *_root = &root->root; + struct mail_namespace *const *namespaces; + unsigned int i, count; + struct mailbox_permissions perm; + const char *p, *dir; + string_t *str, *temp_path; + int fd; + + i_assert(root->fd == -1); + + /* figure out what permissions we should use for maildirsize. + use the inbox namespace's permissions if possible. */ + perm.file_create_mode = 0600; perm.dir_create_mode = 0700; + perm.file_create_gid = (gid_t)-1; + perm.file_create_gid_origin = "default"; + namespaces = array_get(&root->root.quota->namespaces, &count); + i_assert(count > 0); + for (i = 0; i < count; i++) { + if ((namespaces[i]->flags & NAMESPACE_FLAG_INBOX_USER) == 0) + continue; + + mailbox_list_get_root_permissions(namespaces[i]->list, + &perm); + break; + } + + dotlock_settings.use_excl_lock = set->dotlock_use_excl; + dotlock_settings.nfs_flush = set->mail_nfs_storage; + + temp_path = t_str_new(128); + str_append(temp_path, path); + fd = safe_mkstemp_hostpid_group(temp_path, perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + if (fd == -1 && errno == ENOENT) { + /* the control directory doesn't exist yet? create it */ + p = strrchr(path, '/'); + dir = t_strdup_until(path, p); + if (mkdir_parents_chgrp(dir, perm.dir_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin) < 0 && + errno != EEXIST) { + e_error(root->root.backend.event, + "mkdir_parents(%s) failed: %m", dir); + return -1; + } + fd = safe_mkstemp_hostpid_group(temp_path, + perm.file_create_mode, + perm.file_create_gid, + perm.file_create_gid_origin); + } + if (fd == -1) { + e_error(root->root.backend.event, + "safe_mkstemp(%s) failed: %m", path); + return -1; + } + + str = t_str_new(128); + /* if we have no limits, write 0S instead of an empty line */ + if (_root->bytes_limit != 0 || _root->count_limit == 0) { + str_printfa(str, "%"PRId64"S", _root->bytes_limit); + } + if (_root->count_limit != 0) { + if (str_len(str) > 0) + str_append_c(str, ','); + str_printfa(str, "%"PRIu64"C", _root->count_limit); + } + str_printfa(str, "\n%"PRIu64" %"PRIu64"\n", + root->total_bytes, root->total_count); + if (write_full(fd, str_data(str), str_len(str)) < 0) { + e_error(root->root.backend.event, + "write_full(%s) failed: %m", str_c(temp_path)); + i_close_fd(&fd); + i_unlink(str_c(temp_path)); + return -1; + } + i_close_fd(&fd); + + if (rename(str_c(temp_path), path) < 0) { + e_error(root->root.backend.event, + "rename(%s, %s) failed: %m", str_c(temp_path), path); + i_unlink_if_exists(str_c(temp_path)); + return -1; + } + return 0; +} + +static void maildirsize_recalculate_init(struct maildir_quota_root *root) +{ + root->total_bytes = root->total_count = 0; + root->recalc_last_stamp = 0; +} + +static int maildirsize_recalculate_namespace(struct maildir_quota_root *root, + struct mail_namespace *ns, + const char **error_r) +{ + struct maildir_list_context *ctx; + const char *dir; + time_t mtime; + int ret = 0; + + ctx = maildir_list_init(root, ns->list); + while ((dir = maildir_list_next(ctx, &mtime)) != NULL) { + if (mtime > root->recalc_last_stamp) + root->recalc_last_stamp = mtime; + + if (maildir_sum_dir(dir, &root->total_bytes, + &root->total_count, error_r) < 0) + ret = -1; + } + if (maildir_list_deinit(ctx, error_r) < 0) + ret = -1; + + return ret; +} + +static void maildirsize_rebuild_later(struct maildir_quota_root *root) +{ + if (!root->root.set->force_default_rule) { + /* FIXME: can't unlink(), because the limits would be lost. */ + return; + } + + if (unlink(root->maildirsize_path) < 0 && + errno != ENOENT && errno != ESTALE) + e_error(root->root.backend.event, + "unlink(%s) failed: %m", root->maildirsize_path); +} + +static int maildirsize_recalculate_finish(struct maildir_quota_root *root, + int ret, const char **error_r) +{ + if (ret == 0) { + /* maildir didn't change, we can write the maildirsize file */ + if ((ret = maildirsize_write(root, root->maildirsize_path)) < 0) + *error_r = "failed to write maildirsize"; + } + if (ret != 0) + maildirsize_rebuild_later(root); + + return ret; +} + +static int maildirsize_recalculate(struct maildir_quota_root *root, + const char **error_r) +{ + struct mail_namespace *const *namespaces; + struct event_reason *reason; + unsigned int i, count; + int ret = 0; + + reason = event_reason_begin("quota:recalculate"); + maildirsize_recalculate_init(root); + + /* count mails from all namespaces */ + namespaces = array_get(&root->root.quota->namespaces, &count); + for (i = 0; i < count; i++) { + if (!quota_root_is_namespace_visible(&root->root, namespaces[i])) + continue; + + if (maildirsize_recalculate_namespace(root, namespaces[i], error_r) < 0) { + ret = -1; + break; + } + } + + if (ret == 0) { + /* check if any of the directories have changed */ + for (i = 0; i < count; i++) { + if (!quota_root_is_namespace_visible(&root->root, + namespaces[i])) + continue; + + ret = maildirs_check_have_changed(root, namespaces[i], + root->recalc_last_stamp, + error_r); + if (ret != 0) + break; + } + } + + ret = maildirsize_recalculate_finish(root, ret, error_r); + event_reason_end(&reason); + return ret; +} + +static bool +maildir_parse_limit(const char *str, uint64_t *bytes_r, uint64_t *count_r) +{ + const char *const *limit; + unsigned long long value; + const char *pos; + bool ret = TRUE; + + *bytes_r = 0; + *count_r = 0; + + /* 0 values mean unlimited */ + for (limit = t_strsplit(str, ","); *limit != NULL; limit++) { + if (str_parse_ullong(*limit, &value, &pos) < 0) { + ret = FALSE; + continue; + } + if (pos[0] != '\0' && pos[1] == '\0') { + switch (pos[0]) { + case 'C': + if (value != 0) + *count_r = value; + break; + case 'S': + if (value != 0) + *bytes_r = value; + break; + default: + ret = FALSE; + break; + } + } else { + ret = FALSE; + } + } + return ret; +} + +static int maildirsize_parse(struct maildir_quota_root *root, + int fd, const char *const *lines) +{ + struct quota_root *_root = &root->root; + uint64_t message_bytes_limit, message_count_limit; + long long bytes_diff, total_bytes; + int count_diff, total_count; + unsigned int line_count = 0; + + if (*lines == NULL) + return -1; + + /* first line contains the limits */ + (void)maildir_parse_limit(lines[0], &message_bytes_limit, + &message_count_limit); + + /* truncate too high limits to signed 64bit int range */ + if (message_bytes_limit >= (1ULL << 63)) + message_bytes_limit = (1ULL << 63) - 1; + if (message_count_limit >= (1ULL << 63)) + message_count_limit = (1ULL << 63) - 1; + + if (root->root.bytes_limit == (int64_t)message_bytes_limit && + root->root.count_limit == (int64_t)message_count_limit) { + /* limits haven't changed */ + } else if (root->root.set->force_default_rule) { + /* we know the limits and they've changed. + the file must be rewritten. */ + return 0; + } else { + /* we're using limits from the file. */ + root->root.bytes_limit = message_bytes_limit; + root->root.count_limit = message_count_limit; + quota_root_recalculate_relative_rules(root->root.set, + message_bytes_limit, + message_count_limit); + } + + if (*lines == NULL) { + /* no quota lines. rebuild it. */ + return 0; + } + + /* rest of the lines contains <bytes> <count> diffs */ + total_bytes = 0; total_count = 0; + for (lines++; *lines != NULL; lines++, line_count++) { + if (sscanf(*lines, "%lld %d", &bytes_diff, &count_diff) != 2) + return -1; + + total_bytes += bytes_diff; + total_count += count_diff; + } + + if (total_bytes < 0 || total_count < 0) { + /* corrupted */ + return -1; + } + + if ((total_bytes > _root->bytes_limit && _root->bytes_limit != 0) || + (total_count > _root->count_limit && _root->count_limit != 0)) { + /* we're over quota. don't trust these values if the file + contains more than the initial summary line, or if the file + is older than 15 minutes. */ + struct stat st; + + if (line_count > 1) + return 0; + + if (fstat(fd, &st) < 0 || + st.st_mtime < ioloop_time - MAILDIRSIZE_STALE_SECS) + return 0; + } + root->total_bytes = (uint64_t)total_bytes; + root->total_count = (uint64_t)total_count; + return 1; +} + +static int maildirsize_open(struct maildir_quota_root *root, + const char **error_r) +{ + i_close_fd_path(&root->fd, root->maildirsize_path); + + root->fd = nfs_safe_open(root->maildirsize_path, O_RDWR | O_APPEND); + if (root->fd == -1) { + if (errno == ENOENT) + return 0; + *error_r = t_strdup_printf( + "open(%s) failed: %m", root->maildirsize_path); + return -1; + } + return 1; +} + +static bool maildirsize_has_changed(struct maildir_quota_root *root) +{ + struct stat st1, st2; + + if (dotlock_settings.nfs_flush) { + nfs_flush_file_handle_cache(root->maildirsize_path); + nfs_flush_attr_cache_unlocked(root->maildirsize_path); + } + + if (root->fd == -1) + return TRUE; + + if (stat(root->maildirsize_path, &st1) < 0) + return TRUE; + if (fstat(root->fd, &st2) < 0) + return TRUE; + + return root->last_size != st2.st_size || st1.st_ino != st2.st_ino || + !CMP_DEV_T(st1.st_dev, st2.st_dev); +} + +static int maildirsize_read(struct maildir_quota_root *root, bool *retry, + const char **error_r) +{ + char buf[5120+1]; + unsigned int i, size; + bool retry_estale = *retry; + int ret; + + *retry = FALSE; + + if (!maildirsize_has_changed(root)) + return 1; + + if ((ret = maildirsize_open(root, error_r)) <= 0) + return ret; + + /* @UNSAFE */ + size = 0; + while ((ret = read(root->fd, buf + size, sizeof(buf)-1 - size)) != 0) { + if (ret < 0) { + if (errno == ESTALE && retry_estale) { + *retry = TRUE; + break; + } + *error_r = t_strdup_printf( + "read(%s) failed: %m", root->maildirsize_path); + break; + } + size += ret; + if (size >= sizeof(buf)-1) { + /* we'll need to recalculate the quota */ + break; + } + } + + /* try to use the file even if we ran into some error. if we don't have + forced limits, we'll need to read the header to get them */ + root->total_bytes = root->total_count = 0; + root->last_size = size; + + /* skip the last line if there's no LF at the end. Remove the last LF + so we don't get one empty line in the strsplit. */ + while (size > 0 && buf[size-1] != '\n') size--; + if (size > 0) size--; + buf[size] = '\0'; + + if (ret < 0 && size == 0) { + /* the read failed and there's no usable header, fail. */ + i_close_fd(&root->fd); + return -1; + } + + /* If there are any NUL bytes, the file is broken. */ + for (i = 0; i < size; i++) { + if (buf[i] == '\0') + break; + } + + if (i == size && + maildirsize_parse(root, root->fd, t_strsplit(buf, "\n")) > 0 && + ret == 0) + ret = 1; + else { + /* broken file / need recalculation */ + i_close_fd(&root->fd); + ret = 0; + } + return ret; +} + +static bool maildirquota_limits_init(struct maildir_quota_root *root) +{ + struct mailbox_list *list; + struct mail_storage *storage; + const char *control_dir; + + if (root->limits_initialized) + return root->maildirsize_path != NULL; + root->limits_initialized = TRUE; + + if (root->maildirsize_ns == NULL) { + i_assert(root->maildirsize_path == NULL); + return FALSE; + } + + list = root->maildirsize_ns->list; + if (mailbox_list_get_storage(&list, "", &storage) == 0 && + strcmp(storage->name, MAILDIR_STORAGE_NAME) != 0) { + /* non-maildir namespace, skip */ + if ((storage->class_flags & + MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) { + e_warning(root->root.backend.event, + "Namespace '%s' is not Maildir, " + "skipping for Maildir++ quota", + root->maildirsize_ns->prefix); + } + root->maildirsize_path = NULL; + return FALSE; + } + if (root->maildirsize_path == NULL) { + if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_CONTROL, + &control_dir)) + i_unreached(); + root->maildirsize_path = + p_strconcat(root->root.pool, control_dir, + "/"MAILDIRSIZE_FILENAME, NULL); + } + return TRUE; +} + +static int maildirquota_read_limits(struct maildir_quota_root *root, + const char **error_r) +{ + bool retry = TRUE; + int ret, n = 0; + + if (!maildirquota_limits_init(root)) + return 1; + + do { + if (n == NFS_ESTALE_RETRY_COUNT) + retry = FALSE; + ret = maildirsize_read(root, &retry, error_r); + n++; + } while (ret == -1 && retry); + return ret; +} + +static int +maildirquota_refresh(struct maildir_quota_root *root, bool *recalculated_r, + const char **error_r) +{ + int ret; + + *recalculated_r = FALSE; + + ret = maildirquota_read_limits(root, error_r); + if (ret == 0) { + if (root->root.bytes_limit == 0 && + root->root.count_limit == 0 && + root->root.set->default_rule.bytes_limit == 0 && + root->root.set->default_rule.count_limit == 0) { + /* no quota */ + if (!root->root.set->force_default_rule) + return 0; + /* explicitly specified 0 as quota. keep the quota + updated even if it's not enforced. */ + } + + ret = maildirsize_recalculate(root, error_r); + if (ret == 0) + *recalculated_r = TRUE; + } + return ret < 0 ? -1 : 0; +} + +static int maildirsize_update(struct maildir_quota_root *root, + int count_diff, int64_t bytes_diff) +{ + char str[MAX_INT_STRLEN * 2 + 2]; + int ret = 0; + + if (count_diff == 0 && bytes_diff == 0) + return 0; + + /* We rely on O_APPEND working in here. That isn't NFS-safe, but it + isn't necessarily that bad because the file is recreated once in + a while, and sooner if corruption causes calculations to go + over quota. This is also how Maildir++ spec specifies it should be + done.. */ + if (i_snprintf(str, sizeof(str), "%lld %d\n", + (long long)bytes_diff, count_diff) < 0) + i_unreached(); + if (write_full(root->fd, str, strlen(str)) < 0) { + ret = -1; + if (errno == ESTALE) { + /* deleted/replaced already, ignore */ + } else { + e_error(root->root.backend.event, + "write_full(%s) failed: %m", + root->maildirsize_path); + } + } else { + /* close the file to force a flush with NFS */ + if (close(root->fd) < 0) { + ret = -1; + if (errno != ESTALE) + e_error(root->root.backend.event, + "close(%s) failed: %m", root->maildirsize_path); + } + root->fd = -1; + } + return ret; +} + +static struct quota_root *maildir_quota_alloc(void) +{ + struct maildir_quota_root *root; + + root = i_new(struct maildir_quota_root, 1); + root->fd = -1; + return &root->root; +} + +static int maildir_quota_init(struct quota_root *_root, const char *args, + const char **error_r) +{ + event_set_append_log_prefix(_root->backend.event, "quota-maildir: "); + return quota_root_default_init(_root, args, error_r); +} + +static void maildir_quota_deinit(struct quota_root *_root) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + + i_close_fd(&root->fd); + i_free(root); +} + +static bool +maildir_quota_parse_rule(struct quota_root_settings *root_set ATTR_UNUSED, + struct quota_rule *rule, + const char *str, const char **error_r) +{ + uint64_t bytes, count; + + if (strcmp(str, "NOQUOTA") == 0) { + bytes = 0; + count = 0; + } else if (!maildir_parse_limit(str, &bytes, &count)) { + *error_r = t_strdup_printf( + "quota-maildir: Invalid Maildir++ quota rule \"%s\"", + str); + return FALSE; + } + + rule->bytes_limit = bytes; + rule->count_limit = count; + return TRUE; +} + +static int maildir_quota_init_limits(struct quota_root *_root, + const char **error_r) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + const char *error; + + if (maildirquota_read_limits(root, &error) < 0) { + *error_r = t_strdup_printf( + "quota-maildir: Failed to read limits: %s", error); + return -1; + } + return 0; +} + +static void +maildir_quota_root_namespace_added(struct quota_root *_root, + struct mail_namespace *ns) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + + if (root->maildirsize_ns == NULL) + root->maildirsize_ns = ns; +} + +static void +maildir_quota_namespace_added(struct quota *quota, struct mail_namespace *ns) +{ + struct quota_root **roots; + unsigned int i, count; + + roots = array_get_modifiable("a->roots, &count); + for (i = 0; i < count; i++) { + if (roots[i]->backend.name == quota_backend_maildir.name && + ((roots[i]->ns_prefix == NULL && + ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) || + roots[i]->ns == ns)) + maildir_quota_root_namespace_added(roots[i], ns); + } +} + +static const char *const * +maildir_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) +{ + static const char *resources_both[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + QUOTA_NAME_MESSAGES, + NULL + }; + + return resources_both; +} + +static enum quota_get_result +maildir_quota_get_resource(struct quota_root *_root, const char *name, + uint64_t *value_r, const char **error_r) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + bool recalculated; + const char *error; + + if (maildirquota_refresh(root, &recalculated, &error) < 0) { + *error_r = t_strdup_printf("Failed to get %s: %s", name, error); + return QUOTA_GET_RESULT_INTERNAL_ERROR; + } + + if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) { + *value_r = root->total_bytes; + } else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) { + *value_r = root->total_count; + } else { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + return QUOTA_GET_RESULT_LIMITED; +} + +static int +maildir_quota_update(struct quota_root *_root, + struct quota_transaction_context *ctx, + const char **error_r) +{ + struct maildir_quota_root *root = (struct maildir_quota_root *)_root; + bool recalculated; + const char *error; + + if (!maildirquota_limits_init(root)) { + /* no limits */ + return 0; + } + + /* even though we don't really care about the limits in here ourself, + we do want to make sure the header gets updated if the limits have + changed. also this makes sure the maildirsize file is created if + it doesn't exist. */ + if (maildirquota_refresh(root, &recalculated, &error) < 0) { + *error_r = t_strdup_printf( + "Could not update storage usage data: %s", + error); + return -1; + } + + if (recalculated) { + /* quota was just recalculated and it already contains the changes + we wanted to do. */ + } else if (root->fd == -1) { + if (maildirsize_recalculate(root, &error) < 0) + e_error(root->root.backend.event, "%s", error); + } else if (ctx->recalculate != QUOTA_RECALCULATE_DONT) { + i_close_fd(&root->fd); + if (maildirsize_recalculate(root, &error) < 0) + e_error(root->root.backend.event, "%s", error); + } else if (maildirsize_update(root, ctx->count_used, ctx->bytes_used) < 0) { + i_close_fd(&root->fd); + maildirsize_rebuild_later(root); + } + + return 0; +} + +struct quota_backend quota_backend_maildir = { + .name = "maildir", + + .v = { + .alloc = maildir_quota_alloc, + .init = maildir_quota_init, + .deinit = maildir_quota_deinit, + .parse_rule = maildir_quota_parse_rule, + .init_limits = maildir_quota_init_limits, + .namespace_added = maildir_quota_namespace_added, + .get_resources = maildir_quota_root_get_resources, + .get_resource = maildir_quota_get_resource, + .update = maildir_quota_update, + } +}; |