[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