/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ /* Quota reporting based on simply summing sizes of all files in mailbox together. */ #include "lib.h" #include "array.h" #include "str.h" #include "quota-private.h" #include #include #include struct quota_count_path { const char *path; bool is_file; }; ARRAY_DEFINE_TYPE(quota_count_path, struct quota_count_path); extern struct quota_backend quota_backend_dirsize; static struct quota_root *dirsize_quota_alloc(void) { return i_new(struct quota_root, 1); } static int dirsize_quota_init(struct quota_root *root, const char *args, const char **error_r) { root->auto_updating = TRUE; event_set_append_log_prefix(root->backend.event, "quota-dirsize: "); return quota_root_default_init(root, args, error_r); } static void dirsize_quota_deinit(struct quota_root *_root) { i_free(_root); } static const char *const * dirsize_quota_root_get_resources(struct quota_root *root ATTR_UNUSED) { static const char *resources[] = { QUOTA_NAME_STORAGE_KILOBYTES, NULL }; return resources; } static int get_dir_usage(const char *dir, uint64_t *value, const char **error_r) { DIR *dirp; string_t *path; struct dirent *d; struct stat st; unsigned int path_pos; int ret; dirp = opendir(dir); if (dirp == NULL) { if (errno == ENOENT) return 0; *error_r = t_strdup_printf("opendir(%s) failed: %m", dir); return -1; } path = t_str_new(128); str_append(path, dir); str_append_c(path, '/'); path_pos = str_len(path); ret = 0; while ((d = readdir(dirp)) != NULL) { if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { /* skip . and .. */ continue; } str_truncate(path, path_pos); str_append(path, d->d_name); if (lstat(str_c(path), &st) < 0) { if (errno == ENOENT) continue; *error_r = t_strdup_printf("lstat(%s) failed: %m", dir); ret = -1; break; } else if (S_ISDIR(st.st_mode)) { if (get_dir_usage(str_c(path), value, error_r) < 0) { ret = -1; break; } } else { *value += st.st_size; } } (void)closedir(dirp); return ret; } static int get_usage(const char *path, bool is_file, uint64_t *value_r, const char **error_r) { struct stat st; if (is_file) { if (lstat(path, &st) < 0) { if (errno == ENOENT) return 0; *error_r = t_strdup_printf("lstat(%s) failed: %m", path); return -1; } *value_r += st.st_size; } else { if (get_dir_usage(path, value_r, error_r) < 0) return -1; } return 0; } static void quota_count_path_add(ARRAY_TYPE(quota_count_path) *paths, const char *path, bool is_file) { struct quota_count_path *count_path; unsigned int i, count; size_t path_len; path_len = strlen(path); count_path = array_get_modifiable(paths, &count); for (i = 0; i < count; ) { if (strncmp(count_path[i].path, path, strlen(count_path[i].path)) == 0) { /* this path has already been counted */ return; } if (strncmp(count_path[i].path, path, path_len) == 0 && count_path[i].path[path_len] == '/') { /* the new path contains the existing path. drop it and see if there are more to drop. */ array_delete(paths, i, 1); count_path = array_get_modifiable(paths, &count); } else { i++; } } count_path = array_append_space(paths); count_path->path = t_strdup(path); count_path->is_file = is_file; } static int get_quota_root_usage(struct quota_root *root, uint64_t *value_r, const char **error_r) { struct mail_namespace *const *namespaces; ARRAY_TYPE(quota_count_path) paths; const struct quota_count_path *count_paths; unsigned int i, count; const char *path; bool is_file; t_array_init(&paths, 8); namespaces = array_get(&root->quota->namespaces, &count); for (i = 0; i < count; i++) { if (!quota_root_is_namespace_visible(root, namespaces[i])) continue; is_file = mail_storage_is_mailbox_file(namespaces[i]->storage); if (mailbox_list_get_root_path(namespaces[i]->list, MAILBOX_LIST_PATH_TYPE_DIR, &path)) quota_count_path_add(&paths, path, FALSE); /* INBOX may be in different path. */ if (mailbox_list_get_path(namespaces[i]->list, "INBOX", MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0) quota_count_path_add(&paths, path, is_file); } /* now sum up the found paths */ *value_r = 0; count_paths = array_get(&paths, &count); for (i = 0; i < count; i++) { if (get_usage(count_paths[i].path, count_paths[i].is_file, value_r, error_r) < 0) return -1; } return 0; } static enum quota_get_result dirsize_quota_get_resource(struct quota_root *_root, const char *name, uint64_t *value_r, const char **error_r) { int ret; if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0) { *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; } ret = get_quota_root_usage(_root, value_r, error_r); return ret < 0 ? QUOTA_GET_RESULT_INTERNAL_ERROR : QUOTA_GET_RESULT_LIMITED; } static int dirsize_quota_update(struct quota_root *root ATTR_UNUSED, struct quota_transaction_context *ctx ATTR_UNUSED, const char **error_r ATTR_UNUSED) { return 0; } struct quota_backend quota_backend_dirsize = { .name = "dirsize", .v = { .alloc = dirsize_quota_alloc, .init = dirsize_quota_init, .deinit = dirsize_quota_deinit, .get_resources = dirsize_quota_root_get_resources, .get_resource = dirsize_quota_get_resource, .update = dirsize_quota_update, } };