[Dovecot] Group-based filesystem quota

Scott Alter scott at symphonyhosting.com
Tue Jul 11 07:42:27 EEST 2006


> Could you change that and update the patch against the latest CVS since
> I've done some changes there? Thanks. :)

I have modified the quota-fs patch to work with the current CVS code 
(unchanged since RC2 I believe).  I also fixed a bug in determining the 
appropriate quota to display (user or group quota depending on free 
space).

> What happens if there is no group quota?

With this patch, if there is a user quota and no group quota, it will only 
display the user quota.  If there is only a group quota and not a user 
quota, it will only show the group quota.  If both quotas are off (yet the 
quota plugin is enabled), it gives the "* BAD Internal quota error" 
message as it currently does.

> Also assuming inode quota == message count quota works only with
> maildir, so it should do that only if storage->name ==
> "maildir"

I attempted to do this, however I was unable to get the storage variable 
in the fs_quota_root_get_resources function.  I left the if statement 
commented out, so if you can just get the storage->name variable in this 
function, it will work as intended.


Scott Alter
-------------- next part --------------
--- quota-fs.c.orig	2006-07-10 22:15:12.000000000 -0400
+++ quota-fs.c	2006-07-11 00:35:00.000000000 -0400
@@ -43,6 +43,7 @@
 	struct quota_root root;
 
 	uid_t uid;
+	gid_t gid;
 	struct fs_quota_mountpoint *mount;
 };
 
@@ -63,6 +64,7 @@
 	root->root.name = i_strdup(name);
 	root->root.v = quota_backend_fs.v;
 	root->uid = geteuid();
+	root->gid = getegid();
 
 	return &root->root;
 }
@@ -167,7 +169,18 @@
 static const char *const *
 fs_quota_root_get_resources(struct quota_root *root __attr_unused__)
 {
-	static const char *resources[] = { QUOTA_NAME_STORAGE, NULL };
+	//if (strcmp(storage->name, "maildir") == 0) {
+		static const char *resources[] = {
+			QUOTA_NAME_STORAGE,
+			QUOTA_NAME_MESSAGES,
+			NULL
+		};
+	/*} else {
+		static const char *resources[] = {
+			QUOTA_NAME_STORAGE,
+			NULL
+		};
+	}*/
 
 	return resources;
 }
@@ -182,76 +195,103 @@
 	struct quotctl ctl;
 #endif
 
+	int value_r_t, limit_r_t, value_r_c=0, limit_r_c=0;
+	char args[] = {USRQUOTA, GRPQUOTA};
+	int what[] = {root->uid, root->gid};
+	short i;
+	int quota_error=0;
+
 	*value_r = 0;
 	*limit_r = 0;
 
-	if (strcasecmp(name, QUOTA_NAME_STORAGE) != 0 || root->mount == NULL)
+	if (root->mount == NULL)
 		return 0;
 
+	for (i = 0; i < 2; i++) {
+
 #if defined (HAVE_QUOTACTL) && defined(HAVE_SYS_QUOTA_H)
-	/* Linux */
+		/* Linux */
 #ifdef HAVE_LINUX_DQBLK_XFS_H
-	if (strcmp(root->mount->type, "xfs") == 0) {
-		/* XFS */
-		struct fs_disk_quota xdqblk;
+		if (strcmp(root->mount->type, "xfs") == 0) {
+			/* XFS */
+			struct fs_disk_quota xdqblk;
 
-		if (quotactl(QCMD(Q_XGETQUOTA, USRQUOTA),
-			     root->mount->device_path,
-			     root->uid, (caddr_t)&xdqblk) < 0) {
-			i_error("quotactl(Q_XGETQUOTA, %s) failed: %m",
-				root->mount->device_path);
-			quota_set_error(_root->setup->quota,
-					"Internal quota error");
-			return -1;
-		}
+			if (quotactl(QCMD(Q_XGETQUOTA, args[i]),
+				     root->mount->device_path,
+				     what[i], (caddr_t)&xdqblk) < 0) {
+				quota_error = quota_error + 1;
+			}
 
-		/* values always returned in 512 byte blocks */
-		*value_r = xdqblk.d_bcount >> 1;
-		*limit_r = xdqblk.d_blk_softlimit >> 1;
-	} else
+			/* values always returned in 512 byte blocks */
+			value_r_t = xdqblk.d_bcount >> 1;
+			limit_r_t = xdqblk.d_blk_softlimit >> 1;
+
+			dqblk.dqb_curinodes = xdqblk.d_icount;
+			dqblk.dqb_isoftlimit = xdqblk.d_ino_softlimit;
+
+		} else
 #endif
-	{
-		/* ext2, ext3 */
-		if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA),
-			     root->mount->device_path,
-			     root->uid, (caddr_t)&dqblk) < 0) {
-			i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
-				root->mount->device_path);
-			quota_set_error(_root->setup->quota,
-					"Internal quota error");
+		{
+			/* ext2, ext3 */
+			if (quotactl(QCMD(Q_GETQUOTA, args[i]),
+				     root->mount->device_path,
+				     what[i], (caddr_t)&dqblk) < 0) {
+				quota_error = quota_error + 1;
+			}
+
+			value_r_t = dqblk.dqb_curblocks / 1024;
+			limit_r_t = dqblk.dqb_bsoftlimit;
+		}
+	#elif defined(HAVE_QUOTACTL)
+		/* BSD, AIX */
+		if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, args[i]),
+			     what[i], (void *)&dqblk) < 0) {
+			quota_error = quota_error + 1;
+		}
+		value_r_t = (uint64_t)dqblk.dqb_curblocks * 1024 / DEV_BSIZE;
+		limit_r_t = (uint64_t)dqblk.dqb_bsoftlimit * 1024 / DEV_BSIZE;
+#else
+		/* Solaris */
+		if (root->mount->fd == -1)
+			return 0;
+
+		ctl.op = Q_GETQUOTA;
+		ctl.uid = root->uid;
+		ctl.addr = (caddr_t)&dqblk;
+		if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) {
+			i_error("ioctl(%s, Q_QUOTACTL) failed: %m", root->mount->path);
+			quota_set_error(_root->setup->quota, "Internal quota error");
 			return -1;
 		}
