changeset 7294:9e4c26471b19 HEAD

Relative (%) limits in quota rules and warnings work now if the backend specifies or changes the limits (e.g. Maildir++ maildirsize file)
author Timo Sirainen <tss@iki.fi>
date Tue, 26 Feb 2008 16:21:05 +0200
parents f78b83bf16b7
children a39b16b0a530
files src/plugins/quota/quota-dict.c src/plugins/quota/quota-dirsize.c src/plugins/quota/quota-fs.c src/plugins/quota/quota-maildir.c src/plugins/quota/quota-private.h src/plugins/quota/quota.c
diffstat 6 files changed, 144 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/quota/quota-dict.c	Tue Feb 26 15:11:34 2008 +0200
+++ b/src/plugins/quota/quota-dict.c	Tue Feb 26 16:21:05 2008 +0200
@@ -96,7 +96,7 @@
 
 static int
 dict_quota_get_resource(struct quota_root *_root, const char *name,
-			uint64_t *value_r, uint64_t *limit ATTR_UNUSED)
+			uint64_t *value_r)
 {
 	struct dict_quota_root *root = (struct dict_quota_root *)_root;
 	bool want_bytes;
--- a/src/plugins/quota/quota-dirsize.c	Tue Feb 26 15:11:34 2008 +0200
+++ b/src/plugins/quota/quota-dirsize.c	Tue Feb 26 16:21:05 2008 +0200
@@ -180,7 +180,7 @@
 
 static int
 dirsize_quota_get_resource(struct quota_root *_root, const char *name,
-			   uint64_t *value_r, uint64_t *limit ATTR_UNUSED)
+			   uint64_t *value_r)
 {
 	int ret;
 
--- a/src/plugins/quota/quota-fs.c	Tue Feb 26 15:11:34 2008 +0200
+++ b/src/plugins/quota/quota-fs.c	Tue Feb 26 16:21:05 2008 +0200
@@ -549,14 +549,14 @@
 
 static int
 fs_quota_get_resource(struct quota_root *_root, const char *name,
-		      uint64_t *value_r, uint64_t *limit_r)
+		      uint64_t *value_r)
 {
 	struct fs_quota_root *root = (struct fs_quota_root *)_root;
+	uint64_t limit = 0;
 	bool bytes;
 	int ret;
 
 	*value_r = 0;
-	*limit_r = 0;
 
 	if (root->mount == NULL ||
 	    (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0 &&
@@ -566,22 +566,29 @@
 
 #ifdef HAVE_RQUOTA
 	if (strcmp(root->mount->type, "nfs") == 0) {
-		int ret;
-
 		T_BEGIN {
-			ret = do_rquota(root, bytes, value_r, limit_r);
+			ret = do_rquota(root, bytes, value_r, &limit);
 		} T_END;
-		return ret;
-	}
+	} else
 #endif
-
-	ret = fs_quota_get_one_resource(root, FALSE, bytes,
-					value_r, limit_r);
-	if (ret != 0)
+	{
+		ret = fs_quota_get_one_resource(root, FALSE, bytes,
+						value_r, &limit);
+		if (ret == 0) {
+			/* fallback to group quota */
+			ret = fs_quota_get_one_resource(root, TRUE, bytes,
+							value_r, &limit);
+		}
+	}
+	if (ret <= 0)
 		return ret;
 
-	/* fallback to group quota */
-	return fs_quota_get_one_resource(root, TRUE, bytes, value_r, limit_r);
+	/* update limit */
+	if (bytes)
+		_root->default_rule.bytes_limit = limit;
+	else
+		_root->default_rule.count_limit = limit;
+	return 1;
 }
 
 static int 
--- a/src/plugins/quota/quota-maildir.c	Tue Feb 26 15:11:34 2008 +0200
+++ b/src/plugins/quota/quota-maildir.c	Tue Feb 26 16:21:05 2008 +0200
@@ -23,8 +23,6 @@
 	struct quota_root root;
 
 	const char *maildirsize_path;
-	uint64_t message_bytes_limit;
-	uint64_t message_count_limit;
 
 	uint64_t total_bytes;
 	uint64_t total_count;
@@ -208,6 +206,7 @@
 
 static int maildirsize_write(struct maildir_quota_root *root, const char *path)
 {
+	const struct quota_rule *rule = &root->root.default_rule;
 	struct dotlock *dotlock;
 	string_t *str;
 	int fd;
@@ -229,15 +228,15 @@
 	}
 
 	str = t_str_new(128);
-	if (root->message_bytes_limit != (uint64_t)-1) {
+	if (rule->bytes_limit != 0) {
 		str_printfa(str, "%lluS",
-			    (unsigned long long)root->message_bytes_limit);
+			    (unsigned long long)rule->bytes_limit);
 	}
-	if (root->message_count_limit != (uint64_t)-1) {
+	if (rule->count_limit != 0) {
 		if (str_len(str) > 0)
 			str_append_c(str, ',');
 		str_printfa(str, "%lluC",
-			    (unsigned long long)root->message_count_limit);
+			    (unsigned long long)rule->count_limit);
 	}
 	str_printfa(str, "\n%llu %llu\n",
 		    (unsigned long long)root->total_bytes,
@@ -352,8 +351,8 @@
 	char *pos;
 	bool ret = TRUE;
 
-	*bytes_r = (uint64_t)-1;
-	*count_r = (uint64_t)-1;
+	*bytes_r = 0;
+	*count_r = 0;
 
 	/* 0 values mean unlimited */
 	for (limit = t_strsplit(str, ","); *limit != NULL; limit++) {
@@ -382,6 +381,7 @@
 static int maildirsize_parse(struct maildir_quota_root *root,
 			     int fd, const char *const *lines)
 {
+	struct quota_rule *rule = &root->root.default_rule;
 	uint64_t message_bytes_limit, message_count_limit;
 	long long bytes_diff, total_bytes;
 	int count_diff, total_count;
@@ -394,15 +394,24 @@
 	(void)maildir_parse_limit(lines[0], &message_bytes_limit,
 				  &message_count_limit);
 
-	if (!root->master_message_limits) {
-		/* we don't know the limits, use whatever the file says */
-		root->message_bytes_limit = message_bytes_limit;
-		root->message_count_limit = message_count_limit;
-	} else if (root->message_bytes_limit != message_bytes_limit ||
-		   root->message_count_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 (rule->bytes_limit == (int64_t)message_bytes_limit &&
+	    rule->count_limit == (int64_t)message_count_limit) {
+		/* limits haven't changed */
+	} else if (root->master_message_limits) {
 		/* we know the limits and they've changed.
 		   the file must be rewritten. */
 		return 0;
+	} else {
+		/* we're using limits from the file. */
+		rule->bytes_limit = message_bytes_limit;
+		rule->count_limit = message_count_limit;
+		quota_root_recalculate_relative_rules(&root->root);
 	}
 
 	if (*lines == NULL) {
@@ -425,8 +434,8 @@
 		return -1;
 	}
 
-	if ((uint64_t)total_bytes > root->message_bytes_limit ||
-	    (uint64_t)total_count > root->message_count_limit) {
+	if (total_bytes > rule->bytes_limit ||
+	    total_count > rule->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. */
@@ -540,26 +549,23 @@
 	return ret;
 }
 
-static void maildirquota_init_limits(struct maildir_quota_root *root)
+static bool maildirquota_limits_init(struct maildir_quota_root *root)
 {
+	if (root->limits_initialized)
+		return root->maildirsize_path != NULL;
 	root->limits_initialized = TRUE;
 
+	/* these limits must be checked before the maildirsize is read the
+	   first time. if master limits aren't used, the default rule limits
+	   will be zero initially, but they'll be updated after the file is
+	   read. */
 	if (root->root.default_rule.bytes_limit != 0 ||
-	    root->root.default_rule.count_limit != 0) {
+	    root->root.default_rule.count_limit != 0)
 		root->master_message_limits = TRUE;
-		root->message_bytes_limit = root->root.default_rule.bytes_limit;
-		root->message_count_limit = root->root.default_rule.count_limit;
-	}
-}
 
-static bool maildirquota_limits_init(struct maildir_quota_root *root)
-{
-	if (!root->limits_initialized) {
-		maildirquota_init_limits(root);
-		if (root->maildirsize_path == NULL) {
-			i_warning("quota maildir: No maildir storages, "
-				  "ignoring quota.");
-		}
+	if (root->maildirsize_path == NULL) {
+		i_warning("quota maildir: No maildir storages, "
+			  "ignoring quota.");
 	}
 	return root->maildirsize_path != NULL;
 }
@@ -575,8 +581,8 @@
 		ret = maildirsize_read(root);
 	} T_END;
 	if (ret == 0) {
-		if (root->message_bytes_limit == (uint64_t)-1 &&
-		    root->message_count_limit == (uint64_t)-1) {
+		if (root->root.default_rule.bytes_limit == 0 &&
+		    root->root.default_rule.count_limit == 0) {
 			/* no quota */
 			return 0;
 		}
@@ -620,8 +626,6 @@
 
 	root = i_new(struct maildir_quota_root, 1);
 	root->fd = -1;
-	root->message_bytes_limit = (uint64_t)-1;
-	root->message_count_limit = (uint64_t)-1;
 	return &root->root;
 }
 
@@ -703,7 +707,7 @@
 
 static int
 maildir_quota_get_resource(struct quota_root *_root, const char *name,
-			   uint64_t *value_r, uint64_t *limit_r)
+			   uint64_t *value_r)
 {
 	struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
 
@@ -712,14 +716,8 @@
 
 	if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) {
 		*value_r = root->total_bytes;
-		if (!root->master_message_limits &&
-		    root->message_bytes_limit != (uint64_t)-1)
-			*limit_r = root->message_bytes_limit;
 	} else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
 		*value_r = root->total_count;
-		if (!root->master_message_limits &&
-		    root->message_count_limit != (uint64_t)-1)
-			*limit_r = root->message_count_limit;
 	} else
 		return 0;
 	return 1;
--- a/src/plugins/quota/quota-private.h	Tue Feb 26 15:11:34 2008 +0200
+++ b/src/plugins/quota/quota-private.h	Tue Feb 26 16:21:05 2008 +0200
@@ -23,11 +23,12 @@
 	char *mailbox_name;
 
 	int64_t bytes_limit, count_limit;
+	/* relative to default_rule */
+	unsigned int bytes_percent, count_percent;
 };
 
 struct quota_warning_rule {
-	uint64_t bytes_limit;
-	uint64_t count_limit;
+	struct quota_rule rule;
 
 	char *command;
 };
@@ -45,10 +46,8 @@
 			      struct mail_storage *storage);
 
 	const char *const *(*get_resources)(struct quota_root *root);
-	/* the limit is set by default, so it shouldn't normally need to
-	   be changed. */
 	int (*get_resource)(struct quota_root *root, const char *name,
-			    uint64_t *value_r, uint64_t *limit);
+			    uint64_t *value_r);
 
 	int (*update)(struct quota_root *root, 
 		      struct quota_transaction_context *ctx);
@@ -102,6 +101,7 @@
 void quota_remove_user_storage(struct quota *quota, 
 			       struct mail_storage *storage);
 
+void quota_root_recalculate_relative_rules(struct quota_root *root);
 int quota_count(struct quota *quota, uint64_t *bytes_r, uint64_t *count_r);
 
 #endif
--- a/src/plugins/quota/quota.c	Tue Feb 26 15:11:34 2008 +0200
+++ b/src/plugins/quota/quota.c	Tue Feb 26 16:21:05 2008 +0200
@@ -195,7 +195,7 @@
 {
 	int64_t percentage = *limit;
 
-	if (percentage < 0) {
+	if (percentage <= 0 || percentage >= -1U) {
 		*error_r = p_strdup_printf(root->pool,
 			"Invalid rule percentage: %lld", (long long)percentage);
 		return -1;
@@ -207,17 +207,51 @@
 	}
 
 	if (limit == &rule->bytes_limit)
-		*limit = root->default_rule.bytes_limit * percentage / 100;
+		rule->bytes_percent = percentage;
 	else if (limit == &rule->count_limit)
-		*limit = root->default_rule.count_limit * percentage / 100;
+		rule->count_percent = percentage;
 	else
 		i_unreached();
 	return 0;
 }
 
+static void
+quota_rule_recalculate_relative_rules(struct quota_rule *rule,
+				      const struct quota_rule *default_rule)
+{
+	if (rule->bytes_percent > 0) {
+		rule->bytes_limit = default_rule->bytes_limit *
+			rule->bytes_percent / 100;
+	}
+	if (rule->count_percent > 0) {
+		rule->count_limit = default_rule->count_limit *
+			rule->count_percent / 100;
+	}
+}
+
+void quota_root_recalculate_relative_rules(struct quota_root *root)
+{
+	struct quota_rule *rules;
+	struct quota_warning_rule *warning_rules;
+	unsigned int i, count;
+
+	rules = array_get_modifiable(&root->rules, &count);
+	for (i = 0; i < count; i++) {
+		quota_rule_recalculate_relative_rules(&rules[i],
+						      &root->default_rule);
+	}
+
+	warning_rules = array_get_modifiable(&root->warning_rules, &count);
+	for (i = 0; i < count; i++) {
+		quota_rule_recalculate_relative_rules(&warning_rules[i].rule,
+						      &root->default_rule);
+	}
+}
+
 static int
 quota_rule_parse_limits(struct quota_root *root, struct quota_rule *rule,
-			const char *limits, const char **error_r)
+			const char *limits, bool allow_negative,
+			const char **error_r)
 {
 	const char **args;
 	char *p;
@@ -264,7 +298,7 @@
 			multiply = 1024ULL*1024*1024*1024;
 			break;
 		case '%':
-			multiply = 1;
+			multiply = 0;
 			if (quota_rule_parse_percentage(root, rule, limit,
 							error_r) < 0)
 				return -1;
@@ -276,6 +310,16 @@
 		}
 		*limit *= multiply;
 	}
