quota-maildir.c revision bd7b1a9000b12349e2a99bb43b3ce8b96a18e92b
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen/* Copyright (C) 2006 Timo Sirainen */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenextern struct quota_backend quota_backend_maildir;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildir_sum_dir(const char *dir, uint64_t *total_bytes,
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen const char *p;
b83f52b3ac3736d7f509db7ba80360d89f8a776dTimo Sirainen (dp->d_name[1] == '\0' || dp->d_name[1] == '.'))
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* ,S=nnnn[:,] */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* not in expected format, fallback to stat() */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen } else if (errno != ENOENT && errno != ESTALE) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenmaildir_list_init(struct mail_storage *storage)
e392fcb39a06609af20a9e79017683f194de3ddeTimo Sirainen ctx->iter = mailbox_list_iter_init(mail_storage_get_list(storage), "*",
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenstatic const char *
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenmaildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r)
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen ctx->info = mailbox_list_iter_next(ctx->iter);
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen path = mail_storage_get_mailbox_path(ctx->storage,
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen str_append(ctx->path, ctx->state == 0 ? "/new" : "/cur");
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* ignore if the directory got lost, stale or if it was
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen actually a file and not a directory */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if (errno != ENOENT && errno != ESTALE && errno != ENOTDIR) {
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen i_error("stat(%s) failed: %m", str_c(ctx->path));
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenstatic int maildir_list_deinit(struct maildir_list_context *ctx)
a27e065f1a1f91c7fbdf7c2ea1c387441af0cbb3Timo Sirainen int ret = mailbox_list_iter_deinit(&ctx->iter);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenmaildirs_check_have_changed(struct mail_storage *storage, time_t latest_mtime)
ccb77e2f63626ec46e5745ef4f38baa8e8e504fcTimo Sirainen while (maildir_list_next(ctx, &mtime) != NULL) {
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildirsize_write(struct maildir_quota_root *root, const char *path)
8887bf3757d51d73887dd20b1db3334d867d3817Timo Sirainen dotlock_settings.use_excl_lock = getenv("DOTLOCK_USE_EXCL") != NULL;
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen fd = file_dotlock_open(&dotlock_settings, path,
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* someone's just in the middle of updating it */
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen i_error("file_dotlock_open(%s) failed: %m", path);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if (root->message_bytes_limit != (uint64_t)-1) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen (unsigned long long)root->message_bytes_limit);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if (root->message_count_limit != (uint64_t)-1) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen (unsigned long long)root->message_count_limit);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if (write_full(fd, str_data(str), str_len(str)) < 0) {
b83f52b3ac3736d7f509db7ba80360d89f8a776dTimo Sirainen /* keep the fd open since we might want to update it later */
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen i_error("file_dotlock_replace(%s) failed: %m", path);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic void maildirsize_recalculate_init(struct maildir_quota_root *root)
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildirsize_recalculate_storage(struct maildir_quota_root *root,
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen const char *dir;
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen while ((dir = maildir_list_next(ctx, &mtime)) != NULL) {
bd7b1a9000b12349e2a99bb43b3ce8b96a18e92bTimo Sirainenstatic void maildirsize_rebuild_later(struct maildir_quota_root *root)
bd7b1a9000b12349e2a99bb43b3ce8b96a18e92bTimo Sirainen /* FIXME: can't unlink(), because the limits would be lost. */
bd7b1a9000b12349e2a99bb43b3ce8b96a18e92bTimo Sirainen i_error("unlink(%s) failed: %m", root->maildirsize_path);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildirsize_recalculate_finish(struct maildir_quota_root *root,
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* maildir didn't change, we can write the maildirsize file */
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen ret = maildirsize_write(root, root->maildirsize_path);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildirsize_recalculate(struct maildir_quota_root *root)
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen unsigned int i, count;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen /* count mails from all storages */
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen storages = array_get(&root->root.quota->storages, &count);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen for (i = 0; i < count; i++) {
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen if (maildirsize_recalculate_storage(root, storages[i]) < 0) {
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen /* check if any of the directories have changed */
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen for (i = 0; i < count; i++) {
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen ret = maildirs_check_have_changed(storages[i],
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen return maildirsize_recalculate_finish(root, ret);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenstatic int maildirsize_parse(struct maildir_quota_root *root,
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen unsigned long long bytes;
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen uint64_t message_bytes_limit, message_count_limit;
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen unsigned int line_count = 0;
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen const char *const *limit;
915a93236b3db747fb5a56db30beb95a41ee0221Timo Sirainen /* first line contains the limits. 0 value mean unlimited. */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen for (limit = t_strsplit(lines[0], ","); *limit != NULL; limit++) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen switch (pos[0]) {
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen /* we don't know the limits, use whatever the file says */
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen root->message_bytes_limit = message_bytes_limit;
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen root->message_count_limit = message_count_limit;
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen } else if (root->message_bytes_limit != message_bytes_limit ||
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen root->message_count_limit != message_count_limit) {
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen /* we know the limits and they've changed.
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen the file must be rewritten. */
ba3f68dfb8475299f43125c3e86985a713013c5dTimo Sirainen /* no quota lines. rebuild it. */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* rest of the lines contains <bytes> <count> diffs */
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen for (lines++; *lines != NULL; lines++, line_count++) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if (sscanf(*lines, "%lld %d", &bytes_diff, &count_diff) != 2)
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* we end always with LF, which shows up as empty last line. there
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen should be no other empty lines */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* corrupted */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if ((uint64_t)total_bytes > root->message_bytes_limit ||
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen (uint64_t)total_count > root->message_count_limit) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* we're over quota. don't trust these values if the file
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen contains more than the initial summary line, or if the file
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen is older than 15 minutes. */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen st.st_mtime < ioloop_time - MAILDIRSIZE_STALE_SECS)
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildirsize_read(struct maildir_quota_root *root)
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen unsigned int i, size;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen i_error("close(%s) failed: %m", root->maildirsize_path);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen fd = nfs_safe_open(root->maildirsize_path, O_RDWR | O_APPEND);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen i_error("open(%s) failed: %m", root->maildirsize_path);
4c5272b168b4d71a95a08d48cc5b29cd7b27f193Timo Sirainen /* @UNSAFE */
4c5272b168b4d71a95a08d48cc5b29cd7b27f193Timo Sirainen (ret = read(fd, buf + size, sizeof(buf)-1 - size)) != 0) {
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen i_error("read(%s) failed: %m", root->maildirsize_path);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* error / recalculation needed. */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* file is smaller than 5120 bytes, which means we can use it */
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen /* skip the last line if there's no LF at the end. Remove the last LF
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen so we don't get one empty line in the strsplit. */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen while (size > 0 && buf[size-1] != '\n') size--;
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen /* If there are any NUL bytes, the file is broken. */
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen for (i = 0; i < size; i++) {
0563ad5c7f179554623682f6fd7b98596901b49fTimo Sirainen maildirsize_parse(root, fd, t_strsplit(buf, "\n")) > 0) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* broken file / need recalculation */
c73bb576f140e1c1aae57fd607d02b418afe2c3fTimo Sirainenstatic void maildirquota_init_limits(struct maildir_quota_root *root)
c73bb576f140e1c1aae57fd607d02b418afe2c3fTimo Sirainen if (root->root.default_rule.bytes_limit != 0 ||
c73bb576f140e1c1aae57fd607d02b418afe2c3fTimo Sirainen root->message_bytes_limit = root->root.default_rule.bytes_limit;
c73bb576f140e1c1aae57fd607d02b418afe2c3fTimo Sirainen root->message_count_limit = root->root.default_rule.count_limit;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic int maildirquota_refresh(struct maildir_quota_root *root)
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen if (root->message_bytes_limit == (uint64_t)-1 &&
2d71e0ea3006576961b47d91d564d31771676624Timo Sirainen /* no quota */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenstatic int maildirsize_update(struct maildir_quota_root *root,
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen const char *str;
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* We rely on O_APPEND working in here. That isn't NFS-safe, but it
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen isn't necessarily that bad because the file is recreated once in
a8fcd55e88550ebb905249825bdb1eec7b9667ffTimo Sirainen a while, and sooner if corruption causes calculations to go
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen over quota. This is also how Maildir++ spec specifies it should be
0461b5dfd1ac6b5ae49a2f1ab6ffdc2e5e2ee7c2Timo Sirainen str = t_strdup_printf("%lld %d\n", (long long)bytes_diff, count_diff);
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen if (write_full(root->fd, str, strlen(str)) < 0) {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen /* deleted/replaced already, ignore */
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenstatic struct quota_root *maildir_quota_alloc(void)
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenstatic void maildir_quota_deinit(struct quota_root *_root)
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenmaildir_quota_root_storage_added(struct quota_root *_root,
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen control_dir = mail_storage_get_mailbox_control_dir(storage, "");
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainenmaildir_quota_storage_added(struct quota *quota,
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen unsigned int i, count;
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen roots = array_get_modifiable("a->roots, &count);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen for (i = 0; i < count; i++) {
3fe44a0df5a0bdd80c495f79cbf0e384441d6fccTimo Sirainen if (roots[i]->backend.name == quota_backend_maildir.name)
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen maildir_quota_root_storage_added(roots[i], _storage);
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen /* For newly generated filenames add ,S=size. */
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenstatic const char *const *
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenmaildir_quota_root_get_resources(struct quota_root *root __attr_unused__)
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen static const char *resources_both[] = {
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainenmaildir_quota_get_resource(struct quota_root *_root, const char *name,
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen uint64_t *value_r, uint64_t *limit __attr_unused__)
cb951d3282610a9a0960230865bc5f3e3347b203Timo Sirainen struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
e392fcb39a06609af20a9e79017683f194de3ddeTimo Sirainen if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
3ccfcf0856958cb9208a9fc51c3bdf13c58ad52aTimo Sirainen else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)