/* quota.c -- program to report/reconstruct quotas * * Copyright (c) 1998-2003 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 other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@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.46 2003/04/22 17:39:41 rjs3 Exp $ */ #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #include #include #if HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif #include "assert.h" #include "imapconf.h" #include "exitcodes.h" #include "imap_err.h" #include "mailbox.h" #include "xmalloc.h" #include "mboxlist.h" #include "mboxname.h" #include "convert_code.h" extern int optind; extern char *optarg; /* current namespace */ static struct namespace quota_namespace; /* forward declarations */ void usage(void); void reportquota(void); int buildquotalist(char **roots, int nroots); int fixquota_mailbox(char *name, int matchlen, int maycreate, void* rock); int fixquota(int ispartial); int fixquota_fixroot(struct mailbox *mailbox, char *root); int fixquota_finish(int thisquota); struct quotaentry { struct quota quota; int refcount; int deleted; unsigned long newused; }; #define QUOTAGROW 300 struct quotaentry zeroquotaentry; struct quotaentry *quota; int quota_num = 0, quota_alloc = 0; int firstquota; int redofix; int partial; int main(int argc,char **argv) { int opt; int fflag = 0; int r, code = 0; char *alt_config = NULL; if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); while ((opt = getopt(argc, argv, "C:f")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; case 'f': fflag = 1; break; default: usage(); } } config_init(alt_config, "quota"); /* Set namespace -- force standard (internal) */ if ((r = mboxname_init_namespace("a_namespace, 1)) != 0) { syslog(LOG_ERR, error_message(r)); fatal(error_message(r), EC_CONFIG); } r = buildquotalist(argv+optind, argc-optind); if (!r && fflag) { mboxlist_init(0); r = fixquota(argc-optind); mboxlist_done(); } if (!r) reportquota(); if (r) { com_err("quota", r, (r == IMAP_IOERROR) ? error_message(errno) : NULL); code = convert_code(r); } exit(code); return 0; } void usage(void) { fprintf(stderr, "usage: quota [-C ] [-f] [prefix]...\n"); exit(EC_USAGE); } /* * Comparison function for sorting quota roots */ int compare_quota(const void *a, const void *b) { return strcmp(((struct quotaentry *)a)->quota.root, ((struct quotaentry *)b)->quota.root); } /* * Build the list of quota roots in 'quota' */ int buildquotalist(char **roots, int nroots) { int r; char quota_path[MAX_MAILBOX_PATH+1]; int i; DIR *dirp; DIR *topp; struct dirent *dirent; /* Translate separator in mailboxnames. * * 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. */ for (i = 0; i < nroots; i++) { mboxname_hiersep_tointernal("a_namespace, roots[i]); } snprintf(quota_path, sizeof(quota_path), "%s%s", config_dir, FNAME_QUOTADIR); if (chdir(quota_path)) { return IMAP_IOERROR; } topp = opendir("."); if (!topp) { return IMAP_IOERROR; } while ((dirent = readdir(topp))!=NULL) { if (dirent->d_name[0] == '.') continue; dirp = opendir(dirent->d_name); if (!dirp) continue; while ((dirent = readdir(dirp))!=NULL) { if (dirent->d_name[0] == '.') continue; /* If restricting our list, see if this quota file matches */ if (nroots) { for (i = 0; i < nroots; i++) { if (!strcmp(dirent->d_name, roots[i]) || (!strncmp(dirent->d_name, roots[i], strlen(roots[i])) && dirent->d_name[strlen(roots[i])] == '.')) break; } if (i == nroots) continue; } /* Ignore .NEW files */ i = strlen(dirent->d_name); if (i > 4 && !strcmp(dirent->d_name+i-4, ".NEW")) continue; if (quota_num == quota_alloc) { quota_alloc += QUOTAGROW; quota = (struct quotaentry *) xrealloc((char *)quota, quota_alloc * sizeof(struct quotaentry)); } quota[quota_num] = zeroquotaentry; quota[quota_num].quota.fd = -1; quota[quota_num].quota.root = xstrdup(dirent->d_name); r = mailbox_read_quota("a[quota_num].quota); if (quota[quota_num].quota.fd != -1) { close(quota[quota_num].quota.fd); quota[quota_num].quota.fd = -1; } if (r) { com_err(dirent->d_name, r, (r == EC_IOERR) ? error_message(errno) : NULL); quota[quota_num].quota.used = 0; quota[quota_num].quota.limit = -1; } quota_num++; } /* close this subdirectory */ closedir(dirp); } closedir(topp); qsort((char *)quota, quota_num, sizeof(*quota), compare_quota); return 0; } /* * Account for mailbox 'name' when fixing the quota roots */ int fixquota_mailbox(char *name, int matchlen, int maycreate, void* rock) { int r; struct mailbox mailbox; int i, len, thisquota, thisquotalen; while (firstquota < quota_num && strncmp(name, quota[firstquota].quota.root, strlen(quota[firstquota].quota.root)) > 0) { r = fixquota_finish(firstquota++); 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] == '.')) { quota[i].refcount++; if (len > thisquotalen) { thisquota = i; thisquotalen = len; } } } if (partial && thisquota == -1) return 0; r = mailbox_open_header(name, 0, &mailbox); if (r) return r; if (thisquota == -1) { if (mailbox.quota.root) { r = fixquota_fixroot(&mailbox, (char *)0); if (r) { mailbox_close(&mailbox); return r; } } mailbox_close(&mailbox); return 0; } if (!mailbox.quota.root || strcmp(mailbox.quota.root, quota[thisquota].quota.root) != 0) { r = fixquota_fixroot(&mailbox, quota[thisquota].quota.root); if (r) { mailbox_close(&mailbox); return r; } } if (quota[thisquota].quota.fd == -1) { r = mailbox_lock_quota("a[thisquota].quota); if (r) { mailbox_close(&mailbox); return r; } } r = mailbox_open_index(&mailbox); if (r) { mailbox_close(&mailbox); return r; } quota[thisquota].newused += mailbox.quota_mailbox_used; mailbox_close(&mailbox); return 0; } int fixquota_fixroot(struct mailbox *mailbox, char *root) { int i, r; /* * Locking order is to lock header before quota. We therefore * unlock all the quota roots we have locked in order to avoid a * deadlock. As releasing these locks can cause the quota use * recalculation to screw up, we set the global variable 'redofix' * to cause the quota use recalculation to be redone. * * We could optimize this by trying to get a nonblocking lock on * the header and unlocking all the quota roots only when that fails. */ for (i = firstquota; i < quota_num; i++) { if (quota[i].quota.fd != -1) { close(quota[i].quota.fd); quota[i].quota.fd = -1; } } redofix = 1; r = mailbox_lock_header(mailbox); if (r) return r; printf("%s: quota root %s --> %s\n", mailbox->name, mailbox->quota.root ? mailbox->quota.root : "(none)", root ? root : "(none)"); if (mailbox->quota.root) free(mailbox->quota.root); if (root) { mailbox->quota.root = xstrdup(root); } else { mailbox->quota.root = 0; } r = mailbox_write_header(mailbox); (void) mailbox_unlock_header(mailbox); return r; } /* * Finish fixing up a quota root */ int fixquota_finish(int thisquota) { int r; if (!quota[thisquota].refcount) { if (!quota[thisquota].deleted++) { char buf[MAX_MAILBOX_PATH+1]; printf("%s: removed\n", quota[thisquota].quota.root); mailbox_hash_quota(buf, sizeof(buf), quota[thisquota].quota.root); unlink(buf); } return 0; } if (quota[thisquota].quota.fd == -1) { r = mailbox_lock_quota("a[thisquota].quota); if (r) { if (quota[thisquota].quota.fd != -1) { close(quota[thisquota].quota.fd); quota[thisquota].quota.fd = -1; } return r; } } if (quota[thisquota].quota.used != quota[thisquota].newused) { printf("%s: usage was %lu, now %lu\n", quota[thisquota].quota.root, quota[thisquota].quota.used, quota[thisquota].newused); quota[thisquota].quota.used = quota[thisquota].newused; r = mailbox_write_quota("a[thisquota].quota); if (r) return r; } close(quota[thisquota].quota.fd); quota[thisquota].quota.fd = -1; return 0; } /* * Fix all the quota roots */ int fixquota(int ispartial) { int r = 0; static char pattern[2] = "*"; /* * Lock mailbox list to prevent mailbox creation/deletion * during the fix */ mboxlist_open(NULL); redofix = 1; while (redofix) { redofix = 0; firstquota = 0; partial = ispartial; r = (*quota_namespace.mboxlist_findall)("a_namespace, pattern, 1, 0, 0, fixquota_mailbox, NULL); if (r) { mboxlist_close(); return r; } while (firstquota < quota_num) { r = fixquota_finish(firstquota++); if (r) { mboxlist_close(); return r; } } } mboxlist_close(); 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 %7ld", 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(" %7ld %s\n", quota[i].quota.used / QUOTA_UNITS, buf); } } void fatal(const char* s, int code) { fprintf(stderr, "quota: %s\n", s); exit(code); }