changeset 4086:07c3a801c3dc HEAD

Maildir++ quota support.
author Timo Sirainen <timo.sirainen@movial.fi>
date Mon, 06 Mar 2006 12:31:11 +0200
parents cce51b1b5d00
children 61fc347bcceb
files src/plugins/quota/Makefile.am src/plugins/quota/quota-dict.c src/plugins/quota/quota-dirsize.c src/plugins/quota/quota-maildir.c src/plugins/quota/quota-private.h src/plugins/quota/quota.c
diffstat 6 files changed, 717 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/quota/Makefile.am	Mon Mar 06 12:05:29 2006 +0200
+++ b/src/plugins/quota/Makefile.am	Mon Mar 06 12:31:11 2006 +0200
@@ -14,6 +14,7 @@
 	quota-fs.c \
 	quota-dict.c \
 	quota-dirsize.c \
+	quota-maildir.c \
         quota-plugin.c \
 	quota-storage.c
 
--- a/src/plugins/quota/quota-dict.c	Mon Mar 06 12:05:29 2006 +0200
+++ b/src/plugins/quota/quota-dict.c	Mon Mar 06 12:31:11 2006 +0200
@@ -126,16 +126,16 @@
 		t_push();
 		(void)dict_lookup(root->dict, unsafe_data_stack_pool,
 				  DICT_QUOTA_LIMIT_PATH"storage", &value);
-		ctx->storage_limit = value == NULL ? 0 :
+		ctx->bytes_limit = value == NULL ? 0 :
 			strtoull(value, NULL, 10);
 
 		(void)dict_lookup(root->dict, unsafe_data_stack_pool,
 				  DICT_QUOTA_CURRENT_PATH"storage", &value);
-		ctx->storage_current = value == NULL ? 0 :
+		ctx->bytes_current = value == NULL ? 0 :
 			strtoull(value, NULL, 10);
 		t_pop();
 	} else {
-		ctx->storage_limit = (uint64_t)-1;
+		ctx->bytes_limit = (uint64_t)-1;
 	}
 
 	return ctx;