+	if (!allow_negative) {
+		if (rule->bytes_limit < 0) {
+			*error_r = "Bytes limit can't be negative";
+			return -1;
+		}
+		if (rule->count_limit < 0) {
+			*error_r = "Count limit can't be negative";
+			return -1;
+		}
+	}
 	return 0;
 }
 
@@ -309,16 +353,20 @@
 		if (!root->backend.v.parse_rule(root, rule, p, error_r))
 			ret = -1;
 	} else {
-		if (quota_rule_parse_limits(root, rule, p, error_r) < 0)
+		bool allow_negative = rule != &root->default_rule;
+
+		if (quota_rule_parse_limits(root, rule, p,
+					    allow_negative, error_r) < 0)
 			ret = -1;
 	}
 
+	quota_root_recalculate_relative_rules(root);
 	if (root->quota->debug) {
 		i_info("Quota rule: root=%s mailbox=%s "
-		       "bytes=%lld messages=%lld", root->name,
+		       "bytes=%lld (%u%%) messages=%lld (%u%%)", root->name,
 		       rule->mailbox_name != NULL ? rule->mailbox_name : "",
-		       (long long)rule->bytes_limit,
-		       (long long)rule->count_limit);
+		       (long long)rule->bytes_limit, rule->bytes_percent,
+		       (long long)rule->count_limit, rule->count_percent);
 	}
 	return ret;
 }