+		value_r_t = (uint64_t)dqblk.dqb_curblocks * 1024 / DEV_BSIZE;
+		limit_r_t = (uint64_t)dqblk.dqb_bsoftlimit * 1024 / DEV_BSIZE;
+#endif
 
-		*value_r = dqblk.dqb_curblocks / 1024;
-		*limit_r = dqblk.dqb_bsoftlimit;
+		if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
+			value_r_t = (uint64_t)dqblk.dqb_curinodes;
+			limit_r_t = (uint64_t)dqblk.dqb_isoftlimit;
+		}
+
+		if ((value_r_c == 0) ||		// first time in loop
+		    (limit_r_c == 0  && limit_r_t >= 0) ||	// first quota
+		    (limit_r_c > 0 && limit_r_t > 0 && 
+		      (limit_r_t - value_r_t) < (limit_r_c - value_r_c)) || 	// another quota, closer
+		    (limit_r_c == 0 && limit_r_t == 0 && value_r_t > value_r_c)) {	// no quotas, higher usage
+			limit_r_c = limit_r_t;
+			value_r_c = value_r_t;
+		}
 	}
-#elif defined(HAVE_QUOTACTL)
-	/* BSD, AIX */
-	if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, USRQUOTA),
-		     root->uid, (void *)&dqblk) < 0) {
+
+	if (quota_error == 2) {
 		i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
 			root->mount->mount_path);
 		quota_set_error(_root->setup->quota, "Internal quota error");
 		return -1;
 	}
-	*value_r = (uint64_t)dqblk.dqb_curblocks * 1024 / DEV_BSIZE;
-	*limit_r = (uint64_t)dqblk.dqb_bsoftlimit * 1024 / DEV_BSIZE;
-#else
-	/* Solaris */
-	if (root->mount->fd == -1)
-		return 0;
 
-	ctl.op = Q_GETQUOTA;
-	ctl.uid = root->uid;
-	ctl.addr = (caddr_t)&dqblk;
-	if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) {
-		i_error("ioctl(%s, Q_QUOTACTL) failed: %m", root->mount->path);
-		quota_set_error(_root->setup->quota, "Internal quota error");
-		return -1;
-	}
-	*value_r = (uint64_t)dqblk.dqb_curblocks * 1024 / DEV_BSIZE;
-	*limit_r = (uint64_t)dqblk.dqb_bsoftlimit * 1024 / DEV_BSIZE;
-#endif
+	*limit_r = limit_r_c;
+	*value_r = value_r_c;
+
 	return 1;
 }
 
-------------- next part --------------
/* Copyright (C) 2005-2006 Timo Sirainen */

/* Only for reporting filesystem quota */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "mountpoint.h"
#include "quota-private.h"
#include "quota-fs.h"

#ifdef HAVE_FS_QUOTA

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef HAVE_LINUX_DQBLK_XFS_H
#  include <linux/dqblk_xfs.h>
#endif

#ifdef HAVE_STRUCT_DQBLK_CURSPACE
#  define dqb_curblocks dqb_curspace
#endif

#ifndef DEV_BSIZE
#  define DEV_BSIZE 512
#endif