@@ -170,9 +170,9 @@
 dict_quota_try_alloc_bytes(struct quota_root_transaction_context *ctx,
 			   uoff_t size, bool *too_large_r)
 {
-	*too_large_r = size > ctx->storage_limit;
+	*too_large_r = size > ctx->bytes_limit;
 
-	if (ctx->storage_current + ctx->bytes_diff + size > ctx->storage_limit)
+	if (ctx->bytes_current + ctx->bytes_diff + size > ctx->bytes_limit)
 		return 0;
 
 	ctx->bytes_diff += size;
--- a/src/plugins/quota/quota-dirsize.c	Mon Mar 06 12:05:29 2006 +0200
+++ b/src/plugins/quota/quota-dirsize.c	Mon Mar 06 12:31:11 2006 +0200
@@ -198,21 +198,21 @@
 	/* Get dir usage only once at the beginning of transaction.
 	   When copying/appending lots of mails we don't want to re-read the
 	   entire directory structure after each mail. */
-	if (get_quota_root_usage(root, &ctx->storage_current) < 0 ||
-	    ctx->storage_current == (uoff_t)-1) {
-                ctx->storage_current = (uoff_t)-1;
+	if (get_quota_root_usage(root, &ctx->bytes_current) < 0 ||
+	    ctx->bytes_current == (uint64_t)-1) {
+                ctx->bytes_current = (uint64_t)-1;
 		quota_set_error(_root->setup->quota,
 				"Internal quota calculation error");
 	}
 
-	ctx->storage_limit = root->storage_limit * 1024;
+	ctx->bytes_limit = root->storage_limit * 1024;
 	return ctx;
 }
 
 static int
 dirsize_quota_transaction_commit(struct quota_root_transaction_context *ctx)
 {
-	int ret = ctx->storage_current == (uoff_t)-1 ? -1 : 0;
+	int ret = ctx->bytes_current == (uint64_t)-1 ? -1 : 0;
 
 	i_free(ctx);
 	return ret;
@@ -228,12 +228,12 @@
 dirsize_quota_try_alloc_bytes(struct quota_root_transaction_context *ctx,
 			      uoff_t size, bool *too_large_r)
 {
-	if (ctx->storage_current == (uoff_t)-1)
+	if (ctx->bytes_current == (uint64_t)-1)
 		return -1;
 
-	*too_large_r = size > ctx->storage_limit;
+	*too_large_r = size > ctx->bytes_limit;
 
-	if (ctx->storage_current + ctx->bytes_diff + size > ctx->storage_limit)
+	if (ctx->bytes_current + ctx->bytes_diff + size > ctx->bytes_limit)
 		return 0;
 
 	ctx->bytes_diff += size;
@@ -246,7 +246,7 @@
 {
 	uoff_t size;
 
-	if (ctx->storage_current == (uoff_t)-1)
+	if (ctx->bytes_current == (uint64_t)-1)
 		return -1;
 
 	size = mail_get_physical_size(mail);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-maildir.c	Mon Mar 06 12:31:11 2006 +0200
@@ -0,0 +1,693 @@
+/* Copyright (C) 2006 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "nfs-workarounds.h"
+#include "file-dotlock.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "str.h"
+#include "quota-private.h"
+
+#include <stdio.h>
+#include <stdlib.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;
+
+	uint64_t message_bytes_limit;
+	uint64_t message_count_limit;
+
+	uint64_t total_bytes;
+	uint64_t total_count;
+
+	int fd;
+};
+
+struct maildir_list_context {
+	struct mailbox_list_context *ctx;
+	struct mailbox_list *list;
+
+	string_t *path;
+	int state;
+};
+
+extern struct quota_backend quota_backend_maildir;
+
+const struct dotlock_settings dotlock_settings = {
+	MEMBER(temp_prefix) NULL,
+	MEMBER(lock_suffix) NULL,
+
+	MEMBER(timeout) 0,
+	MEMBER(stale_timeout) 30,
+
+	MEMBER(callback) NULL,
+	MEMBER(context) NULL,
+
+	MEMBER(use_excl_lock) FALSE
+};
+
+static int maildir_sum_dir(struct mail_storage *storage, const char *dir,
+			   uint64_t *total_bytes, uint64_t *total_count)
+{
+	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;
+		mail_storage_set_critical(storage, "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) {
+		p = strstr(dp->d_name, ",S=");
+		num = (uoff_t)-1;
+		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)-1;
+			} else {
+				*total_bytes += num;
+				*total_count += 1;
+			}
+		}
+		if (num == (uoff_t)-1) {
+			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) {
+				mail_storage_set_critical(storage,
+					"stat(%s) failed: %m", str_c(path));
+				ret = -1;
+			}
+		}
+	}
+
+	if (closedir(dirp) < 0) {
+		mail_storage_set_critical(storage, "closedir(%s) failed: %m",
+					  dir);
+		return -1;
+	}
+	return ret;
+}
+
+static struct maildir_list_context *
+maildir_list_init(struct mail_storage *storage)
+{
+	struct maildir_list_context *ctx;
+
+	ctx = i_new(struct maildir_list_context, 1);
+	ctx->path = str_new(default_pool, 512);
+	ctx->ctx = mail_storage_mailbox_list_init(storage, "", "*",
+						  MAILBOX_LIST_FAST_FLAGS |
+						  MAILBOX_LIST_INBOX);
+	return ctx;
+}
+
+static const char *
+maildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r)
+{
+	struct stat st;
+	const char *path;
+	bool is_file;
+
+	for (;;) {
+		if (ctx->state == 0) {
+			ctx->list = mail_storage_mailbox_list_next(ctx->ctx);
+			if (ctx->list == NULL)
+				return NULL;
+		}
+
+		t_push();
+		path = mail_storage_get_mailbox_path(ctx->ctx->storage,
+						     ctx->list->name,
+						     &is_file);
+		str_truncate(ctx->path, 0);
+		str_append(ctx->path, path);
+		str_append(ctx->path, ctx->state == 0 ? "/new" : "/cur");
+		t_pop();
+
+		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) {
+			mail_storage_set_critical(ctx->ctx->storage,
+				"stat(%s) failed: %m", str_c(ctx->path));
+			ctx->state = 0;
+		}
+	}
+
+	*mtime_r = st.st_size;
+	return str_c(ctx->path);
+}
+
+static int maildir_list_deinit(struct maildir_list_context *ctx)
+{
+	int ret = mail_storage_mailbox_list_deinit(&ctx->ctx);
+
+	str_free(&ctx->path);
+	i_free(ctx);
+	return ret;
+}
+
+static int
+maildirs_check_have_changed(struct mail_storage *storage, time_t latest_mtime)
+{
+	struct maildir_list_context *ctx;
+	const char *dir;
+	time_t mtime;
+	int ret = 0;
+
+	ctx = maildir_list_init(storage);
+	while ((dir = maildir_list_next(ctx, &mtime)) != NULL) {
+		if (mtime > latest_mtime) {
+			ret = 1;
+			break;
+		}
+	}
+	if (maildir_list_deinit(ctx) < 0)
+		return -1;
+	return ret;
+}
+
+static int maildirsize_write(struct maildir_quota_root *root,
+			     struct mail_storage *storage, const char *path)
+{
+	struct dotlock *dotlock;
+	string_t *str;
+	int fd;
+
+	fd = file_dotlock_open(&dotlock_settings, path,
+			       DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock);
+	if (fd == -1) {
+		if (errno == EAGAIN) {
+			/* someone's just in the middle of updating it */
+			return -1;
+		}
+
+		mail_storage_set_critical(storage,
+			"file_dotlock_open(%s) failed: %m", path);
+		return -1;
+	}
+
+	str = t_str_new(128);
+	if (root->message_bytes_limit != (uint64_t)-1) {
+		str_printfa(str, "S=%llu",
+			    (unsigned long long)root->message_bytes_limit);
+	}
+	if (root->message_count_limit != (uint64_t)-1) {
+		if (str_len(str) > 0)
+			str_append_c(str, ',');
+		str_printfa(str, "C=%llu",
+			    (unsigned long long)root->message_count_limit);
+	}
+	str_printfa(str, "\n%llu %llu\n",
+		    (unsigned long long)root->total_bytes,
+		    (unsigned long long)root->total_count);
+	if (write_full(fd, str_data(str), str_len(str)) < 0) {
+		mail_storage_set_critical(storage,
+			"write_full(%s) failed: %m", path);
+		file_dotlock_delete(&dotlock);
+		return -1;
+	}
+
+	if (file_dotlock_replace(&dotlock, 0) < 0) {
+		mail_storage_set_critical(storage,
+			"file_dotlock_replace(%s) failed: %m", path);
+		return -1;
+	}
+	return 0;
+}
+
+static const char *maildirsize_get_path(struct mail_storage *storage)
+{
+	return t_strconcat(mail_storage_get_mailbox_control_dir(storage, ""),
+			   "/"MAILDIRSIZE_FILENAME, NULL);
+}
+
+static int maildirsize_recalculate(struct maildir_quota_root *root,
+				   struct mail_storage *storage)
+{
+	struct maildir_list_context *ctx;
+	const char *dir, *path;
+	time_t mtime, last_stamp = 0;
+	int ret = 0;
+
+	root->total_bytes = root->total_count = 0;
+
+	ctx = maildir_list_init(storage);
+	while ((dir = maildir_list_next(ctx, &mtime)) != NULL) {
+		if (mtime > last_stamp)
+			last_stamp = mtime;
+
+		t_push();
+		if (maildir_sum_dir(storage, dir,
+				    &root->total_bytes,
+				    &root->total_count) < 0)
+			ret = -1;
+		t_pop();
+	}
+	if (maildir_list_deinit(ctx) < 0)
+		ret = -1;
+
+	if (ret == 0)
+		ret = maildirs_check_have_changed(storage, last_stamp);
+
+	t_push();
+	path = maildirsize_get_path(storage);
+	if (ret == 0) {
+		/* maildir didn't change, we can write the maildirsize file */
+		ret = maildirsize_write(root, storage, path);
+	}
+	if (ret != 0) {
+		/* make sure it gets rebuilt later */
+		if (unlink(path) < 0 && errno != ENOENT && errno != ESTALE) {
+			mail_storage_set_critical(storage,
+				"unlink(%s) failed: %m", path);
+		}
+	}
+	t_pop();
+
+	return ret;
+}
+
+static int maildirsize_parse(struct maildir_quota_root *root,
+			     int fd, const char *const *lines)
+{
+	unsigned long long bytes;
+	long long bytes_diff, total_bytes;
+	int count_diff, total_count;
+	unsigned int line_count = 0;
+	const char *const *limit;
+	char *pos;
+
+	if (*lines == NULL)
+		return -1;
+
+	/* first line contains the limits */
+	root->message_bytes_limit = (uint64_t)-1;
+	root->message_count_limit = (uint64_t)-1;
+	for (limit = t_strsplit(lines[0], ","); *limit != NULL; limit++) {
+		bytes = strtoull(*limit, &pos, 10);
+		if (pos[0] != '\0' && pos[1] == '\0') {
+			switch (pos[0]) {
+			case 'C':
+				root->message_count_limit = bytes;
+				break;
+			case 'S':
+				root->message_bytes_limit = bytes;
+				break;
+			}
+		}
+	}
+
+	/* rest of the lines contains <bytes> <count> diffs */
+	total_bytes = 0; total_count = 0;
+	for (lines++; **lines != '\0'; lines++, line_count++) {
+		if (sscanf(*lines, "%lld %d", &bytes_diff, &count_diff) != 2)
+			return -1;
+
+		total_bytes += bytes_diff;
+		total_count += count_diff;
+	}
+	/* we end always with LF, which shows up as empty last line. there
+	   should be no other empty lines */
+	if (lines[1] != NULL)
+		return -1;
+
+	if (total_bytes < 0 || total_count < 0) {
+		/* corrupted */
+		return -1;
+	}
+
+	if ((uint64_t)total_bytes > root->message_bytes_limit ||
+	    (uint64_t)total_count > root->message_count_limit) {
+		/* 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_read(struct maildir_quota_root *root,
+			    struct mail_storage *storage)
+{
+	const char *path;
+	char buf[5120+1];
+	unsigned int size;
+	int fd, ret;
+
+	t_push();
+	path = maildirsize_get_path(storage);
+	if (root->fd != -1) {
+		if (close(root->fd) < 0) {
+			mail_storage_set_critical(storage,
+				"close(%s) failed: %m", path);
+		}
+		root->fd = -1;
+	}
+
+	fd = nfs_safe_open(path, O_RDWR | O_APPEND);
+	if (fd == -1) {
+		if (errno == ENOENT)
+			ret = 0;
+		else {
+			ret = -1;
+			mail_storage_set_critical(storage,
+				"open(%s) failed: %m", path);
+		}
+		t_pop();
+		return ret;
+	}
+
+	size = 0;
+	while ((ret = read(fd, buf, sizeof(buf)-1)) != 0) {
+		if (ret < 0) {
+			if (errno == ESTALE)
+				break;
+			mail_storage_set_critical(storage, "read(%s) failed: %m",
+						  path);
+		}
+		size += ret;
+	}
+	if (ret < 0 || size == sizeof(buf)-1) {
+		/* error / recalculation needed. */
+		(void)close(fd);
+		t_pop();
+		return ret < 0 ? -1 : 0;
+	}
+
+	/* file is smaller than 5120 bytes, which means we can use it */
+	root->total_bytes = root->total_count = 0;
+
+	/* skip the last line if there's no LF at the end */
+	while (size > 0 && buf[size-1] != '\n') size--;
+	buf[size] = '\0';
+
+	if (maildirsize_parse(root, fd, t_strsplit(buf, "\n")) > 0) {
+		root->fd = fd;
+		ret = 1;
+	} else {
+		/* broken file / need recalculation */
+		(void)close(root->fd);
+		root->fd = -1;
+		ret = 0;
+	}
+	t_pop();
+	return ret;
+}
+
+static int maildirquota_refresh(struct maildir_quota_root *root,
+				struct mail_storage *storage)
+{
+	int ret;
+
+	ret = maildirsize_read(root, storage);
+	if (ret == 0)
+		ret = maildirsize_recalculate(root, storage);
+	return ret < 0 ? -1 : 0;
+}
+
+static int maildirsize_update(struct maildir_quota_root *root,
+			      struct mail_storage *storage,
+			      int count_diff, int64_t bytes_diff)
+{
+	const char *str;
+	int ret = 0;
+
+	t_push();
+
+	/* 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 cases calculations to go
+	   over quota. This is also how Maildir++ spec specifies it should be
+	   done.. */
+	str = t_strdup_printf("%d %lld\n", count_diff, (long long)bytes_diff);
+	if (write_full(root->fd, str, strlen(str)) < 0) {
+		ret = -1;
+		if (errno == ESTALE) {
+			/* deleted/replaced already, ignore */
+		} else {
+			mail_storage_set_critical(storage,
+				"write_full(%s) failed: %m",
+				maildirsize_get_path(storage));
+		}
+	}
+	t_pop();
+	return ret;
+}
+
+static struct quota_root *
+maildir_quota_init(struct quota_setup *setup __attr_unused__,
+		   const char *name __attr_unused__)
+{
+	struct maildir_quota_root *root;
+
+	root = i_new(struct maildir_quota_root, 1);
+	root->root.name = i_strdup(name);
+	root->root.v = quota_backend_maildir.v;
+	root->fd = -1;
+	root->message_bytes_limit = (uint64_t)-1;
+	root->message_count_limit = (uint64_t)-1;
+
+	return &root->root;
+}
+
+static void maildir_quota_deinit(struct quota_root *_root)
+{
+	struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+
+	i_free(root->root.name);
+	i_free(root);
+}
+
+static bool
+maildir_quota_add_storage(struct quota_root *root __attr_unused__,
+			  struct mail_storage *storage __attr_unused__)
+{
+	return TRUE;
+}
+
+static void
+maildir_quota_remove_storage(struct quota_root *root __attr_unused__,
+			     struct mail_storage *storage __attr_unused__)
+{
+}
+
+static const char *const *
+maildir_quota_root_get_resources(struct quota_root *root __attr_unused__)
+{
+	static const char *resources_both[] = {
+		QUOTA_NAME_STORAGE,
+		QUOTA_NAME_MESSAGES,
+		NULL
+	};
+
+	return resources_both;
+}
+
+static struct mail_storage *
+maildir_quota_root_get_storage(struct quota_root *root)
+{
+	/* FIXME: figure out how to support multiple storages */
+	struct mail_storage *const *storages;
+	unsigned int count;
+
+	storages = array_get(&root->storages, &count);
+	i_assert(count > 0);
+
+	return storages[0];
+}
+
+static int
+maildir_quota_get_resource(struct quota_root *_root, const char *name,
+			   uint64_t *value_r, uint64_t *limit_r)
+{
+	struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+
+	if (maildirquota_refresh(root,
+				 maildir_quota_root_get_storage(_root)) < 0)
+		return -1;
+
+	if (strcmp(name, QUOTA_NAME_STORAGE) == 0) {
+		*limit_r = root->message_bytes_limit / 1024;
+		*value_r = root->total_bytes / 1024;
+	} else {
+		*limit_r = root->message_count_limit;
+		*value_r = root->total_count;
+	}
+	return 0;
+}
+
+static int
+maildir_quota_set_resource(struct quota_root *root,
+			   const char *name __attr_unused__,
+			   uint64_t value __attr_unused__)
+{
+	quota_set_error(root->setup->quota, MAIL_STORAGE_ERR_NO_PERMISSION);
+	return -1;
+}
+
+static struct quota_root_transaction_context *
+maildir_quota_transaction_begin(struct quota_root *_root,
+				struct quota_transaction_context *_ctx)
+{
+	struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+	struct quota_root_transaction_context *ctx;
+
+	ctx = i_new(struct quota_root_transaction_context, 1);
+	ctx->root = _root;
+	ctx->ctx = _ctx;
+
+	if (maildirquota_refresh(root,
+				 maildir_quota_root_get_storage(_root)) == 0) {
+		ctx->bytes_limit = root->message_bytes_limit;
+		ctx->count_limit = root->message_count_limit;
+		ctx->bytes_current = root->total_bytes;
+		ctx->count_current = root->total_count;
+	}
+	return ctx;
+}
+
+static int
+maildir_quota_transaction_commit(struct quota_root_transaction_context *ctx)
+{
+	struct maildir_quota_root *root =
+		(struct maildir_quota_root *)ctx->root;
+
+	if (root->fd != -1) {
+		/* if writing fails, we don't care all that much */
+		(void)maildirsize_update(root,
+				maildir_quota_root_get_storage(ctx->root),
+				ctx->count_diff, ctx->bytes_diff);
+	}
+	i_free(ctx);
+	return 0;
+}
+
+static void
+maildir_quota_transaction_rollback(struct quota_root_transaction_context *ctx)
+{
+	i_free(ctx);
+}
+
+static int
+maildir_quota_try_alloc_bytes(struct quota_root_transaction_context *ctx,
+			      uoff_t size, bool *too_large_r)
+{
+	*too_large_r = size > ctx->bytes_limit;
+
+	if (ctx->bytes_current + ctx->bytes_diff + size > ctx->bytes_limit)
+		return 0;
+	if (ctx->count_current + ctx->count_diff + 1 > ctx->count_limit)
+		return 0;
+
+	ctx->count_diff++;
+	ctx->bytes_diff += size;
+	return 1;
+}
+
+static int
+maildir_quota_try_alloc(struct quota_root_transaction_context *ctx,
+			struct mail *mail, bool *too_large_r)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size == (uoff_t)-1)
+		return -1;
+
+	return maildir_quota_try_alloc_bytes(ctx, size, too_large_r);
+}
+
+static void
+maildir_quota_alloc(struct quota_root_transaction_context *ctx,
+		    struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size != (uoff_t)-1)
+		ctx->bytes_diff += size;
+	ctx->count_diff++;
+}
+
+static void
+maildir_quota_free(struct quota_root_transaction_context *ctx,
+		   struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size != (uoff_t)-1)
+		ctx->bytes_diff -= size;
+	ctx->count_diff--;
+}
+
+struct quota_backend quota_backend_maildir = {
+	"maildir",
+
+	{
+		maildir_quota_init,
+		maildir_quota_deinit,
+
+		maildir_quota_add_storage,
+		maildir_quota_remove_storage,
+
+		maildir_quota_root_get_resources,
+
+		maildir_quota_get_resource,
+		maildir_quota_set_resource,
+
+		maildir_quota_transaction_begin,
+		maildir_quota_transaction_commit,
+		maildir_quota_transaction_rollback,
+
+		maildir_quota_try_alloc,
+		maildir_quota_try_alloc_bytes,
+		maildir_quota_alloc,
+		maildir_quota_free
+	}
+};
--- a/src/plugins/quota/quota-private.h	Mon Mar 06 12:05:29 2006 +0200
+++ b/src/plugins/quota/quota-private.h	Mon Mar 06 12:31:11 2006 +0200
@@ -97,8 +97,8 @@
 	int count_diff;
 	int64_t bytes_diff;
 
-	uint64_t storage_limit;
-	uint64_t storage_current;
+	uint64_t bytes_limit, count_limit;
+	uint64_t bytes_current, count_current;
 };
 
 /* Register storage to all user's quota roots. */
--- a/src/plugins/quota/quota.c	Mon Mar 06 12:05:29 2006 +0200
+++ b/src/plugins/quota/quota.c	Mon Mar 06 12:31:11 2006 +0200
@@ -8,16 +8,18 @@
 
 unsigned int quota_module_id = 0;
 
+extern struct quota_backend quota_backend_dict;
 extern struct quota_backend quota_backend_dirsize;
-extern struct quota_backend quota_backend_dict;
 extern struct quota_backend quota_backend_fs;
+extern struct quota_backend quota_backend_maildir;
 
 static struct quota_backend *quota_backends[] = {
-	&quota_backend_dirsize,
+#ifdef HAVE_FS_QUOTA
+	&quota_backend_fs,
+#endif
 	&quota_backend_dict,
-#ifdef HAVE_FS_QUOTA
-	&quota_backend_fs
-#endif
+	&quota_backend_dirsize,
+	&quota_backend_maildir
 };
 #define QUOTA_CLASS_COUNT (sizeof(quota_backends)/sizeof(quota_backends[0]))