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