[Dovecot] Quota

Thomas Wouters thomas at xs4all.net
Mon Apr 25 11:58:35 EEST 2005


On Fri, Apr 22, 2005 at 01:26:23AM +0200, Thomas Wouters wrote:

> quota-rquotad works, by the way; I'll clean it up a bit and provide a patch
> against quota.tar.gz.

Patch attached. Feel free to include it any which way. It probably requires
extensive changes to make it portable, though; it's built for FreeBSD (which
we use.) I've also only tested it with Netapp Filer backends (which we
use :). It doesn't try to enforce the quotas (which would involve complicated
guesses as to disk blocksize and such) but it does report them properly, so
you can see your quota and usage in your mail client. It currently treats
inode-quota as message-quota (we use maildir) but I only added that to see
if it worked (we don't actually *have* inode or message quota, ourselves.)
There's also a horrible cast-and-dereference in there to find the directory
associated with a mailbox, but it seems to work for mbox and maildir in my
tests. so I'm thinking it 'should be fine for now' ;)

Compile instructions at the top of quota-rquotad.c, usage similar to
quota-dirsize; the QUOTA variable should be set to a comma-separated list of
quotaroots (nfs mountpoints that use quota.) Perhaps (probably)
quota-rquotad.c should be renamed quota-filesystem.c and use quotactl for
local disks, but I haven't gotten to that part yet. (But quotactl is
easy-peasy compared to rquotad.)

-- 
Thomas Wouters <thomas at xs4all.net>

Hi! I'm a .signature virus! copy me into your .signature file to help me spread!
-------------- next part --------------
diff -crN quota/quota-dirsize.c quota-rquotad/quota-dirsize.c
*** quota/quota-dirsize.c	Tue Mar 15 20:17:50 2005
--- quota-rquotad/quota-dirsize.c	Mon Apr 25 10:19:11 2005
***************
*** 1,7 ****
  /* Copyright (C) 2005 Timo Sirainen */
  
  /* Quota reporting based on simply summing sizes of all files in mailbox
!    together. */
  
  #include "lib.h"
  #include "str.h"
--- 1,9 ----
  /* Copyright (C) 2005 Timo Sirainen */
  
  /* Quota reporting based on simply summing sizes of all files in mailbox
!    together. Compile with:
!    gcc -g -W -Wall -Wformat=2 -shared -DQUOTA_TYPE=dirsize_quota quota-plugin.c quota-storage.c quota.c quota-dirsize.c -o imap_quota.so -DHAVE_CONFIG_H -I../.. -I../lib -I../lib-mail -I../lib-imap -I../lib-storage -I../lib-index -I../imap -lrpcsvc
! */
  
  #include "lib.h"
  #include "str.h"
diff -crN quota/quota-plugin.c quota-rquotad/quota-plugin.c
*** quota/quota-plugin.c	Tue Apr 12 10:26:32 2005
--- quota-rquotad/quota-plugin.c	Mon Apr 25 10:18:37 2005
***************
*** 1,6 ****
  /*
!    gcc -g -W -Wall -Wformat=2 -shared quota-plugin.c quota-storage.c quota.c quota-dirsize.c -o /usr/local/lib/dovecot/imap/imap_quota.so -DHAVE_CONFIG_H -I../.. -I../lib -I../lib-mail -I../lib-imap -I../lib-storage -I../imap
! */
  
  #include "common.h"
  #include "str.h"
--- 1,6 ----
  /*
!    See quota-dirsize.c or other quota-implementation for compile instructions
!  */
  
  #include "common.h"
  #include "str.h"
