[Dovecot] Re: Group-based filesystem quota

Scott Alter scott at symphonyhosting.com
Fri Jun 23 07:34:03 EEST 2006


After a few hours of learning C, I have created a patch for the quota-fs 
plugin that should provide complete support for filesystem-based quotas. 
The patch was made off of the latest CVS version, which includes support 
for XFS filesystems.

Currently, the quota-fs plugin only reports the storage quota for the 
user.

With this patch and the plugin enabled (quota = fs), the GETQUOTA 
command now reports both the storage (block) and message (inode) quotas.

Also, this patch examines both user and group disk quotas, and returns the 
quota information nearest to the limit.  For example, if I had a user 
usage of 200 with 400 limit and a group usage of 100 with 200 limit, it 
would return the usage of 100 and limit of 200.  This is because I only 
have 100 left before I reach my group limit, but I have 200 left before I 
reach my user limit.  If, for storage, the user is closer than the group, 
and for messages, the group is closer than the user, the reported quota 
will be the user's for the storage and the group's for the messages.

The only catch is that as far as I know, Solaris does not natively support 
group-based quotas.  Otherwise, this should work on Linux (xfs, ext2, & 
ext3), BSD, and AIX.  I have only tested it on Linux with ext3 disk (it's 
all I have access to), but it should compile everywhere else.

Both the patch to the current CVS version, and the full quota-fs.c are 
attached. Please give it a try and see how you like it. Then, Timo, could 
you add it to the official code?

Thanks,
Scott Alter
-------------- next part --------------
--- quota-fs.c.bak	2006-06-22 21:34:44.000000000 -0400
+++ quota-fs.c	2006-06-22 23:57:40.000000000 -0400
@@ -166,7 +166,11 @@
 static const char *const *
 fs_quota_root_get_resources(struct quota_root *root __attr_unused__)
 {
-	static const char *resources[] = { QUOTA_NAME_STORAGE, NULL };
+	static const char *resources[] = {
+		QUOTA_NAME_STORAGE,
+		QUOTA_NAME_MESSAGES,
+		NULL
+	};
 
 	return resources;
 }
@@ -180,72 +184,99 @@
 #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 };
+	short i;
 
 	*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, (void *)&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;
-		}
-		dqblk.dqb_curblocks = xdqblk.d_bcount << 9;
-		dqblk.dqb_bsoftlimit = xdqblk.d_blk_softlimit >> 1;
-	} else
+			if (quotactl(QCMD(Q_XGETQUOTA, args[i]),
+				     root->mount->device_path,
+				     root->uid, (void *)&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;
+			}
+			dqblk.dqb_curblocks = xdqblk.d_bcount << 9;
+			dqblk.dqb_curinodes = xdqblk.d_icount << 9;
+			dqblk.dqb_bsoftlimit = xdqblk.d_blk_softlimit >> 1;
+			dqblk.dqb_isoftlimit = xdqblk.d_ino_softlimit >> 1;
+		} else
 #endif
-	{
-		/* ext2, ext3 */
-		if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA),
-			     root->mount->device_path,
+		{
+			/* ext2, ext3 */
+			if (quotactl(QCMD(Q_GETQUOTA, args[i]),
+				     root->mount->device_path,
+				     root->uid, (void *)&dqblk) < 0) {
+				i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
+					root->mount->device_path);
+				quota_set_error(_root->setup->quota,
+						"Internal quota error");
+				return -1;
+			}
+		}
+#elif defined(HAVE_QUOTACTL)
+		/* BSD, AIX */
+		if (quotactl(root->mount->device_path, QCMD(Q_GETQUOTA, args[i]),
 			     root->uid, (void *)&dqblk) < 0) {
 			i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
 				root->mount->device_path);
-			quota_set_error(_root->setup->quota,
-					"Internal quota error");
+			quota_set_error(_root->setup->quota, "Internal quota error");
 			return -1;
 		}