@@ -426,29 +474,22 @@
 
 	memset(&rule, 0, sizeof(rule));
 	ret = quota_rule_parse_limits(root, &rule, t_strdup_until(rule_def, p),
-				      error_r);
+				      TRUE, error_r);
 	if (ret < 0)
 		return -1;
 
-	if (rule.bytes_limit < 0) {
-		*error_r = "Bytes limit can't be negative";
-		return -1;
-	}
-	if (rule.count_limit < 0) {
-		*error_r = "Count limit can't be negative";
-		return -1;
-	}
-
 	warning = array_append_space(&root->warning_rules);
 	warning->command = i_strdup(p+1);
-	warning->bytes_limit = rule.bytes_limit;
-	warning->count_limit = rule.count_limit;
+	warning->rule = rule;
 
+	quota_root_recalculate_relative_rules(root);
 	if (root->quota->debug) {
-		i_info("Quota warning: bytes=%llu messages=%llu command=%s",
-		       (unsigned long long)warning->bytes_limit,
-		       (unsigned long long)warning->count_limit,
-		       warning->command);
+		i_info("Quota warning: bytes=%llu (%u%%) "
+		       "messages=%llu (%u%%) command=%s",
+		       (unsigned long long)warning->rule.bytes_limit,
+		       warning->rule.bytes_percent,
+		       (unsigned long long)warning->rule.count_limit,
+		       warning->rule.count_percent, warning->command);
 	}
 	return 0;
 }
