diff options
-rw-r--r-- | Changelog | 8 | ||||
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | edquota.c | 8 | ||||
-rw-r--r-- | quota.1 | 11 | ||||
-rw-r--r-- | quota.c | 78 | ||||
-rw-r--r-- | quotacheck.c | 4 | ||||
-rw-r--r-- | quotaops.c | 10 | ||||
-rw-r--r-- | quotaops.h | 2 | ||||
-rw-r--r-- | quotasys.c | 28 | ||||
-rw-r--r-- | quotasys.h | 6 | ||||
-rw-r--r-- | repquota.8 | 31 | ||||
-rw-r--r-- | repquota.c | 24 | ||||
-rw-r--r-- | setquota.c | 4 | ||||
-rw-r--r-- | warnquota.8 | 26 | ||||
-rw-r--r-- | warnquota.c | 208 | ||||
-rw-r--r-- | warnquota.conf | 9 |
16 files changed, 374 insertions, 85 deletions
@@ -5,6 +5,14 @@ Changes in quota-tools from 3.06 to 3.07 * rised maximal number of mountpoint to 256 (Jan Kara) * small fix in configure (Nathan Scott) * small fix in quotacheck(8) docs (Jan Kara) +* fixed quotacheck(8) to continue when old quota files were not found (Jan Kara) +* quota(1) now doesn't report failure to connect to rpc.rquotad server when -Q specified (Jan Kara) +* add quota(1) option -l (report only local filesystems) (Jan Kara) +* warnquota(8) now also mails specified member of the group about violation of the group quotas + when -g option is specified (Jan Kara) +* added options by which user can specify whether repquota(8) should translate names in + big chunks by scanning all users or individually. Added automagic detection using nsswitch.conf + which behaviour should lead to faster translating. (Jan Kara) Changes in quota-tools from 3.05 to 3.06 * fixed caching of libwrap configure result (Jan Kara) diff --git a/Makefile.in b/Makefile.in index 5cbe80b..713e242 100644 --- a/Makefile.in +++ b/Makefile.in @@ -84,6 +84,8 @@ install: all inst_mo $(ROOTDIR)$(sbindir) -mkdir -p $(ROOTDIR)$(sysconfdir) -$(INSTALL) -m $(DEF_CONF_MODE) warnquota.conf $(ROOTDIR)$(sysconfdir) + -$(INSTALL) -m $(DEF_CONF_MODE) quotatab $(ROOTDIR)$(sysconfdir) + -$(INSTALL) -m $(DEF_CONF_MODE) quotagrpadmins $(ROOTDIR)$(sysconfdir) -mkdir -p $(ROOTDIR)$(mandir)/man1 -mkdir -p $(ROOTDIR)$(mandir)/man2 -mkdir -p $(ROOTDIR)$(mandir)/man3 @@ -34,7 +34,7 @@ #ident "$Copyright: (c) 1980, 1990 Regents of the University of California. $" #ident "$Copyright: All rights reserved. $" -#ident "$Id: edquota.c,v 1.8 2002/03/27 16:21:26 jkar8572 Exp $" +#ident "$Id: edquota.c,v 1.9 2002/07/23 15:59:27 jkar8572 Exp $" /* * Disk quota editor. @@ -150,10 +150,10 @@ int main(int argc, char **argv) } if (pflag) { protoid = name2id(protoname, quotatype); - protoprivs = getprivs(protoid, handles); + protoprivs = getprivs(protoid, handles, 0); while (argc-- > 0) { id = name2id(*argv++, quotatype); - curprivs = getprivs(id, handles); + curprivs = getprivs(id, handles, 0); for (pprivs = protoprivs, cprivs = curprivs; pprivs && cprivs; pprivs = pprivs->dq_next, cprivs = cprivs->dq_next) { @@ -206,7 +206,7 @@ int main(int argc, char **argv) else { for (; argc > 0; argc--, argv++) { id = name2id(*argv, quotatype); - curprivs = getprivs(id, handles); + curprivs = getprivs(id, handles, 0); if (writeprivs(curprivs, tmpfd, *argv, quotatype) < 0) { errstr(_("Can't write quotas to file.\n")); continue; @@ -7,7 +7,7 @@ quota \- display disk usage and limits .B -F .I format-name ] [ -.BR -guvs \ | +.BR -guvsl \ | .B q ] .br @@ -16,7 +16,7 @@ quota \- display disk usage and limits .B -F .I format-name ] [ -.BR -uvs \ | +.BR -uvsl \ | .B q ] .I user @@ -26,7 +26,7 @@ quota \- display disk usage and limits .B -F .I format-name ] [ -.BR -gvs \ | +.BR -gvsl \ | .B q ] .I group @@ -67,10 +67,13 @@ will display quotas on filesystems where no storage is allocated. .TP .B \-s -flag will make +option will make .BR quota (1) try to choose units for showing limits, used space and used inodes. .TP +.B \-l +report quotas only on local filesystems (ie. ignore NFS mounted filesystems). +.TP .B \-q Print a more terse message, containing only information @@ -34,7 +34,7 @@ #ident "$Copyright: (c) 1980, 1990 Regents of the University of California. $" #ident "$Copyright: All rights reserved. $" -#ident "$Id: quota.c,v 1.11 2002/05/20 12:16:31 jkar8572 Exp $" +#ident "$Id: quota.c,v 1.12 2002/07/23 15:59:27 jkar8572 Exp $" /* * Disk quota reporting program. @@ -60,7 +60,15 @@ #include "pot.h" #include "common.h" -int qflag, vflag, sflag, fmt = -1; +#define FL_QUIET 1 +#define FL_VERBOSE 2 +#define FL_USER 4 +#define FL_GROUP 8 +#define FL_SMARTSIZE 16 +#define FL_LOCALONLY 32 +#define FL_QUIETREFUSE 64 + +int flags, fmt = -1; char *progname; void usage(void); @@ -71,31 +79,37 @@ int main(int argc, char **argv) { int ngroups; gid_t gidset[NGROUPS]; - int i, ret, gflag = 0, uflag = 0; + int i, ret; gettexton(); progname = basename(argv[0]); - while ((ret = getopt(argc, argv, "guqvsVF:")) != -1) { + while ((ret = getopt(argc, argv, "guqvsVlQF:")) != -1) { switch (ret) { case 'g': - gflag++; + flags |= FL_GROUP; break; case 'u': - uflag++; + flags |= FL_USER; break; case 'q': - qflag++; + flags |= FL_QUIET; break; case 'v': - vflag++; + flags |= FL_VERBOSE; break; case 'F': if ((fmt = name2fmt(optarg)) == QF_ERROR) /* Error? */ exit(1); break; case 's': - sflag++; + flags |= FL_SMARTSIZE; + break; + case 'l': + flags |= FL_LOCALONLY; + break; + case 'Q': + flags |= FL_QUIETREFUSE; break; case 'V': version(); @@ -107,15 +121,15 @@ int main(int argc, char **argv) argc -= optind; argv += optind; - if (!uflag && !gflag) - uflag++; + if (!(flags & FL_USER) && !(flags & FL_GROUP)) + flags |= FL_USER; init_kernel_interface(); ret = 0; if (argc == 0) { - if (uflag) + if (flags & FL_USER) ret |= showquotas(USRQUOTA, getuid()); - if (gflag) { + if (flags & FL_GROUP) { ngroups = getgroups(NGROUPS, gidset); if (ngroups < 0) die(1, _("quota: getgroups(): %s\n"), strerror(errno)); @@ -125,13 +139,13 @@ int main(int argc, char **argv) exit(ret); } - if (uflag && gflag) + if ((flags & FL_USER) && (flags & FL_GROUP)) usage(); - if (uflag) + if (flags & FL_USER) for (; argc > 0; argc--, argv++) ret |= showquotas(USRQUOTA, user2uid(*argv)); - else if (gflag) + else if (flags & FL_GROUP) for (; argc > 0; argc--, argv++) ret |= showquotas(GRPQUOTA, group2gid(*argv)); return ret; @@ -140,9 +154,9 @@ int main(int argc, char **argv) void usage(void) { errstr( "%s%s%s", - _("Usage: quota [-guqvs] [-F quotaformat]\n"), - _("\tquota [-qvs] [-F quotaformat] -u username ...\n"), - _("\tquota [-qvs] [-F quotaformat] -g groupname ...\n")); + _("Usage: quota [-guqvs] [-l | -Q] [-F quotaformat]\n"), + _("\tquota [-qvs] [-l | -Q] [-F quotaformat] -u username ...\n"), + _("\tquota [-qvs] [-l | -Q] [-F quotaformat] -g groupname ...\n")); fprintf(stderr, _("Bugs to: %s\n"), MY_EMAIL); exit(1); } @@ -159,12 +173,12 @@ int showquotas(int type, qid_t id) time(&now); id2name(id, type, name); - handles = create_handle_list(0, NULL, type, fmt, IOI_READONLY); - qlist = getprivs(id, handles); + handles = create_handle_list(0, NULL, type, fmt, IOI_READONLY | ((flags & FL_LOCALONLY) ? IOI_LOCALONLY : 0)); + qlist = getprivs(id, handles, !!(flags & FL_QUIETREFUSE)); over = 0; for (q = qlist; q; q = q->dq_next) { bover = iover = 0; - if (!vflag && !q->dq_dqb.dqb_isoftlimit && !q->dq_dqb.dqb_ihardlimit + if (!(flags & FL_VERBOSE) && !q->dq_dqb.dqb_isoftlimit && !q->dq_dqb.dqb_ihardlimit && !q->dq_dqb.dqb_bsoftlimit && !q->dq_dqb.dqb_bhardlimit) continue; msgi = NULL; @@ -200,7 +214,7 @@ int showquotas(int type, qid_t id) } } over |= bover | iover; - if (qflag) { + if (flags & FL_QUIET) { if ((msgi || msgb) && !lines++) heading(type, id, name, ""); if (msgi) @@ -209,7 +223,7 @@ int showquotas(int type, qid_t id) printf("\t%s %s\n", msgb, q->dq_h->qh_quotadev); continue; } - if (vflag || q->dq_dqb.dqb_curspace || q->dq_dqb.dqb_curinodes) { + if ((flags & FL_VERBOSE) || q->dq_dqb.dqb_curspace || q->dq_dqb.dqb_curinodes) { char numbuf[3][MAXNUMLEN]; if (!lines++) @@ -220,22 +234,22 @@ int showquotas(int type, qid_t id) printf("%15s", q->dq_h->qh_quotadev); if (bover) difftime2str(q->dq_dqb.dqb_btime, timebuf); - space2str(toqb(q->dq_dqb.dqb_curspace), numbuf[0], sflag); - space2str(q->dq_dqb.dqb_bsoftlimit, numbuf[1], sflag); - space2str(q->dq_dqb.dqb_bhardlimit, numbuf[2], sflag); + space2str(toqb(q->dq_dqb.dqb_curspace), numbuf[0], !!(flags & FL_SMARTSIZE)); + space2str(q->dq_dqb.dqb_bsoftlimit, numbuf[1], !!(flags & FL_SMARTSIZE)); + space2str(q->dq_dqb.dqb_bhardlimit, numbuf[2], !!(flags & FL_SMARTSIZE)); printf(" %7s%c %6s %7s %7s", numbuf[0], bover ? '*' : ' ', numbuf[1], numbuf[2], bover > 1 ? timebuf : ""); if (iover) difftime2str(q->dq_dqb.dqb_itime, timebuf); - number2str(q->dq_dqb.dqb_curinodes, numbuf[0], sflag); - number2str(q->dq_dqb.dqb_isoftlimit, numbuf[1], sflag); - number2str(q->dq_dqb.dqb_ihardlimit, numbuf[2], sflag); + number2str(q->dq_dqb.dqb_curinodes, numbuf[0], !!(flags & FL_SMARTSIZE)); + number2str(q->dq_dqb.dqb_isoftlimit, numbuf[1], !!(flags & FL_SMARTSIZE)); + number2str(q->dq_dqb.dqb_ihardlimit, numbuf[2], !!(flags & FL_SMARTSIZE)); printf(" %7s%c %6s %7s %7s\n", numbuf[0], iover ? '*' : ' ', numbuf[1], numbuf[2], iover > 1 ? timebuf : ""); continue; } } - if (!qflag && !lines) + if (!(flags & FL_QUIET) && !lines) heading(type, id, name, _("none")); freeprivs(qlist); dispose_handle_list(handles); @@ -246,7 +260,7 @@ void heading(int type, qid_t id, char *name, char *tag) { printf(_("Disk quotas for %s %s (%cid %d): %s\n"), type2name(type), name, *type2name(type), id, tag); - if (!qflag && !tag[0]) { + if (!(flags & FL_QUIET) && !tag[0]) { printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n", _("Filesystem"), _("blocks"), _("quota"), _("limit"), _("grace"), _("files"), _("quota"), _("limit"), _("grace")); diff --git a/quotacheck.c b/quotacheck.c index b318f34..351bf99 100644 --- a/quotacheck.c +++ b/quotacheck.c @@ -8,7 +8,7 @@ * New quota format implementation - Jan Kara <jack@suse.cz> - Sponsored by SuSE CR */ -#ident "$Id: quotacheck.c,v 1.29 2002/05/03 07:05:46 jkar8572 Exp $" +#ident "$Id: quotacheck.c,v 1.30 2002/07/23 15:59:27 jkar8572 Exp $" #include <dirent.h> #include <stdio.h> @@ -570,7 +570,7 @@ Please turn quotas off or use -f to force checking.\n"), } if (!(flags & FL_NEWFILE)) { /* Need to buffer file? */ - if (get_qf_name(mnt, type, (1 << cfmt), NF_EXIST, &qfname) < 0) { + if (get_qf_name(mnt, type, (1 << cfmt), 0, &qfname) < 0) { errstr(_("Cannot get quotafile name for %s\n"), mnt->mnt_fsname); return -1; } @@ -34,7 +34,7 @@ #ident "$Copyright: (c) 1980, 1990 Regents of the University of California. $" #ident "$Copyright: All rights reserved. $" -#ident "$Id: quotaops.c,v 1.7 2001/09/27 21:34:58 jkar8572 Exp $" +#ident "$Id: quotaops.c,v 1.8 2002/07/23 15:59:27 jkar8572 Exp $" #include <rpc/rpc.h> #include <sys/types.h> @@ -109,7 +109,7 @@ void update_grace_times(struct dquot *q) /* * Collect the requested quota information. */ -struct dquot *getprivs(qid_t id, struct quota_handle **handles) +struct dquot *getprivs(qid_t id, struct quota_handle **handles, int quiet) { struct dquot *q, *qtail = NULL, *qhead = NULL; int i; @@ -154,8 +154,10 @@ struct dquot *getprivs(qid_t id, struct quota_handle **handles) #endif if (!(q = handles[i]->qh_ops->read_dquot(handles[i], id))) { - errstr(_("Error while getting quota from %s for %u: %s\n"), - handles[i]->qh_quotadev, id, strerror(errno)); + /* If rpc.rquotad is not running filesystem might be just without quotas... */ + if (errno != ECONNREFUSED || !quiet) + errstr(_("Error while getting quota from %s for %u: %s\n"), + handles[i]->qh_quotadev, id, strerror(errno)); continue; } if (qhead == NULL) @@ -3,7 +3,7 @@ #include "quotaio.h" -struct dquot *getprivs(qid_t id, struct quota_handle ** handles); +struct dquot *getprivs(qid_t id, struct quota_handle ** handles, int quiet); int putprivs(struct dquot * qlist); int editprivs(char *tmpfile); int writeprivs(struct dquot * qlist, int outfd, char *name, int quotatype); @@ -16,6 +16,7 @@ #include <unistd.h> #include <fcntl.h> #include <signal.h> +#include <ctype.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/vfs.h> @@ -146,6 +147,33 @@ int id2name(int id, int qtype, char *buf) } /* + * Parse /etc/nsswitch.conf and return type of default passwd handling + */ +int passwd_handling(void) +{ + FILE *f; + char buf[1024], *colpos, *spcpos; + int ret = PASSWD_FILES; + + if (!(f = fopen("/etc/nsswitch.conf", "r"))) + return PASSWD_FILES; /* Can't open nsswitch.conf - fallback on compatible mode */ + while (fgets(buf, sizeof(buf), f)) { + if (strncmp(buf, "passwd:", 7)) /* Not passwd entry? */ + continue; + for (colpos = buf+7; isspace(*colpos); colpos++); + if (!*colpos) /* Not found any type of handling? */ + break; + for (spcpos = colpos; !isspace(*spcpos) && *spcpos; spcpos++); + *spcpos = 0; + if (!strcmp(colpos, "db") || !strcmp(colpos, "nis") || !strcmp(colpos, "nis+")) + ret = PASSWD_DB; + break; + } + fclose(f); + return ret; +} + +/* * Convert quota format name to number */ int name2fmt(char *str) @@ -58,6 +58,12 @@ int gid2group(gid_t, char *); /* Convert id to user/group name */ int id2name(int id, int qtype, char *buf); +/* Possible default passwd handling */ +#define PASSWD_FILES 0 +#define PASSWD_DB 1 +/* Parse /etc/nsswitch.conf and return type of default passwd handling */ +int passwd_handling(void); + /* Convert quota format name to number */ int name2fmt(char *str); @@ -7,6 +7,10 @@ repquota \- summarize quotas for a filesystem [ .B \-vsug ] [ +.B \-c +| +.B \-C +] [ .B \-t | .B \-n @@ -20,6 +24,10 @@ repquota \- summarize quotas for a filesystem [ .B \-avtsug ] [ +.B \-c +| +.B \-C +] [ .B \-t | .B \-n @@ -42,6 +50,22 @@ prints a summary of the disc usage and quotas for the specified file systems. For each user the current number of files and amount of space (in kilobytes) is printed, along with any quotas created with .BR edquota (8). +As +.B repquota +has to translate ids of all users/groups to names (unless option +.B -n +was specified) it may take a while to +print all the information. To make translating as fast as possible +.B repquota +tries to detect (by reading +.BR /etc/nsswitch.conf ) +whether entries are stored in standard plain text file or in database and either +translates chunks of 1024 names or each name individually. You can override this +autodetection by +.B -c +or +.B -C +options. .SH OPTIONS .TP .B \-a @@ -53,6 +77,13 @@ to be read-write with quotas. Report all quotas, even if there is no usage. Be also more verbose about quotafile information. .TP +.B \-c +Cache entries to report and translate uids/gids to names in big chunks by scanning +all users (default). This is good (fast) behaviour when using /etc/passwd file. +.TP +.B \-C +Translate individual entries. This is faster when you have users stored in database. +.TP .B \-t Truncate user/group names longer than 9 characters. This results in nicer output when there are such names. @@ -31,6 +31,7 @@ #define FL_TRUNCNAMES 16 #define FL_SHORTNUMS 32 #define FL_NONAME 64 +#define FL_NOCACHE 128 /* Don't cache dquots before resolving */ int flags, fmt = -1; char **mnt; @@ -41,7 +42,7 @@ char *progname; static void usage(void) { - errstr(_("Utility for reporting quotas.\nUsage:\n%s [-vugs] [-t|n] [-F quotaformat] (-a | mntpoint)\n"), progname); + errstr(_("Utility for reporting quotas.\nUsage:\n%s [-vugs] [-c|C] [-t|n] [-F quotaformat] (-a | mntpoint)\n"), progname); fprintf(stderr, _("Bugs to %s\n"), MY_EMAIL); exit(1); } @@ -49,8 +50,9 @@ static void usage(void) static void parse_options(int argcnt, char **argstr) { int ret; + int cache_specified = 0; - while ((ret = getopt(argcnt, argstr, "VavughtsnF:")) != -1) { + while ((ret = getopt(argcnt, argstr, "VavughtsncCF:")) != -1) { switch (ret) { case '?': case 'h': @@ -76,6 +78,13 @@ static void parse_options(int argcnt, char **argstr) case 's': flags |= FL_SHORTNUMS; break; + case 'C': + flags |= FL_NOCACHE; + cache_specified = 1; + break; + case 'c': + cache_specified = 1; + break; case 'F': if ((fmt = name2fmt(optarg)) == QF_ERROR) exit(1); @@ -105,6 +114,8 @@ static void parse_options(int argcnt, char **argstr) mnt = argstr + optind; mntcnt = argcnt - optind; } + if (!cache_specified && !(flags & FL_NONAME) && passwd_handling() == PASSWD_DB) + flags |= FL_NOCACHE; } static char overlim(uint usage, uint softlim, uint hardlim) @@ -198,8 +209,15 @@ static int output(struct dquot *dquot, char *name) sprintf(namebuf, "#%u", dquot->dq_id); print(dquot, namebuf); } - else if (name) + else if (name || flags & FL_NOCACHE) { + char namebuf[MAXNAMELEN]; + + if (!name) { + id2name(dquot->dq_id, dquot->dq_h->qh_type, namebuf); + name = namebuf; + } print(dquot, name); + } else { memcpy(dquot_cache+cached_dquots++, dquot, sizeof(struct dquot)); if (cached_dquots >= MAX_CACHE_DQUOTS) @@ -173,9 +173,9 @@ static void setlimits(struct quota_handle **handles) { struct dquot *q, *protoq, *protoprivs = NULL, *curprivs; - curprivs = getprivs(id, handles); + curprivs = getprivs(id, handles, 0); if (flags & FL_PROTO) { - protoprivs = getprivs(protoid, handles); + protoprivs = getprivs(protoid, handles, 0); for (q = curprivs, protoq = protoprivs; q; q = q->dq_next, protoq = protoq->dq_next) { q->dq_dqb.dqb_bsoftlimit = protoq->dq_dqb.dqb_bsoftlimit; q->dq_dqb.dqb_bhardlimit = protoq->dq_dqb.dqb_bhardlimit; diff --git a/warnquota.8 b/warnquota.8 index e8548a4..cd39cef 100644 --- a/warnquota.8 +++ b/warnquota.8 @@ -4,6 +4,8 @@ warnquota \- send mail to users over quota .SH SYNOPSIS .B warnquota [ +.B \-ug +] [ .B \-F .I quotaformat ] [ @@ -12,6 +14,9 @@ warnquota \- send mail to users over quota ] [ .B \-c .I configfile +] [ +.B \-a +.I adminsfile ] .SH DESCRIPTION .B warnquota @@ -32,19 +37,33 @@ Possible format names are: .B xfs (quota on XFS filesystem) .TP -.B -q quotatab +.B -q \f2quotatab\f1 Use .I quotatab instead of .I /etc/quotatab as file with device description strings (see example file for syntax). .TP -.B -c configfile +.B -c \f2configfile\f1 Use .I configfile instead of .I /etc/warnquota.conf as configuration file (see example file for syntax). +.TP +.B -a \f2adminsfile\f1 +Use +.I adminsfile +instead of +.I /etc/quotagrpadmins +as a file with administrators of the groups. +.TP +.B -u +check whether users are not exceeding quotas (default). +.TP +.B -g +check whether groups are not exceeding quotas. If group is exceeding quota +a mail is sent to the user specified in /etc/quotagrpadmins. .SH FILES .PD 0 .TP 20 @@ -60,6 +79,9 @@ configuration file .B /etc/quotatab device description .TP +.B /etc/quotagrpadmins +administrators of the groups +.TP .B /etc/mtab default filesystems .TP diff --git a/warnquota.c b/warnquota.c index 17638a5..f973fa6 100644 --- a/warnquota.c +++ b/warnquota.c @@ -10,7 +10,7 @@ * * Author: Marco van Wieringen <mvw@planets.elm.net> * - * Version: $Id: warnquota.c,v 1.10 2002/03/27 16:21:26 jkar8572 Exp $ + * Version: $Id: warnquota.c,v 1.11 2002/07/23 15:59:27 jkar8572 Exp $ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -26,6 +26,7 @@ #include <errno.h> #include <ctype.h> #include <signal.h> +#include <grp.h> #include <sys/types.h> #include <sys/wait.h> @@ -44,21 +45,36 @@ #define SUPPORT "support@localhost" #define PHONE "(xxx) xxx-xxxx or (yyy) yyy-yyyy" -#define DEF_MESSAGE _("Hi,\n\nWe noticed that you are in violation with the quotasystem\n" \ +#define DEF_USER_MESSAGE _("Hi,\n\nWe noticed that you are in violation with the quotasystem\n" \ "used on this system. We have found the following violations:\n\n") -#define DEF_SIGNATURE _("\nWe hope that you will cleanup before your grace period expires.\n" \ +#define DEF_USER_SIGNATURE _("\nWe hope that you will cleanup before your grace period expires.\n" \ "\nBasically, this means that the system thinks you are using more disk space\n" \ "on the above partition(s) than you are allowed. If you do not delete files\n" \ "and get below your quota before the grace period expires, the system will\n" \ "prevent you from creating new files.\n\n" \ "For additional assistance, please contact us at %s\nor via " \ "phone at %s.\n") +#define DEF_GROUP_MESSAGE _("Hi,\n\nWe noticed that the group %s you are member of violates the quotasystem\n" \ + "used on this system. We have found the following violations:\n\n") +#define DEF_GROUP_SIGNATURE _("\nPlease cleanup the group data before the grace period expires.\n" \ + "\nBasically, this means that the system thinks group is using more disk space\n" \ + "on the above partition(s) than it is allowed. If you do not delete files\n" \ + "and get below group quota before the grace period expires, the system will\n" \ + "prevent you and other members of the group from creating new files owned by\n" \ + "the group.\n\n" \ + "For additional assistance, please contact us at %s\nor via " \ + "phone at %s.\n") #define SHELL "/bin/sh" #define QUOTATAB "/etc/quotatab" #define CNF_BUFFER 2048 #define IOBUF_SIZE 16384 /* Size of buffer for line in config files */ +#define ADMIN_TAB_ALLOC 256 /* How many entries to admins table should we allocate at once? */ #define WARNQUOTA_CONF "/etc/warnquota.conf" +#define ADMINSFILE "/etc/quotagrpadmins" + +#define FL_USER 1 +#define FL_GROUP 2 struct usage { char *devicename; @@ -73,11 +89,14 @@ struct configparams { char cc_to[CNF_BUFFER]; char support[CNF_BUFFER]; char phone[CNF_BUFFER]; - char *message; - char *signature; + char *user_message; + char *user_signature; + char *group_message; + char *group_signature; }; struct offenderlist { + int offender_type; int offender_id; char *offender_name; struct usage *usage; @@ -89,29 +108,37 @@ typedef struct quotatable { char *devdesc; } quotatable_t; -int qtab_i = 0, fmt = -1; -char *configfile = WARNQUOTA_CONF, *quotatabfile = QUOTATAB; +struct adminstable { + char *grpname; + char *adminname; +}; + +int qtab_i = 0, fmt = -1, flags; +char *configfile = WARNQUOTA_CONF, *quotatabfile = QUOTATAB, *adminsfile = ADMINSFILE; char *progname; -quotatable_t *quotatable = (quotatable_t *) NULL; +quotatable_t *quotatable; +int adminscnt, adminsalloc; +struct adminstable *adminstable; /* * Global pointers to list. */ static struct offenderlist *offenders = (struct offenderlist *)0; -struct offenderlist *add_offender(int id, char *name) +struct offenderlist *add_offender(int type, int id, char *name) { struct offenderlist *offender; char namebuf[MAXNAMELEN]; if (!name) { - if (id2name(id, USRQUOTA, namebuf)) { - errstr(_("Can't get name for uid %u.\n"), id); + if (id2name(id, type, namebuf)) { + errstr(_("Can't get name for uid/gid %u.\n"), id); return NULL; } name = namebuf; } offender = (struct offenderlist *)smalloc(sizeof(struct offenderlist)); + offender->offender_type = type; offender->offender_id = id; offender->offender_name = sstrdup(name); offender->usage = (struct usage *)NULL; @@ -126,11 +153,11 @@ void add_offence(struct dquot *dquot, char *name) struct usage *usage; for (lptr = offenders; lptr; lptr = lptr->next) - if (lptr->offender_id == dquot->dq_id) + if (dquot->dq_h->qh_type == lptr->offender_type && lptr->offender_id == dquot->dq_id) break; if (!lptr) - if (!(lptr = add_offender(dquot->dq_id, name))) + if (!(lptr = add_offender(dquot->dq_h->qh_type, dquot->dq_id, name))) return; usage = (struct usage *)smalloc(sizeof(struct usage)); @@ -183,6 +210,11 @@ FILE *run_mailer(char *command) } } +int admin_name_cmp(const void *key, const void *mem) +{ + return strcmp(key, ((struct adminstable *)mem)->grpname); +} + int mail_user(struct offenderlist *offender, struct configparams *config) { struct usage *lptr; @@ -190,19 +222,37 @@ int mail_user(struct offenderlist *offender, struct configparams *config) int cnt, status; char timebuf[MAXTIMELEN]; struct util_dqblk *dqb; + char *to; + + if (offender->offender_type == USRQUOTA) + to = offender->offender_name; + else { + struct adminstable *admin; + if (!(admin = bsearch(offender->offender_name, adminstable, adminscnt, sizeof(struct adminstable), admin_name_cmp))) { + errstr(_("Administrator for a group %s not found. Cancelling mail.\n"), offender->offender_name); + return -1; + } + to = admin->adminname; + } if (!(fp = run_mailer(config->mail_cmd))) return -1; fprintf(fp, "From: %s\n", config->from); fprintf(fp, "Reply-To: %s\n", config->support); fprintf(fp, "Subject: %s\n", config->subject); - fprintf(fp, "To: %s\n", offender->offender_name); + fprintf(fp, "To: %s\n", to); fprintf(fp, "Cc: %s\n", config->cc_to); fprintf(fp, "\n"); - if (config->message) - fputs(config->message, fp); + if (offender->offender_type == USRQUOTA) + if (config->user_message) + fputs(config->user_message, fp); + else + fputs(DEF_USER_MESSAGE, fp); else - fputs(DEF_MESSAGE, fp); + if (config->group_message) + fprintf(fp, config->group_message, offender->offender_name); + else + fprintf(fp, DEF_GROUP_MESSAGE, offender->offender_name); for (lptr = offender->usage; lptr; lptr = lptr->next) { dqb = &lptr->dq_dqb; @@ -235,10 +285,16 @@ int mail_user(struct offenderlist *offender, struct configparams *config) fprintf(fp, " %6Lu%6Lu%6Lu%7s\n\n", (long long)dqb->dqb_curinodes, (long long)dqb->dqb_isoftlimit, (long long)dqb->dqb_ihardlimit, timebuf); } - if (config->signature) - fputs(config->signature, fp); + if (offender->offender_type == USRQUOTA) + if (config->user_signature) + fputs(config->user_signature, fp); + else + fprintf(fp, DEF_USER_SIGNATURE, config->support, config->phone); else - fprintf(fp, DEF_SIGNATURE, config->support, config->phone); + if (config->group_signature) + fputs(config->group_signature, fp); + else + fprintf(fp, DEF_GROUP_SIGNATURE, config->support, config->phone); fclose(fp); if (wait(&status) < 0) /* Wait for mailer */ errstr(_("Can't wait for mailer: %s\n"), strerror(errno)); @@ -363,7 +419,7 @@ int readconfigfile(const char *filename, struct configparams *config) sstrncpy(config->cc_to, CC_TO, CNF_BUFFER); sstrncpy(config->support, SUPPORT, CNF_BUFFER); sstrncpy(config->phone, PHONE, CNF_BUFFER); - config->signature = config->message = NULL; + config->user_signature = config->user_message = config->group_signature = config->group_message = NULL; if (!(fp = fopen(filename, "r"))) { errstr(_("Can't open %s: %s\n"), filename, strerror(errno)); @@ -420,12 +476,20 @@ int readconfigfile(const char *filename, struct configparams *config) else if (!strcmp(var, "PHONE")) sstrncpy(config->phone, value, CNF_BUFFER); else if (!strcmp(var, "MESSAGE")) { - config->message = sstrdup(value); - create_eoln(config->message); + config->user_message = sstrdup(value); + create_eoln(config->user_message); } else if (!strcmp(var, "SIGNATURE")) { - config->signature = sstrdup(value); - create_eoln(config->signature); + config->user_signature = sstrdup(value); + create_eoln(config->user_signature); + } + else if (!strcmp(var, "GROUP_MESSAGE")) { + config->group_message = sstrdup(value); + create_eoln(config->group_message); + } + else if (!strcmp(var, "GROUP_SIGNATURE")) { + config->group_signature = sstrdup(value); + create_eoln(config->group_signature); } else /* not matched at all */ errstr(_("Error in config file (line %d), ignoring\n"), line); @@ -440,6 +504,68 @@ int readconfigfile(const char *filename, struct configparams *config) return 0; } +int admin_cmp(const void *a1, const void *a2) +{ + return strcmp(((struct adminstable *)a1)->grpname, ((struct adminstable *)a2)->grpname); +} + +/* Get administrators of the groups */ +int get_groupadmins(void) +{ + FILE *f; + int line = 0; + char buffer[IOBUF_SIZE], *colpos, *grouppos, *endname, *adminpos; + + if (!(f = fopen(adminsfile, "r"))) { + errstr(_("Can't open file with group administrators: %s\n"), strerror(errno)); + return -1; + } + + while (fgets(buffer, IOBUF_SIZE, f)) { + line++; + if (buffer[0] == ';' || buffer[0] == '#') + continue; + /* Skip initial spaces */ + for (colpos = buffer; isspace(*colpos); colpos++); + if (!*colpos) /* Empty line? */ + continue; + /* Find splitting colon */ + for (grouppos = colpos; *colpos && *colpos != ':'; colpos++); + if (!*colpos || grouppos == colpos) { + errstr(_("Parse error at line %d. Can't find end of group name.\n"), line); + continue; + } + /* Cut trailing spaces */ + for (endname = colpos-1; isspace(*endname); endname--); + *(++endname) = 0; + /* Skip initial spaces at admins name */ + for (colpos++; isspace(*colpos); colpos++); + if (!*colpos) { + errstr(_("Parse error at line %d. Can't find administrators name.\n"), line); + continue; + } + /* Go through admins name */ + for (adminpos = colpos; !isspace(*colpos); colpos++); + if (*colpos) { /* Some characters after name? */ + *colpos = 0; + /* Skip trailing spaces */ + for (colpos++; isspace(*colpos); colpos++); + if (*colpos) { + errstr(_("Parse error at line %d. Trailing characters after administrators name.\n"), line); + continue; + } + } + if (adminscnt >= adminsalloc) + adminstable = srealloc(adminstable, sizeof(struct adminstable)*(adminsalloc+=ADMIN_TAB_ALLOC)); + adminstable[adminscnt].grpname = sstrdup(grouppos); + adminstable[adminscnt++].adminname = sstrdup(adminpos); + } + + fclose(f); + qsort(adminstable, adminscnt, sizeof(struct adminstable), admin_cmp); + return 0; +} + void warn_quota(void) { struct quota_handle **handles; @@ -451,9 +577,20 @@ void warn_quota(void) if (get_quotatable() < 0) exit(1); - handles = create_handle_list(0, NULL, USRQUOTA, -1, IOI_LOCALONLY | IOI_READONLY | IOI_OPENFILE); - for (i = 0; handles[i]; i++) - handles[i]->qh_ops->scan_dquots(handles[i], check_offence); + if (flags & FL_USER) { + handles = create_handle_list(0, NULL, USRQUOTA, -1, IOI_LOCALONLY | IOI_READONLY | IOI_OPENFILE); + for (i = 0; handles[i]; i++) + handles[i]->qh_ops->scan_dquots(handles[i], check_offence); + dispose_handle_list(handles); + } + if (flags & FL_GROUP) { + if (get_groupadmins() < 0) + exit(1); + handles = create_handle_list(0, NULL, GRPQUOTA, -1, IOI_LOCALONLY | IOI_READONLY | IOI_OPENFILE); + for (i = 0; handles[i]; i++) + handles[i]->qh_ops->scan_dquots(handles[i], check_offence); + dispose_handle_list(handles); + } if (mail_to_offenders(&config) < 0) exit(1); } @@ -461,14 +598,14 @@ void warn_quota(void) /* Print usage information */ static void usage(void) { - errstr(_("Usage:\n warnquota [-F quotaformat] [-c configfile] [-q quotatabfile]\n")); + errstr(_("Usage:\n warnquota [-ug] [-F quotaformat] [-c configfile] [-q quotatabfile]\n")); } static void parse_options(int argcnt, char **argstr) { int ret; - while ((ret = getopt(argcnt, argstr, "VF:hc:q:")) != -1) { + while ((ret = getopt(argcnt, argstr, "ugVF:hc:q:a:")) != -1) { switch (ret) { case '?': case 'h': @@ -486,8 +623,19 @@ static void parse_options(int argcnt, char **argstr) case 'q': quotatabfile = optarg; break; + case 'a': + adminsfile = optarg; + break; + case 'u': + flags |= FL_USER; + break; + case 'g': + flags |= FL_GROUP; + break; } } + if (!(flags & FL_USER) && !(flags & FL_GROUP)) + flags |= FL_USER; } int main(int argc, char **argv) diff --git a/warnquota.conf b/warnquota.conf index e406298..b31fef2 100644 --- a/warnquota.conf +++ b/warnquota.conf @@ -13,12 +13,19 @@ SUPPORT = "support@myhost.com" PHONE = "(123) 456-1111 or (222) 333-4444" # Text in the beginning of the mail (if not specified, default text is used) # This way text can be split to more lines -# Line break are done by '|' character +# Line breaks are done by '|' character MESSAGE = Hello, I've noticed you use too much space\ on my disk|Delete your files on following filesystems:| # Text in the end of the mail (if not specified, default text using SUPPORT and PHONE # is created) SIGNATURE = See you!| Your admin| +# Following text is used for mails about group exceeding quotas +# It should contain string %s exactly once - it will be substituted for a group name +GROUP_MESSAGE = Hello, a group '%s' you're member of use too much space.|\ +I chose you to do the cleanup.|Delete group files on following filesystems:| +# Text in the end of the mail to the group (if not specified, default text using SUPPORT +# and PHONE is created). +GROUP_SIGNATURE = See you!| Your admin| # # end of example warnquota.conf file # |