/* quota.c -- program to report/reconstruct quotas
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: quota.c,v 1.72 2010/01/06 17:01:39 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "assert.h"
#include "cyrusdb.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "mboxlist.h"
#include "mboxname.h"
#include "quota.h"
#include "convert_code.h"
#include "util.h"
extern int optind;
extern char *optarg;
/* current namespace */
static struct namespace quota_namespace;
/* config.c stuff */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
struct fix_rock {
char *domain;
struct txn **tid;
unsigned long change_count;
};
struct quotaentry {
struct quota quota;
char *allocname;
int refcount;
int deleted;
uquota_t newused;
};
/* forward declarations */
void usage(void);
void reportquota(void);
int buildquotalist(char *domain, char **roots, int nroots,
struct fix_rock *frock);
int fixquota_mailbox(char *name, int matchlen, int maycreate, void *rock);
int fixquota(struct fix_rock *frock);
int fixquota_fixroot(struct mailbox *mailbox, const char *root);
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count);
#define QUOTAGROW 300
struct quotaentry *quota;
int quota_num = 0, quota_alloc = 0;
int firstquota = 0;
int redofix = 0;
int partial;
int main(int argc,char **argv)
{
int opt;
int fflag = 0;
int r, code = 0;
char *alt_config = NULL, *domain = NULL;
struct fix_rock frock;
struct txn *tid = NULL;
if ((geteuid()) == 0 && (become_cyrus() != 0)) {
fatal("must run as the Cyrus user", EC_USAGE);
}
while ((opt = getopt(argc, argv, "C:d:f")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'd':
domain = optarg;
break;
case 'f':
fflag = 1;
break;
default:
usage();
}
}
cyrus_init(alt_config, "quota", 0);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace("a_namespace, 1)) != 0) {
syslog(LOG_ERR, "%s", error_message(r));
fatal(error_message(r), EC_CONFIG);
}
/*
* Lock mailbox list to prevent mailbox creation/deletion
* during work
*/
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
if (!r) {
frock.domain = domain;
frock.tid = &tid;
frock.change_count = 0;
r = buildquotalist(domain, argv+optind, argc-optind,
fflag ? &frock : NULL);
}
if (!r && fflag) {
partial = argc-optind;
r = fixquota(&frock);
}
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
if (r) code = convert_code(r);
else reportquota();
cyrus_done();
return code;
}
void usage(void)
{
fprintf(stderr,
"usage: quota [-C <alt_config>] [-d <domain>] [-f] [prefix]...\n");
exit(EC_USAGE);
}
void errmsg(const char *fmt, const char *arg, int err)
{
char buf[1024];
size_t len;
len = snprintf(buf, sizeof(buf), fmt, arg);
if (len < sizeof(buf))
len += snprintf(buf+len, sizeof(buf)-len, ": %s", error_message(err));
if ((err == IMAP_IOERROR) && (len < sizeof(buf)))
len += snprintf(buf+len, sizeof(buf)-len, ": %%m");
syslog(LOG_ERR, "%s", buf);
fprintf(stderr, "%s\n", buf);
}
/*
* A matching mailbox was found, process it.
*/
static int found_match(char *name, int matchlen, int maycreate, void *frock)
{
int r;
if (quota_num == quota_alloc) {
/* Create new qr list entry */
quota_alloc += QUOTAGROW;
quota = (struct quotaentry *)
xrealloc((char *)quota, quota_alloc * sizeof(struct quotaentry));
memset("a[quota_num], 0, QUOTAGROW * sizeof(struct quotaentry));
}
/* See if the mailbox name corresponds to a quotaroot */
quota[quota_num].quota.root = name;
do {
r = quota_read("a[quota_num].quota, NULL, 0);
} while (r == IMAP_AGAIN);
switch (r) {
case 0:
/* Its a quotaroot! */
quota[quota_num].allocname = xstrdup(name);
quota[quota_num].quota.root = quota[quota_num].allocname;
quota_num++;
break;
case IMAP_QUOTAROOT_NONEXISTENT:
if (!frock || !quota_num ||
strncmp(name, quota[quota_num-1].quota.root,
strlen(quota[quota_num-1].quota.root))) {
/* Its not a quotaroot, and either we're not fixing quotas,
or its not part of the most recent quotaroot */
return 0;
}
break;
default:
return r;
}
if (frock) {
/* Recalculate the quota (we need the subfolders too!) */
r = fixquota_mailbox(name, matchlen, maycreate, frock);
}
return r;
}
/*
* Build the list of quota roots in 'quota'
*/
int buildquotalist(char *domain, char **roots, int nroots,
struct fix_rock *frock)
{
int i, r;
char buf[MAX_MAILBOX_BUFFER], *tail;
size_t domainlen = 0;
buf[0] = '\0';
tail = buf;
if (domain) {
domainlen = snprintf(buf, sizeof(buf), "%s!", domain);
tail += domainlen;
}
/*
* Walk through all given pattern(s) and resolve them to all
* matching mailbox names. Call found_match() for every mailbox
* name found. If no pattern is given, assume "*".
*/
i = 0;
do {
if (nroots > 0) {
/* Translate separator in quotaroot.
*
* We do this directly instead of using the mboxname_tointernal()
* function pointer because we know that we are using the internal
* namespace and so we don't have to allocate a buffer for the
* translated name.
*/
mboxname_hiersep_tointernal("a_namespace, roots[i], 0);
strlcpy(tail, roots[i], sizeof(buf) - domainlen);
}
else {
strlcpy(tail, "*", sizeof(buf) - domainlen);
}
i++;
r = (*quota_namespace.mboxlist_findall)("a_namespace, buf, 1, 0, 0,
&found_match, frock);
if (r < 0) {
errmsg("failed building quota list for '%s'", buf, IMAP_IOERROR);
return IMAP_IOERROR;
}
} while (i < nroots);
return 0;
}
/*
* Account for mailbox 'name' when fixing the quota roots
*/
int fixquota_mailbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
int r;
struct mailbox *mailbox = NULL;
int i, len, thisquota, thisquotalen;
struct fix_rock *frock = (struct fix_rock *) rock;
char *p, *domain = frock->domain;
/* make sure the domains match */
if (domain &&
(!(p = strchr(name, '!')) || (p - name) != (int) strlen(domain) ||
strncmp(name, domain, p - name))) {
return 0;
}
while (firstquota < quota_num &&
strncmp(name, quota[firstquota].quota.root,
strlen(quota[firstquota].quota.root)) > 0) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
if (r) return r;
}
thisquota = -1;
thisquotalen = 0;
for (i = firstquota;
i < quota_num && strcmp(name, quota[i].quota.root) >= 0; i++) {
len = strlen(quota[i].quota.root);
if (!strncmp(name, quota[i].quota.root, len) &&
(!name[len] || name[len] == '.' ||
(domain && name[len-1] == '!'))) {
quota[i].refcount++;
if (len > thisquotalen) {
thisquota = i;
thisquotalen = len;
}
}
}
if (partial && thisquota == -1) return 0;
r = mailbox_open_iwl(name, &mailbox);
if (r) errmsg("failed opening header for mailbox '%s'", name, r);
else {
if (thisquota == -1) {
if (mailbox->quotaroot) {
r = fixquota_fixroot(mailbox, (char *)0);
}
}
else {
if (!mailbox->quotaroot ||
strcmp(mailbox->quotaroot, quota[thisquota].quota.root) != 0) {
r = fixquota_fixroot(mailbox, quota[thisquota].quota.root);
}
if (!r) quota[thisquota].newused += mailbox->i.quota_mailbox_used;
}
mailbox_close(&mailbox);
}
if (r) {
/* mailbox error of some type, commit what we have */
quota_commit(frock->tid);
*(frock->tid) = NULL;
}
return r;
}
int fixquota_fixroot(struct mailbox *mailbox,
const char *root)
{
int r;
redofix = 1;
printf("%s: quota root %s --> %s\n", mailbox->name,
mailbox->quotaroot ? mailbox->quotaroot : "(none)",
root ? root : "(none)");
r = mailbox_set_quotaroot(mailbox, root);
if (r) errmsg("failed writing header for mailbox '%s'", mailbox->name, r);
return r;
}
/*
* Finish fixing up a quota root
*/
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count)
{
int r = 0;
if (!quota[thisquota].refcount) {
if (!quota[thisquota].deleted++) {
printf("%s: removed\n", quota[thisquota].quota.root);
r = quota_deleteroot(quota[thisquota].quota.root);
if (r) {
errmsg("failed deleting quotaroot '%s'",
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
}
return 0;
}
if (quota[thisquota].quota.used != quota[thisquota].newused) {
/* re-read the quota with the record locked */
r = quota_read("a[thisquota].quota, tid, 1);
if (r) {
errmsg("failed reading quotaroot '%s'",
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
}
if (quota[thisquota].quota.used != quota[thisquota].newused) {
printf("%s: usage was " UQUOTA_T_FMT ", now " UQUOTA_T_FMT "\n",
quota[thisquota].quota.root,
quota[thisquota].quota.used, quota[thisquota].newused);
quota[thisquota].quota.used = quota[thisquota].newused;
r = quota_write("a[thisquota].quota, tid);
if (r) {
errmsg("failed writing quotaroot '%s'",
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
}
/* commit the transaction every 100 changes */
if (*count && !(*count % 100)) {
quota_commit(tid);
*tid = NULL;
}
return r;
}
/*
* Fix all the quota roots
*/
int fixquota(struct fix_rock *frock)
{
int i, r = 0;
while (!r && redofix) {
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
}
redofix = 0;
firstquota = 0;
/*
* Loop over all qr entries and recalculate the quota.
* We need the subfolders too!
*/
for (i = 0; !r && i < quota_num; i++) {
r = (*quota_namespace.mboxlist_findall)("a_namespace,
quota[i].quota.root,
1, 0, 0, fixquota_mailbox,
frock);
}
}
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
}
if (!r && *(frock->tid)) quota_commit(frock->tid);
return 0;
}
/*
* Print out the quota report
*/
void reportquota(void)
{
int i;
char buf[MAX_MAILBOX_PATH+1];
printf(" Quota %% Used Used Root\n");
for (i = 0; i < quota_num; i++) {
if (quota[i].deleted) continue;
if (quota[i].quota.limit > 0) {
printf(" %7d " QUOTA_REPORT_FMT , quota[i].quota.limit,
((quota[i].quota.used / QUOTA_UNITS) * 100) / quota[i].quota.limit);
}
else if (quota[i].quota.limit == 0) {
printf(" 0 ");
}
else {
printf(" ");
}
/* Convert internal name to external */
(*quota_namespace.mboxname_toexternal)("a_namespace,
quota[i].quota.root,
"cyrus", buf);
printf(" " QUOTA_REPORT_FMT " %s\n",
quota[i].quota.used / QUOTA_UNITS, buf);
}
}