@@ -537,6 +578,12 @@
 		kilobytes = TRUE;
 	}
 
+	/* Get the value first. This call may also update quota limits if
+	   they're defined externally. */
+	ret = root->backend.v.get_resource(root, name, value_r);
+	if (ret <= 0)
+		return ret;
+
 	(void)quota_root_get_rule_limits(root, mailbox_name,
 					 &bytes_limit, &count_limit);
 	if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
@@ -546,13 +593,11 @@
 	else
 		*limit_r = 0;
 
-	ret = root->backend.v.get_resource(root, name, value_r, limit_r);
-	if (kilobytes && ret > 0) {
+	if (kilobytes) {
 		*value_r /= 1024;
 		*limit_r /= 1024;
 	}
-	return ret <= 0 ? ret :
-		(*limit_r == 0 ? 0 : 1);
+	return *limit_r == 0 ? 0 : 1;
 }
 
 int quota_set_resource(struct quota_root *root ATTR_UNUSED,
@@ -662,10 +707,10 @@
 	bytes_before = bytes_current - ctx->bytes_used;
 	count_before = count_current - ctx->count_used;
 	for (i = 0; i < count; i++) {
-		if ((bytes_before < warnings[i].bytes_limit &&
-		     bytes_current >= warnings[i].bytes_limit) ||
-		    (count_before < warnings[i].count_limit &&
-		     count_current >= warnings[i].count_limit)) {
+		if ((bytes_before < (uint64_t)warnings[i].rule.bytes_limit &&
+		     bytes_current >= (uint64_t)warnings[i].rule.bytes_limit) ||
+		    (count_before < (uint64_t)warnings[i].rule.count_limit &&
+		     count_current >= (uint64_t)warnings[i].rule.count_limit)) {
 			quota_warning_execute(warnings[i].command);
 			break;
 		}