struct fs_quota_mountpoint {
	char *mount_path;
	char *device_path;
	char *type;

#ifdef HAVE_Q_QUOTACTL
	int fd;
	char *path;
#endif
};

struct fs_quota_root {
	struct quota_root root;

	uid_t uid;
	gid_t gid;
	struct fs_quota_mountpoint *mount;
};

struct fs_quota_root_iter {
	struct quota_root_iter iter;

	bool sent;
};

extern struct quota_backend quota_backend_fs;

static struct quota_root *
fs_quota_init(struct quota_setup *setup __attr_unused__, const char *name)
{
	struct fs_quota_root *root;

	root = i_new(struct fs_quota_root, 1);
	root->root.name = i_strdup(name);
	root->root.v = quota_backend_fs.v;
	root->uid = geteuid();
	root->gid = getegid();

	return &root->root;
}

static void fs_quota_mountpoint_free(struct fs_quota_mountpoint *mount)
{
#ifdef HAVE_Q_QUOTACTL
	if (mount->fd != -1) {
		if (close(mount->fd) < 0)
			i_error("close(%s) failed: %m", mount->path);
	}
	i_free(mount->path);
#endif

	i_free(mount->device_path);
	i_free(mount->mount_path);
	i_free(mount->type);
	i_free(mount);
}

static void fs_quota_deinit(struct quota_root *_root)
{
	struct fs_quota_root *root = (struct fs_quota_root *)_root;

	if (root->mount != NULL)
		fs_quota_mountpoint_free(root->mount);
	i_free(root->root.name);
	i_free(root);
}

static struct fs_quota_mountpoint *fs_quota_mountpoint_get(const char *dir)
{
	struct fs_quota_mountpoint *mount;
	struct mountpoint point;
	int ret;

	ret = mountpoint_get(dir, default_pool, &point);
	if (ret <= 0)
		return NULL;

	mount = i_new(struct fs_quota_mountpoint, 1);
	mount->device_path = point.device_path;
	mount->mount_path = point.mount_path;
	mount->type = point.type;
	return mount;
}

static bool fs_quota_add_storage(struct quota_root *_root,
				 struct mail_storage *storage)
{
	struct fs_quota_root *root = (struct fs_quota_root *)_root;
	struct fs_quota_mountpoint *mount;
	const char *dir;
	bool is_file;

	dir = mail_storage_get_mailbox_path(storage, "", &is_file);

	if (getenv("DEBUG") != NULL)
		i_info("fs quota add storage dir = %s", dir);

	mount = fs_quota_mountpoint_get(dir);
	if (root->mount == NULL) {
		if (mount == NULL) {
			/* Not found */
			return TRUE;
		}
		root->mount = mount;
	} else {
		bool match = strcmp(root->mount->mount_path,
				    mount->mount_path) == 0;

		fs_quota_mountpoint_free(mount);
		if (!match) {
			/* different mountpoints, can't use this */
			return FALSE;
		}
		mount = root->mount;
	}

	if (getenv("DEBUG") != NULL) {
		i_info("fs quota block device = %s", mount->device_path);
		i_info("fs quota mount point = %s", mount->mount_path);
	}

#ifdef HAVE_Q_QUOTACTL
	if (mount->path == NULL) {
		mount->path = i_strconcat(mount->mount_path, "/quotas", NULL);
		mount->fd = open(mount->path, O_RDONLY);
		if (mount->fd == -1 && errno != ENOENT)
			i_error("open(%s) failed: %m", mount->path);
	}
#endif
	return TRUE;
}

static void
fs_quota_remove_storage(struct quota_root *root __attr_unused__,
			struct mail_storage *storage __attr_unused__)
{
}

static const char *const *
fs_quota_root_get_resources(struct quota_root *root __attr_unused__)
{
	//if (strcmp(storage->name, "maildir") == 0) {
		static const char *resources[] = {
			QUOTA_NAME_STORAGE,
			QUOTA_NAME_MESSAGES,
			NULL
		};
	/*} else {
		static const char *resources[] = {
			QUOTA_NAME_STORAGE,
			NULL
		};
	}*/

	return resources;
}