diff -crN quota/quota-rquotad.c quota-rquotad/quota-rquotad.c
*** quota/quota-rquotad.c	Thu Jan  1 01:00:00 1970
--- quota-rquotad/quota-rquotad.c	Mon Apr 25 10:34:39 2005
***************
*** 0 ****
--- 1,472 ----
+ /* Copyright (C) 2005 Thomas Wouters */
+ 
+ /* Quota reporting based on rquotad, loosely based on FreeBSD's quota(1)
+    program. Compile with:
+ 
+   gcc -g -W -Wall -Wformat=2 -shared -DQUOTA_TYPE=rquotad_quota quota-plugin.c quota-storage.c quota.c quota-rquotad.c -o imap_quota.so -DHAVE_CONFIG_H -I../.. -I../lib -I../lib-mail -I../lib-imap -I../lib-storage -I../lib-index -I../imap -lrpcsvc
+ 
+  */
+ 
+ #include "lib.h"
+ #include "str.h"
+ #include "quota-private.h"
+ #include "index/index-storage.h"
+ 
+ #include <stdlib.h>
+ #include <unistd.h>
+ #include <dirent.h>
+ #include <sys/stat.h>
+ 
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <sys/param.h>
+ #include <sys/mount.h>
+ #include <rpc/rpc.h>
+ #include <rpc/pmap_prot.h>
+ #include <rpcsvc/rquota.h>
+ #include <netdb.h>
+ #include <stdio.h>
+ 
+ struct rquotad_quota {
+ 	struct quota quota;
+ 
+ 	pool_t pool;
+ 	const char *error;
+ 
+ 	size_t nroots;
+ 	struct rquotad_quota_root *roots;
+ 
+ };
+ 
+ enum rquotad_states { RQUOTAD_UNKNOWN, RQUOTAD_ERR, RQUOTAD_OFF, RQUOTAD_ON };
+ 
+ struct rquotad_quota_root {
+ 	struct quota_root root;
+ 
+ 	pool_t pool;
+ 	const char *name;
+ 	enum rquotad_states state;
+ 	const char **resources;
+ 	uint64_t kbyte_usage;
+ 	uint64_t inode_usage;
+ 	uint64_t kbyte_limit;
+ 	uint64_t inode_limit;
+ };
+ 
+ struct rquotad_quota_root_iter {
+ 	struct quota_root_iter iter;
+ 	char boxfs[MNAMELEN];
+ 	size_t offset;
+ };
+ 
+ extern struct quota rquotad_quota;
+ static void rquotad_quota_root_init(struct rquotad_quota_root *root,
+ 				    const char *name,
+ 				    struct rquotad_quota *quota);
+ static void rquotad_quota_root_update(struct rquotad_quota_root *root);
+ 
+ static struct quota *rquotad_quota_init(const char *data)
+ {
+ 	struct rquotad_quota *quota;
+ 	const char *const *qroots;
+ 	size_t qr_idx;
+ 	pool_t pool;
+ 
+ 	pool = pool_alloconly_create("quota", 1024);
+ 	quota = p_new(pool, struct rquotad_quota, 1);
+ 	quota->pool = pool;
+ 	quota->quota = rquotad_quota;
+ 
+ 	qroots = t_strsplit(data, ",");
+ 	for (qr_idx = 0; qroots[qr_idx] != NULL; qr_idx++) {
+ 		/* NOTHING */;
+ 	}
+ 	quota->roots = p_new(pool, struct rquotad_quota_root, qr_idx);
+ 	quota->nroots = qr_idx;
+ 	for (qr_idx = 0; qroots[qr_idx] != NULL; qr_idx++) {
+ 		rquotad_quota_root_init(&(quota->roots[qr_idx]),
+ 					qroots[qr_idx], quota);
+ 	}
+ 	return &quota->quota;
+ }
+ 
+ static void rquotad_quota_root_init(struct rquotad_quota_root *root,
+ 				    const char *name,
+ 				    struct rquotad_quota *quota)
+ {
+ 	root->name = p_strdup(quota->pool, name);
+ 	root->root.quota = (struct quota *)quota;
+ 	root->pool = quota->pool;
+ 	root->state = RQUOTAD_UNKNOWN;
+ }
+ 
+ static void rquotad_quota_deinit(struct quota *_quota)
+ {
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)_quota;
+ 
+ 	pool_unref(quota->pool);
+ }
+ 
+ static struct quota_root_iter *
+ rquotad_quota_root_iter_init(struct quota *quota,
+ 			     struct mailbox *box)
+ {
+ 	struct rquotad_quota_root_iter *iter;
+ 	struct statfs statbuf;
+ 	
+ 	if (statfs(((struct index_storage *)(mailbox_get_storage(box)))->dir,
+ 	           &statbuf) < 0) {
+ 		return NULL;
+ 	}
+ 
+ 	iter = i_new(struct rquotad_quota_root_iter, 1);
+ 	iter->iter.quota = quota;
+ 	strncpy(iter->boxfs, statbuf.f_mntonname, MNAMELEN);
+ 	return &iter->iter;
+ }
+ 
+ static struct quota_root *
+ rquotad_quota_root_iter_next(struct quota_root_iter *_iter)
+ {
+ 	struct rquotad_quota_root_iter *iter =
+ 		(struct rquotad_quota_root_iter *)_iter;
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)_iter->quota;
+ 	struct rquotad_quota_root *root;
+ 
+ 	for (; iter->offset < quota->nroots; iter->offset++) {
+ 		root = &quota->roots[iter->offset];
+ 		if (strcmp(iter->boxfs, root->name) == 0) {
+ 			iter->offset++;
+ 			return (struct quota_root *) root;
+ 		}
+ 	}
+ 	return NULL;
+ }
+ 
+ static void rquotad_quota_root_iter_deinit(struct quota_root_iter *iter)
+ {
+ 	i_free(iter);
+ }
+ 
+ static struct quota_root *
+ rquotad_quota_root_lookup(struct quota *_quota, const char *name)
+ {
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)_quota;
+ 	struct rquotad_quota_root *root;
+ 	size_t i;
+ 	
+ 	for (i = 0; i < quota->nroots; i++) {
+ 		root = &quota->roots[i];
+ 		if (strcmp(root->name, name) == 0) {
+ 			return (struct quota_root *)root;
+ 		}
+ 	}
+ 	return NULL;
+ }
+ 
+ static const char *
+ rquotad_quota_root_get_name(struct quota_root *_root)
+ {
+ 	struct rquotad_quota_root *root = (struct rquotad_quota_root *)_root;
+ 	return root->name;
+ }
+ 
+ static const char *const *
+ rquotad_quota_root_get_resources(struct quota_root *_root)
+ {
+ 	struct rquotad_quota_root *root = (struct rquotad_quota_root *)_root;
+ 	int i = 0;
+ 
+ 	if (root->resources != NULL) {
+ 		return root->resources;
+ 	}
+ 	if (root->state == RQUOTAD_UNKNOWN) {
+ 		rquotad_quota_root_update(root);
+ 	}
+ 	if (root->state == RQUOTAD_ERR) {
+ 		return NULL;
+ 	}
+ 	if (root->state == RQUOTAD_OFF) {
+ 		root->resources = p_new(root->pool, const char *, 1);
+ 		root->resources[0] = NULL;
+ 		return root->resources;
+ 	}
+ 	/* Out of date is alright, we don't really care about up-to-date
+ 	 * numbers, just whether quotas are active */
+ 	root->resources = p_new(root->pool, const char *, 3);
+ 	if (root->kbyte_limit != 0) {
+ 		root->resources[i++] = QUOTA_NAME_STORAGE;
+ 	}
+ 	if (root->inode_limit != 0) {
+ 		root->resources[i++] = QUOTA_NAME_MESSAGES;
+ 	}
+ 	root->resources[i] = NULL;
+ 
+ 	return root->resources;
+ }
+ 
+ static int
+ rquotad_quota_root_create(struct quota *_quota,
+ 			  const char *name __attr_unused__,
+ 			  struct quota_root **root_r __attr_unused__)
+ {
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)_quota;
+ 
+         quota->error = "Permission denied";
+ 	return -1;
+ }
+ 
+ static int
+ rquotad_quota_get_resource(struct quota_root *_root, const char *name,
+ 			   uint64_t *value_r, uint64_t *limit_r)
+ {
+ 	struct rquotad_quota_root *root = (struct rquotad_quota_root *)_root;
+ 	if (root->state == RQUOTAD_UNKNOWN || root->state == RQUOTAD_ON) {
+ 		rquotad_quota_root_update(root);
+ 	}
+ 	if (root->state == RQUOTAD_ERR) {
+ 		return -1;
+ 	}
+ 	*value_r = 0;
+ 	*limit_r = 0;
+ 	if (root->state == RQUOTAD_OFF) {
+ 		return 0;
+ 	}
+ 	if (strcmp(name, QUOTA_NAME_STORAGE) == 0) {
+ 		*value_r = root->kbyte_usage;
+ 		*limit_r = root->kbyte_limit;
+ 		return 1;
+ 	}
+ 	if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
+ 		*value_r = root->inode_usage;
+ 		*limit_r = root->inode_limit;
+ 		return 1;
+ 	}
+ 	return 0;
+ }
+ 
+ static int
+ rquotad_quota_set_resource(struct quota_root *root,
+ 			   const char *name __attr_unused__,
+ 			   uint64_t value __attr_unused__)
+ {
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)root->quota;
+ 
+ 	quota->error = "Permission denied";
+ 	return -1;
+ }
+ 
+ static int
+ rquotad_quota_try_alloc(struct mailbox_transaction_context *ctx __attr_unused__,
+ 			struct quota *_quota __attr_unused__,
+ 			struct mail *mail __attr_unused__)
+ {
+ 	return 1;
+ /*	struct rquotad_quota *quota = (struct rquotad_quota *)_quota;
+ 	struct rquotad_quota_root *root;
+ 	uoff_t size;
+ 
+ 	root = GET_RIGHT_QUOTA_ROOT_SOMEHOW(quota, ctx);	
+ 
+ 	rquotad_quota_root_update(root);
+ 	if (root->state == RQUOTAD_ERR) {
+ 		return -1;
+ 	}
+ 	if (root->state == RQUOTAD_OFF) {
+ 		return 1;
+ 	}
+ 
+ 	size = mail_get_physical_size(mail) / 1024;
+ 
+ 	return (size + root->kbyte_usage) <= root->kbyte_limit;
+ */
+ }
+ 
+ static void
+ rquotad_quota_alloc(struct mailbox_transaction_context *ctx __attr_unused__,
+ 		    struct quota *quota __attr_unused__,
+ 		    struct mail *mail __attr_unused__)
+ {
+ 	/* no-op */
+ }
+ 
+ static void
+ rquotad_quota_free(struct mailbox_transaction_context *ctx __attr_unused__,
+ 		   struct quota *quota __attr_unused__,
+ 		   struct mail *mail __attr_unused__)
+ {
+ 	/* no-op */
+ }
+ 
+ static void
+ rquotad_quota_recalculate(struct quota *quota __attr_unused__)
+ {
+ 	/* no-op */
+ }
+ 
+ const char *rquotad_quota_last_error(struct quota *_quota)
+ {
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)_quota;
+ 
+ 	return quota->error;
+ }
+ 
+ struct quota rquotad_quota = {
+ 	"rquotad",
+ 
+ 	rquotad_quota_init,
+ 	rquotad_quota_deinit,
+ 
+ 	rquotad_quota_root_iter_init,
+ 	rquotad_quota_root_iter_next,
+ 	rquotad_quota_root_iter_deinit,
+ 
+ 	rquotad_quota_root_lookup,
+ 
+ 	rquotad_quota_root_get_name,
+ 	rquotad_quota_root_get_resources,
+ 
+ 	rquotad_quota_root_create,
+ 	rquotad_quota_get_resource,
+ 	rquotad_quota_set_resource,
+ 
+ 	rquotad_quota_try_alloc,
+ 	rquotad_quota_alloc,
+ 	rquotad_quota_free,
+ 	rquotad_quota_recalculate,
+ 
+ 	rquotad_quota_last_error
+ };
+ 
+ static int
+ rquota_get(char *host, getquota_args *qargs, getquota_rslt *qres)
+ {
+ 	struct sockaddr_in rhost;
+ 	struct hostent *hent;
+ 	struct timeval tout;
+ 	enum clnt_stat cstat;
+ 	CLIENT *clnt = NULL;
+ 	int sock = RPC_ANYSOCK;
+  
+ 	hent = gethostbyname(host);
+ 	if (!hent)
+ 		return -1;
+ 	if (hent->h_length > (int)sizeof(rhost.sin_addr))
+ 		return -1;
+ 
+ 	tout.tv_sec = 6;
+ 	tout.tv_usec = 0;
+ 
+ 	*(long *)&rhost.sin_addr = *(long *)hent->h_addr;
+ 	rhost.sin_family = AF_INET;
+ 	rhost.sin_port =  0;
+ 
+ 	clnt = clntudp_create(&rhost, RQUOTAPROG, RQUOTAVERS, tout, &sock);
+ 	if (!clnt)
+ 		return rpc_createerr.cf_stat;
+ 
+ 	clnt->cl_auth = authunix_create_default();
+ 	tout.tv_sec = 25;
+ 	tout.tv_usec = 0;
+ 	cstat = clnt_call(clnt, RQUOTAPROC_GETQUOTA, xdr_getquota_args,
+ 			  qargs, xdr_getquota_rslt, qres, tout);
+ 	return cstat;
+ }
+ 
+ static void rquotad_quota_root_error_alloc(struct rquotad_quota_root *root,
+ 					   const char *errmsg, ...)
+ {
+ 	struct rquotad_quota *quota = (struct rquotad_quota *)root->root.quota;
+ 	va_list args;
+ 	va_start(args, errmsg);
+ 	quota->error = p_strdup_vprintf(root->pool, errmsg, args);
+ 	va_end(args);
+ }
+ 
+ static void rquotad_quota_root_update(struct rquotad_quota_root *root)
+ {
+ 	struct statfs statfsbuf;
+ 	struct getquota_args qargs;
+ 	struct getquota_rslt qres;
+ 	char *remotehost, *remotepath;
+ 	char *c;
+ 
+ 	if (root->state == RQUOTAD_OFF) {
+ 		return;
+ 	}
+ 
+ 	if (statfs(root->name, &statfsbuf) < 0) {
+ 		rquotad_quota_root_error_alloc(root, strerror(errno));
+ 		root->state = RQUOTAD_ERR;
+ 		return;
+ 	}
+ 
+ 	if (statfsbuf.f_flags & MNT_LOCAL) {
+ 		root->state = RQUOTAD_OFF;
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * must be some form of "hostname:/path"
+ 	 */
+ 	c = strchr(statfsbuf.f_mntfromname, ':');
+ 	if (c == NULL) {
+ 		rquotad_quota_root_error_alloc(root,
+ 					       "cannot find hostname for %s",
+ 					       statfsbuf.f_mntfromname);
+ 		root->state = RQUOTAD_ERR;
+ 		return;
+ 	}
+  
+ 	*c = '\0';
+ 	remotehost = strdup(statfsbuf.f_mntfromname);
+ 	remotepath = strdup(c + 1);
+ 	*c = ':';
+ 
+ 	if (remotepath[0] != '/') {
+ 		rquotad_quota_root_error_alloc(root, "weird filesystem %s",
+ 					       statfsbuf.f_mntfromname);
+ 		root->state = RQUOTAD_ERR;
+ 		return;
+ 	}
+ 
+ 	qargs.gqa_pathp = remotepath;
+ 	qargs.gqa_uid = getuid();
+ 
+ 	if (rquota_get(remotehost, &qargs, &qres) != 0) {
+ 		return;
+ 	}
+ 
+ 	switch (qres.status) {
+ 	case Q_OK:
+ 		root->kbyte_limit =
+ 		    (qres.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
+ 		     qres.getquota_rslt_u.gqr_rquota.rq_bsize) / 1024;
+ 		root->kbyte_usage =
+ 		    (qres.getquota_rslt_u.gqr_rquota.rq_curblocks *
+ 		     qres.getquota_rslt_u.gqr_rquota.rq_bsize) / 1024;
+ 			/* inodes */
+ 		root->inode_limit =
+ 			qres.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
+ 		root->inode_usage =
+ 			qres.getquota_rslt_u.gqr_rquota.rq_curfiles;
+ 		root->state = RQUOTAD_ON;
+ 		return;
+ 	case Q_NOQUOTA:
+ 		root->state = RQUOTAD_OFF;
+ 		return;
+ 	case Q_EPERM:
+ 		rquotad_quota_root_error_alloc(root,
+ 				"quota permission error, host/path %s/%s",
+ 					       remotehost, remotepath);
+ 		root->state = RQUOTAD_ERR;
+ 		return;
+ 	}
+ 	rquotad_quota_root_error_alloc(root,
+ 				       "bad rpc result %d, host/path %s/%s",
+ 				       qres.status, remotehost, remotepath);
+ 	root->state = RQUOTAD_ERR;
+ }
+ 
+ 
+ 
diff -crN quota/quota.c quota-rquotad/quota.c
*** quota/quota.c	Tue Jan 11 23:09:19 2005
--- quota-rquotad/quota.c	Mon Apr 25 10:15:04 2005
***************
*** 3,13 ****
  #include "lib.h"
  #include "quota-private.h"
  
! extern struct quota dirsize_quota;
  
  struct quota *quota_init(const char *data)
  {
! 	return dirsize_quota.init(data);
  }
  
  void quota_deinit(struct quota *quota)
--- 3,13 ----
  #include "lib.h"
  #include "quota-private.h"
  
! extern struct quota QUOTA_TYPE;
  
  struct quota *quota_init(const char *data)
  {
! 	return QUOTA_TYPE.init(data);
  }
  
  void quota_deinit(struct quota *quota)
diff -crN quota/quota.h quota-rquotad/quota.h
*** quota/quota.h	Tue Jan 11 23:12:57 2005
--- quota-rquotad/quota.h	Wed Apr 20 17:35:53 2005
***************
*** 5,13 ****
  struct mailbox_transaction_context;
  
  /* Message storage size kilobytes. */
! #define QUOTA_NAME_STORAGE "storage"
  /* Number of messages. */
! #define QUOTA_NAME_MESSAGES "messages"
  
  struct quota;
  struct quota_root;
--- 5,13 ----
  struct mailbox_transaction_context;
  
  /* Message storage size kilobytes. */
! #define QUOTA_NAME_STORAGE "STORAGE"
  /* Number of messages. */
! #define QUOTA_NAME_MESSAGES "MESSAGES"
  
  struct quota;
  struct quota_root;


More information about the dovecot mailing list