-	}
-#elif defined(HAVE_QUOTACTL)
-	/* BSD, AIX */
-	if (quotactl(root->mount->device_path, QCMD(Q_GETQUOTA, USRQUOTA),
-		     root->uid, (void *)&dqblk) < 0) {
-		i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
-			root->mount->device_path);
-		quota_set_error(_root->setup->quota, "Internal quota error");
-		return -1;
-	}
 #else
-	/* Solaris */
-	if (root->mount->fd == -1)
-		return 0;
+		/* 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;
-	}
+		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;
+		}
 #endif
-	*value_r = (uint64_t)dqblk.dqb_curblocks *
-		(uint64_t)root->mount->blk_size / 1024;
-	*limit_r = (uint64_t)dqblk.dqb_bsoftlimit *
-		(uint64_t)root->mount->blk_size / 1024;
+		if (strcmp(name, QUOTA_NAME_STORAGE) == 0) {
+			value_r_t = (uint64_t)dqblk.dqb_curblocks *
+				(uint64_t)root->mount->blk_size / 1024 / 4096;
+			limit_r_t = (uint64_t)dqblk.dqb_bsoftlimit *
+				(uint64_t)root->mount->blk_size / 1024 / 4;
+		} else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
+			value_r_t = (uint64_t)dqblk.dqb_curinodes;
+			limit_r_t = (uint64_t)dqblk.dqb_isoftlimit;
+		} else {
+			return 0;
+		}
+
+		if ((limit_r_c == 0  && limit_r_t >= 0) || 
+		    (limit_r_t - value_r_t) < (limit_r_c - value_r_c)) {
+			limit_r_c = limit_r_t;
+			value_r_c = value_r_t;
+		}
+
+	}
+
+	*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

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

	unsigned int blk_size;

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

struct fs_quota_root {
	struct quota_root root;

	uid_t uid;
	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();

	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->blk_size = point.block_size;
	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__)
{
	static const char *resources[] = {
		QUOTA_NAME_STORAGE,
		QUOTA_NAME_MESSAGES,
		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 };
	short i;

	*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,
				     root->uid, (void *)&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;
			}
			dqblk.dqb_curblocks = xdqblk.d_bcount << 9;
			dqblk.dqb_curinodes = xdqblk.d_icount << 9;
			dqblk.dqb_bsoftlimit = xdqblk.d_blk_softlimit >> 1;
			dqblk.dqb_isoftlimit = xdqblk.d_ino_softlimit >> 1;
		} else
#endif
		{
			/* ext2, ext3 */
			if (quotactl(QCMD(Q_GETQUOTA, args[i]),
				     root->mount->device_path,
				     root->uid, (void *)&dqblk) < 0) {
				i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
					root->mount->device_path);
				quota_set_error(_root->setup->quota,
						"Internal quota error");
				return -1;
			}
		}
#elif defined(HAVE_QUOTACTL)
		/* BSD, AIX */
		if (quotactl(root->mount->device_path, QCMD(Q_GETQUOTA, args[i]),
			     root->uid, (void *)&dqblk) < 0) {
			i_error("quotactl(Q_GETQUOTA, %s) failed: %m",
				root->mount->device_path);
			quota_set_error(_root->setup->quota, "Internal quota error");
			return -1;
		}
#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;
		}
#endif
		if (strcmp(name, QUOTA_NAME_STORAGE) == 0) {
			value_r_t = (uint64_t)dqblk.dqb_curblocks *
				(uint64_t)root->mount->blk_size / 1024 / 4096;
			limit_r_t = (uint64_t)dqblk.dqb_bsoftlimit *
				(uint64_t)root->mount->blk_size / 1024 / 4;
		} else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
			value_r_t = (uint64_t)dqblk.dqb_curinodes;
			limit_r_t = (uint64_t)dqblk.dqb_isoftlimit;
		} else {
			return 0;
		}

		if ((limit_r_c == 0  && limit_r_t >= 0) || 
		    (limit_r_t - value_r_t) < (limit_r_c - value_r_c)) {
			limit_r_c = limit_r_t;
			value_r_c = value_r_t;
		}

	}

	*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