[Dovecot] Group-based filesystem quota
Hello. After using Dovecot for over a year, I have just started experimenting with the filesystem quotas, and I have a suggestion for improvement. On my mail server, I use group-based quotas, and would like to have Dovecot be able to report these quotas.
It should be simple to implement, requiring only changes to the quota-fs.c file of the quota plugin. Simply changing USRQUOTA to GRPQUOTA should do the trick. In terms of the configuration file, "quota = fs" could be expanded to something like: quota = fs:storage=group # use filesystem quota with group storage limit
Adding in support for message (inode) limits is just as easy, replacing dqb_curblocks with dqb_curinodes and dqb_bsoftlimit with dqb_isoftlimit. It could be easy to configure too, like: quota = fs:storage=group:messages=user # use filesystem quota with group storage limit and user messages limit
Although I think I know what would need to be changed, I'm not a C programmer and my code would not be clean nor efficient. Since the messages and storage parameters of the quota setting are defined elsewhere (quota-maildir.c), I don't think it would be too difficult to duplicate this in the quota-fs.c file.
Also, I am having the same problem as others with used space off by a factor of 4096, total limit off by a factor of 4. Stian's patch (http://dovecot.org/list/dovecot/2006-June/014159.html) does the trick, but I'm wondering if everyone is having this problem, or just those running Linux.
Thanks, Scott
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
Scott Alter wrote:
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.
Also, this patch examines both user and group disk quotas, and returns the quota information nearest to the limit. Apparently, there is still a bug - for the group support to work, the user name and the group name must currently be the same. Does anyone know of a way to capture the group that the user writes files as?
Apparently, there is still a bug - for the group support to work, the user name and the group name must currently be the same.
Sorry for all of the emails, but I just fixed this bug. It should really work now. Attached are quota-fs.c and the working patch to the latest CVS version. Please let me know if you have any problems.
-Scott
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Scott, Do you know if this will address issues with v2 quota support on RHEL 3 using ext3 filesystems? alan Scott Alter wrote:
Apparently, there is still a bug - for the group support to work, the user name and the group name must currently be the same.
Sorry for all of the emails, but I just fixed this bug. It should really work now. Attached are quota-fs.c and the working patch to the latest CVS version. Please let me know if you have any problems.
-Scott
------------------------------------------------------------------------
--- quota-fs.c.xfs 2006-06-23 01:36:55.000000000 -0400 +++ quota-fs.c 2006-06-23 01:39:26.000000000 -0400 @@ -41,6 +41,7 @@ struct quota_root root;
uid_t uid; + gid_t gid; struct fs_quota_mountpoint *mount; };
@@ -61,6 +62,7 @@ root->root.name = i_strdup(name); root->root.v = quota_backend_fs.v; root->uid = geteuid(); + root->gid = getegid();
return &root->root; } @@ -166,7 +168,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 +186,100 @@ #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;
*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, + what[i], (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, - root->uid, (void *)&dqblk) < 0) { + { + /* ext2, ext3 */ + if (quotactl(QCMD(Q_GETQUOTA, args[i]), + root->mount->device_path, + what[i], (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]), + what[i], (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; }
------------------------------------------------------------------------
/* 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
#include #include #include #include #ifdef HAVE_LINUX_DQBLK_XFS_H # include #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; 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->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}; int what[] = {root->uid, root->gid}; 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, what[i], (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, what[i], (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]), what[i], (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 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.1 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQFEm4vQE2gsBSKjZHQRAhedAKCs+fugNvqM7/k1pNA9UWCzvMhjWwCg58qD B1+9+tTV/rFGaBu4XE4vi8M= =JzdC -----END PGP SIGNATURE-----
I posted some code on June 12 that can support v2 quota on systems that don't have headers for it. On Fri, 2006-06-23 at 15:36 +0900, Alan Premselaar wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Scott,
Do you know if this will address issues with v2 quota support on RHEL 3 using ext3 filesystems?
alan
Scott Alter wrote:
Apparently, there is still a bug - for the group support to work, the user name and the group name must currently be the same.
Sorry for all of the emails, but I just fixed this bug. It should really work now. Attached are quota-fs.c and the working patch to the latest CVS version. Please let me know if you have any problems.
-Scott
------------------------------------------------------------------------
--- quota-fs.c.xfs 2006-06-23 01:36:55.000000000 -0400 +++ quota-fs.c 2006-06-23 01:39:26.000000000 -0400 @@ -41,6 +41,7 @@ struct quota_root root;
uid_t uid; + gid_t gid; struct fs_quota_mountpoint *mount; };
@@ -61,6 +62,7 @@ root->root.name = i_strdup(name); root->root.v = quota_backend_fs.v; root->uid = geteuid(); + root->gid = getegid();
return &root->root; } @@ -166,7 +168,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 +186,100 @@ #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;
*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, + what[i], (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, - root->uid, (void *)&dqblk) < 0) { + { + /* ext2, ext3 */ + if (quotactl(QCMD(Q_GETQUOTA, args[i]), + root->mount->device_path, + what[i], (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]), + what[i], (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; }
------------------------------------------------------------------------
/* 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
#include #include #include #include #ifdef HAVE_LINUX_DQBLK_XFS_H # include #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; 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->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}; int what[] = {root->uid, root->gid}; 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, what[i], (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, what[i], (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]), what[i], (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 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.1 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQFEm4vQE2gsBSKjZHQRAhedAKCs+fugNvqM7/k1pNA9UWCzvMhjWwCg58qD B1+9+tTV/rFGaBu4XE4vi8M= =JzdC -----END PGP SIGNATURE----- -- Internet Connection High Quality Web Hosting http://www.internetconnection.net/
On Fri, 2006-06-23 at 01:47 -0400, Scott Alter wrote:
Apparently, there is still a bug - for the group support to work, the user name and the group name must currently be the same.
Sorry for all of the emails, but I just fixed this bug. It should really work now. Attached are quota-fs.c and the working patch to the latest CVS version. Please let me know if you have any problems.
What happens if there is no group quota?
Also assuming inode quota == message count quota works only with maildir, so it should do that only if storage->name == "maildir" (quota_root stores multiple storages, but currently the quota code is broken anyway with multiple storages (== namespaces) so checking just the first one is enough).
Could you change that and update the patch against the latest CVS since I've done some changes there? Thanks. :)
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
On Fri, 23 Jun 2006, Scott Alter wrote:
name and the group name must currently be the same. Does anyone know of a way to capture the group that the user writes files as?
Actually, this is not as easy as it sounds, because an user can be member of more than just one group and some filesystems allow to force a particular group on newly created files (e.g. via mount or setgid on the directory). But I suppose that the getegid() style you use in the patch is what most people are using.
Bye,
-- Steffen Kaiser
On Fri, 2006-06-23 at 09:27 +0200, Steffen Kaiser wrote:
On Fri, 23 Jun 2006, Scott Alter wrote:
name and the group name must currently be the same. Does anyone know of a way to capture the group that the user writes files as?
Actually, this is not as easy as it sounds, because an user can be member of more than just one group and some filesystems allow to force a particular group on newly created files (e.g. via mount or setgid on the directory). But I suppose that the getegid() style you use in the patch is what most people are using.
Bye,
Attached is a getfsgid() call that I have used in the past for this very purpose on Linux. It shouldn't be too hard to make work on other platforms...
It checks /etc/mtab (should probably also check /etc/mnttab, but none of my systems use that anymore), checks setgid bit on dir, and falls back on getegid()
-- Internet Connection High Quality Web Hosting http://www.internetconnection.net/
participants (5)
-
Alan Premselaar
-
Geo Carncross
-
Scott Alter
-
Steffen Kaiser
-
Timo Sirainen