static int
fs_quota_get_resource(struct quota_root *_root, const char *name,
		      uint64_t *value_r, uint64_t *limit_r)
{
	struct fs_quota_root *root = (struct fs_quota_root *)_root;
	struct dqblk dqblk;
#ifdef HAVE_Q_QUOTACTL
	struct quotctl ctl;
#endif

	int value_r_t, limit_r_t, value_r_c=0, limit_r_c=0;
	char args[] = {USRQUOTA, GRPQUOTA};
	int what[] = {root->uid, root->gid};
	short i;
	int quota_error=0;

	*value_r = 0;
	*limit_r = 0;

	if (root->mount == NULL)
		return 0;

	for (i = 0; i < 2; i++) {

#if defined (HAVE_QUOTACTL) && defined(HAVE_SYS_QUOTA_H)
		/* Linux */
#ifdef HAVE_LINUX_DQBLK_XFS_H
		if (strcmp(root->mount->type, "xfs") == 0) {
			/* XFS */
			struct fs_disk_quota xdqblk;

			if (quotactl(QCMD(Q_XGETQUOTA, args[i]),
				     root->mount->device_path,
				     what[i], (caddr_t)&xdqblk) < 0) {
				quota_error = quota_error + 1;
			}

			/* values always returned in 512 byte blocks */
			value_r_t = xdqblk.d_bcount >> 1;
			limit_r_t = xdqblk.d_blk_softlimit >> 1;

			dqblk.dqb_curinodes = xdqblk.d_icount;
			dqblk.dqb_isoftlimit = xdqblk.d_ino_softlimit;

		} else
#endif
		{
			/* ext2, ext3 */
			if (quotactl(QCMD(Q_GETQUOTA, args[i]),
				     root->mount->device_path,
				     what[i], (caddr_t)&dqblk) < 0) {
				quota_error = quota_error + 1;
			}

			value_r_t = dqblk.dqb_curblocks / 1024;
			limit_r_t = dqblk.dqb_bsoftlimit;
		}
	#elif defined(HAVE_QUOTACTL)
		/* BSD, AIX */
		if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, args[i]),
			     what[i], (void *)&dqblk) < 0) {
			quota_error = quota_error + 1;
		}
		value_r_t = (uint64_t)dqblk.dqb_curblocks * 1024 / DEV_BSIZE;
		limit_r_t = (uint64_t)dqblk.dqb_bsoftlimit * 1024 / DEV_BSIZE;
#else
		/* Solaris */
		if (root->mount->fd == -1)
			return 0;

		ctl.op = Q_GETQUOTA;
		ctl.uid = root->uid;
		ctl.addr = (caddr_t)&dqblk;
		if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) {
			i_error("ioctl(%s, Q_QUOTACTL) failed: %m", root->mount->path);
			quota_set_error(_root->setup->quota, "Internal quota error");
			return -1;
		}
		value_r_t = (uint64_t)dqblk.dqb_curblocks * 1024 / DEV_BSIZE;
		limit_r_t = (uint64_t)dqblk.dqb_bsoftlimit * 1024 / DEV_BSIZE;
#endif

		if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
			value_r_t = (uint64_t)dqblk.dqb_curinodes;
			limit_r_t = (uint64_t)dqblk.dqb_isoftlimit;
		}

		if ((value_r_c == 0) ||		// first time in loop
		    (limit_r_c == 0  && limit_r_t >= 0) ||	// first quota
		    (limit_r_c > 0 && limit_r_t > 0 && 
		      (limit_r_t - value_r_t) < (limit_r_c - value_r_c)) || 	// another quota, closer
		    (limit_r_c == 0 && limit_r_t == 0 && value_r_t > value_r_c)) {	// no quotas, higher usage
			limit_r_c = limit_r_t;
			value_r_c = value_r_t;
		}
	}

	if (quota_error == 2) {
		i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
			root->mount->mount_path);
		quota_set_error(_root->setup->quota, "Internal quota error");
		return -1;
	}

	*limit_r = limit_r_c;
	*value_r = value_r_c;

	return 1;
}

static int
fs_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 *
fs_quota_transaction_begin(struct quota_root *root,
			   struct quota_transaction_context *ctx)
{
	struct quota_root_transaction_context *root_ctx;

	root_ctx = i_new(struct quota_root_transaction_context, 1);
	root_ctx->root = root;
	root_ctx->ctx = ctx;
	root_ctx->disabled = TRUE;
	return root_ctx;
}

static int
fs_quota_transaction_commit(struct quota_root_transaction_context *ctx)
{
	i_free(ctx);
	return 0;
}

struct quota_backend quota_backend_fs = {
	"fs",

	{
		fs_quota_init,
		fs_quota_deinit,

		fs_quota_add_storage,
		fs_quota_remove_storage,

		fs_quota_root_get_resources,

		fs_quota_get_resource,
		fs_quota_set_resource,

		fs_quota_transaction_begin,
		fs_quota_transaction_commit,
		quota_default_transaction_rollback,

		quota_default_try_alloc,
		quota_default_try_alloc_bytes,
		quota_default_test_alloc_bytes,
		quota_default_alloc,
		quota_default_free
	}
};

#endif


More information about the dovecot mailing list