Home | History | Annotate | Line # | Download | only in quota
quota.c revision 1.42
      1 /*	$NetBSD: quota.c,v 1.42 2011/11/30 16:09:29 dholland Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1980, 1990, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * This code is derived from software contributed to Berkeley by
      8  * Robert Elz at The University of Melbourne.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. Neither the name of the University nor the names of its contributors
     19  *    may be used to endorse or promote products derived from this software
     20  *    without specific prior written permission.
     21  *
     22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     32  * SUCH DAMAGE.
     33  */
     34 
     35 #include <sys/cdefs.h>
     36 #ifndef lint
     37 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\
     38  The Regents of the University of California.  All rights reserved.");
     39 #endif /* not lint */
     40 
     41 #ifndef lint
     42 #if 0
     43 static char sccsid[] = "@(#)quota.c	8.4 (Berkeley) 4/28/95";
     44 #else
     45 __RCSID("$NetBSD: quota.c,v 1.42 2011/11/30 16:09:29 dholland Exp $");
     46 #endif
     47 #endif /* not lint */
     48 
     49 /*
     50  * Disk quota reporting program.
     51  */
     52 #include <sys/param.h>
     53 #include <sys/types.h>
     54 #include <sys/file.h>
     55 #include <sys/stat.h>
     56 #include <sys/mount.h>
     57 #include <sys/socket.h>
     58 
     59 #include <assert.h>
     60 #include <ctype.h>
     61 #include <err.h>
     62 #include <errno.h>
     63 #include <fstab.h>
     64 #include <grp.h>
     65 #include <netdb.h>
     66 #include <pwd.h>
     67 #include <stdio.h>
     68 #include <stdlib.h>
     69 #include <string.h>
     70 #include <time.h>
     71 #include <unistd.h>
     72 
     73 #include <quota/quotaprop.h>
     74 #include <quota/quota.h>
     75 
     76 #include "printquota.h"
     77 #include "getvfsquota.h"
     78 
     79 struct quotause {
     80 	struct	quotause *next;
     81 	long	flags;
     82 	uid_t	id;
     83 	struct	quotaval qv[QUOTA_NLIMITS];
     84 	char	fsname[MAXPATHLEN + 1];
     85 };
     86 #define	FOUND	0x01
     87 #define	QUOTA2	0x02
     88 
     89 static int	anyusage(struct quotaval *, unsigned);
     90 static int	anyover(struct quotaval *, unsigned, time_t);
     91 static const char *getovermsg(struct quotaval *, const char *, time_t);
     92 static struct quotause	*getprivs(id_t, int);
     93 static void	heading(int, id_t, const char *, const char *);
     94 static int	isover(struct quotaval *qv, time_t now);
     95 static void	printqv(struct quotaval *, int, int, time_t);
     96 static void	showgid(gid_t);
     97 static void	showgrpname(const char *);
     98 static void	showonequota(int, id_t, const char *, struct quotause *);
     99 static void	showquotas(int, id_t, const char *);
    100 static void	showuid(uid_t);
    101 static void	showusrname(const char *);
    102 static int	unlimited(struct quotaval *qvs, unsigned numqvs);
    103 static void	usage(void) __dead;
    104 
    105 static int	qflag = 0;
    106 static int	vflag = 0;
    107 static int	hflag = 0;
    108 static int	dflag = 0;
    109 static int	Dflag = 0;
    110 static uid_t	myuid;
    111 static int needheading;
    112 
    113 int
    114 main(int argc, char *argv[])
    115 {
    116 	int ngroups;
    117 	gid_t mygid, gidset[NGROUPS];
    118 	int i, gflag = 0, uflag = 0;
    119 	int ch;
    120 
    121 	myuid = getuid();
    122 	while ((ch = getopt(argc, argv, "Ddhugvq")) != -1) {
    123 		switch(ch) {
    124 		case 'g':
    125 			gflag++;
    126 			break;
    127 		case 'u':
    128 			uflag++;
    129 			break;
    130 		case 'v':
    131 			vflag++;
    132 			break;
    133 		case 'q':
    134 			qflag++;
    135 			break;
    136 		case 'h':
    137 			hflag++;
    138 			break;
    139 		case 'd':
    140 			dflag++;
    141 			break;
    142 		case 'D':
    143 			Dflag++;
    144 			break;
    145 		default:
    146 			usage();
    147 		}
    148 	}
    149 	argc -= optind;
    150 	argv += optind;
    151 	if (!uflag && !gflag)
    152 		uflag++;
    153 	if (dflag) {
    154 #if 0
    155 		if (myuid != 0)
    156 			errx(1, "-d: permission denied");
    157 #endif
    158 		if (uflag)
    159 			showquotas(QUOTA_CLASS_USER, 0, "");
    160 		if (gflag)
    161 			showquotas(QUOTA_CLASS_GROUP, 0, "");
    162 		return 0;
    163 	}
    164 	if (argc == 0) {
    165 		if (uflag)
    166 			showuid(myuid);
    167 		if (gflag) {
    168 			if (dflag)
    169 				showgid(0);
    170 			else {
    171 				mygid = getgid();
    172 				ngroups = getgroups(NGROUPS, gidset);
    173 				if (ngroups < 0)
    174 					err(1, "getgroups");
    175 				showgid(mygid);
    176 				for (i = 0; i < ngroups; i++)
    177 					if (gidset[i] != mygid)
    178 						showgid(gidset[i]);
    179 			}
    180 		}
    181 		return 0;
    182 	}
    183 	if (uflag && gflag)
    184 		usage();
    185 	if (uflag) {
    186 		for (; argc > 0; argc--, argv++) {
    187 			if (alldigits(*argv))
    188 				showuid((uid_t)atoi(*argv));
    189 			else
    190 				showusrname(*argv);
    191 		}
    192 		return 0;
    193 	}
    194 	if (gflag) {
    195 		for (; argc > 0; argc--, argv++) {
    196 			if (alldigits(*argv))
    197 				showgid((gid_t)atoi(*argv));
    198 			else
    199 				showgrpname(*argv);
    200 		}
    201 		return 0;
    202 	}
    203 	/* NOTREACHED */
    204 	return 0;
    205 }
    206 
    207 static void
    208 usage(void)
    209 {
    210 	const char *p = getprogname();
    211 	fprintf(stderr, "Usage: %s [-Dhguqv]\n"
    212 	    "\t%s [-Dhqv] -u username ...\n"
    213 	    "\t%s [-Dhqv] -g groupname ...\n"
    214 	    "\t%s -d [-Dhguqv]\n", p, p, p, p);
    215 	exit(1);
    216 }
    217 
    218 /*
    219  * Print out quotas for a specified user identifier.
    220  */
    221 static void
    222 showuid(uid_t uid)
    223 {
    224 	struct passwd *pwd = getpwuid(uid);
    225 	const char *name;
    226 
    227 	if (pwd == NULL)
    228 		name = "(no account)";
    229 	else
    230 		name = pwd->pw_name;
    231 	if (uid != myuid && myuid != 0) {
    232 		warnx("%s (uid %d): permission denied", name, uid);
    233 		return;
    234 	}
    235 	showquotas(QUOTA_CLASS_USER, uid, name);
    236 }
    237 
    238 /*
    239  * Print out quotas for a specified user name.
    240  */
    241 static void
    242 showusrname(const char *name)
    243 {
    244 	struct passwd *pwd = getpwnam(name);
    245 
    246 	if (pwd == NULL) {
    247 		warnx("%s: unknown user", name);
    248 		return;
    249 	}
    250 	if (pwd->pw_uid != myuid && myuid != 0) {
    251 		warnx("%s (uid %d): permission denied", name, pwd->pw_uid);
    252 		return;
    253 	}
    254 	showquotas(QUOTA_CLASS_USER, pwd->pw_uid, name);
    255 }
    256 
    257 /*
    258  * Print out quotas for a specified group identifier.
    259  */
    260 static void
    261 showgid(gid_t gid)
    262 {
    263 	struct group *grp = getgrgid(gid);
    264 	int ngroups;
    265 	gid_t mygid, gidset[NGROUPS];
    266 	int i;
    267 	const char *name;
    268 
    269 	if (grp == NULL)
    270 		name = "(no entry)";
    271 	else
    272 		name = grp->gr_name;
    273 	mygid = getgid();
    274 	ngroups = getgroups(NGROUPS, gidset);
    275 	if (ngroups < 0) {
    276 		warn("getgroups");
    277 		return;
    278 	}
    279 	if (gid != mygid) {
    280 		for (i = 0; i < ngroups; i++)
    281 			if (gid == gidset[i])
    282 				break;
    283 		if (i >= ngroups && myuid != 0) {
    284 			warnx("%s (gid %d): permission denied", name, gid);
    285 			return;
    286 		}
    287 	}
    288 	showquotas(QUOTA_CLASS_GROUP, gid, name);
    289 }
    290 
    291 /*
    292  * Print out quotas for a specified group name.
    293  */
    294 static void
    295 showgrpname(const char *name)
    296 {
    297 	struct group *grp = getgrnam(name);
    298 	int ngroups;
    299 	gid_t mygid, gidset[NGROUPS];
    300 	int i;
    301 
    302 	if (grp == NULL) {
    303 		warnx("%s: unknown group", name);
    304 		return;
    305 	}
    306 	mygid = getgid();
    307 	ngroups = getgroups(NGROUPS, gidset);
    308 	if (ngroups < 0) {
    309 		warn("getgroups");
    310 		return;
    311 	}
    312 	if (grp->gr_gid != mygid) {
    313 		for (i = 0; i < ngroups; i++)
    314 			if (grp->gr_gid == gidset[i])
    315 				break;
    316 		if (i >= ngroups && myuid != 0) {
    317 			warnx("%s (gid %d): permission denied",
    318 			    name, grp->gr_gid);
    319 			return;
    320 		}
    321 	}
    322 	showquotas(QUOTA_CLASS_GROUP, grp->gr_gid, name);
    323 }
    324 
    325 static void
    326 showquotas(int type, id_t id, const char *idname)
    327 {
    328 	struct quotause *qup;
    329 	struct quotause *quplist;
    330 
    331 	needheading = 1;
    332 
    333 	quplist = getprivs(id, type);
    334 	for (qup = quplist; qup; qup = qup->next) {
    335 		showonequota(type, id, idname, qup);
    336 	}
    337 	if (!qflag) {
    338 		/* In case nothing printed, issue a header saying "none" */
    339 		heading(type, id, idname, "none");
    340 	}
    341 }
    342 
    343 static void
    344 showonequota(int type, id_t id, const char *idname, struct quotause *qup)
    345 {
    346 	static const int isbytes[QUOTA_NLIMITS] = {
    347 		[QUOTA_LIMIT_BLOCK] = 1,
    348 		[QUOTA_LIMIT_FILE] = 0,
    349 	};
    350 	static time_t now;
    351 	struct quotaval *qvs;
    352 	unsigned numqvs, i;
    353 	const char *msg;
    354 	int isquota2;
    355 
    356 	qvs = qup->qv;
    357 	numqvs = QUOTA_NLIMITS;
    358 
    359 	if (now == 0) {
    360 		time(&now);
    361 	}
    362 
    363 	if (!vflag && unlimited(qvs, numqvs)) {
    364 		return;
    365 	}
    366 
    367 	if (qflag) {
    368 		for (i=0; i<numqvs; i++) {
    369 			msg = getovermsg(&qvs[i], ufs_quota_limit_names[i],
    370 					 now);
    371 			if (msg != NULL) {
    372 				heading(type, id, idname, "");
    373 				printf("\t%s %s\n", msg, qup->fsname);
    374 			}
    375 		}
    376 		return;
    377 	}
    378 
    379 	/*
    380 	 * XXX: anyover can in fact be true if anyusage is not true,
    381 	 * if there's a quota of zero set on some volume. This is
    382 	 * because the check we do checks if adding one more thing
    383 	 * will go over. That is reasonable, I suppose, but Arguably
    384 	 * the resulting behavior with usage 0 is a bug. (Also, what
    385 	 * reason do we have to believe that the reported grace expire
    386 	 * time is valid if we aren't in fact over yet?)
    387 	 */
    388 
    389 	if (vflag || dflag || anyover(qvs, numqvs, now) ||
    390 	    anyusage(qvs, numqvs)) {
    391 		heading(type, id, idname, "");
    392 		if (strlen(qup->fsname) > 4) {
    393 			printf("%s\n", qup->fsname);
    394 			printf("%12s", "");
    395 		} else {
    396 			printf("%12s", qup->fsname);
    397 		}
    398 
    399 		isquota2 = (qup->flags & QUOTA2) != 0;
    400 
    401 		for (i=0; i<numqvs; i++) {
    402 			printqv(&qvs[i], isquota2, isbytes[i], now);
    403 		}
    404 		printf("\n");
    405 	}
    406 }
    407 
    408 static void
    409 heading(int type, id_t id, const char *idname, const char *tag)
    410 {
    411 	if (needheading == 0)
    412 		return;
    413 	needheading = 0;
    414 
    415 	if (dflag)
    416 		printf("Default %s disk quotas: %s\n",
    417 		    ufs_quota_class_names[type], tag);
    418 	else
    419 		printf("Disk quotas for %s %s (%cid %u): %s\n",
    420 		    ufs_quota_class_names[type], idname,
    421 		    *ufs_quota_class_names[type], id, tag);
    422 
    423 	if (!qflag && tag[0] == '\0') {
    424 		printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n"
    425 		    , "Filesystem"
    426 		    , "blocks"
    427 		    , "quota"
    428 		    , "limit"
    429 		    , "grace"
    430 		    , "files"
    431 		    , "quota"
    432 		    , "limit"
    433 		    , "grace"
    434 		);
    435 	}
    436 }
    437 
    438 static void
    439 printqv(struct quotaval *qv, int isquota2, int isbytes, time_t now)
    440 {
    441 	char buf[20];
    442 	const char *str;
    443 	int intprtflags, over, width;
    444 
    445 	/*
    446 	 * The assorted finagling of width is to match the previous
    447 	 * open-coded formatting for exactly two quota object types,
    448 	 * which was chosen to make the default report fit in 80
    449 	 * columns.
    450 	 */
    451 
    452 	width = isbytes ? 9 : 8;
    453 	intprtflags = isbytes ? HN_B : 0;
    454 	over = isover(qv, now);
    455 
    456 	str = intprt(buf, width, qv->qv_usage, intprtflags, hflag);
    457 	printf("%*s", width, str);
    458 
    459 	printf("%c", over ? '*' : ' ');
    460 
    461 	str = intprt(buf, width, qv->qv_softlimit, intprtflags, hflag);
    462 	printf("%*s", width-1, str);
    463 
    464 	str = intprt(buf, width, qv->qv_hardlimit, intprtflags, hflag);
    465 	printf("%*s", width, str);
    466 
    467 	if (over) {
    468 		str = timeprt(buf, 9, now, qv->qv_expiretime);
    469 	} else if (isquota2 && vflag) {
    470 		str = timeprt(buf, 9, 0, qv->qv_grace);
    471 	} else {
    472 		str = "";
    473 	}
    474 	printf("%8s", str);
    475 }
    476 
    477 /*
    478  * Collect the requested quota information.
    479  */
    480 static struct quotause *
    481 getprivs(id_t id, int quotatype)
    482 {
    483 	struct quotause *qup, *quptail;
    484 	struct quotause *quphead;
    485 	struct statvfs *fst;
    486 	int nfst, i;
    487 	int8_t version;
    488 
    489 	qup = quphead = quptail = NULL;
    490 
    491 	nfst = getmntinfo(&fst, MNT_WAIT);
    492 	if (nfst == 0)
    493 		errx(2, "no filesystems mounted!");
    494 	for (i = 0; i < nfst; i++) {
    495 		if (qup == NULL) {
    496 			if ((qup = malloc(sizeof *qup)) == NULL)
    497 				err(1, "out of memory");
    498 		}
    499 		if (strncmp(fst[i].f_fstypename, "nfs",
    500 		    sizeof(fst[i].f_fstypename)) == 0) {
    501 			version = 0;
    502 			if (getnfsquota(fst[i].f_mntfromname,
    503 			    qup->qv, id, ufs_quota_class_names[quotatype]) != 1)
    504 				continue;
    505 		} else if ((fst[i].f_flag & ST_QUOTA) != 0) {
    506 			if (getvfsquota(fst[i].f_mntonname, qup->qv, &version,
    507 			    id, quotatype, dflag, Dflag) != 1)
    508 				continue;
    509 		} else
    510 			continue;
    511 		(void)strncpy(qup->fsname, fst[i].f_mntonname,
    512 		    sizeof(qup->fsname) - 1);
    513 		if (version == 2)
    514 			qup->flags |= QUOTA2;
    515 		if (quphead == NULL)
    516 			quphead = qup;
    517 		else
    518 			quptail->next = qup;
    519 		quptail = qup;
    520 		quptail->next = 0;
    521 		qup = NULL;
    522 	}
    523 	free(qup);
    524 	return quphead;
    525 }
    526 
    527 static int
    528 unlimited(struct quotaval *qvs, unsigned numqvs)
    529 {
    530 	unsigned i;
    531 
    532 	for (i=0; i<numqvs; i++) {
    533 		if (qvs[i].qv_softlimit != UQUAD_MAX ||
    534 		    qvs[i].qv_hardlimit != UQUAD_MAX) {
    535 			return 0;
    536 		}
    537 	}
    538 	return 1;
    539 }
    540 
    541 static int
    542 anyusage(struct quotaval *qvs, unsigned numqvs)
    543 {
    544 	unsigned i;
    545 
    546 	for (i=0; i<numqvs; i++) {
    547 		if (qvs[i].qv_usage > 0) {
    548 			return 1;
    549 		}
    550 	}
    551 	return 0;
    552 }
    553 
    554 static int
    555 anyover(struct quotaval *qvs, unsigned numqvs, time_t now)
    556 {
    557 	unsigned i;
    558 
    559 	for (i=0; i<numqvs; i++) {
    560 		if (isover(&qvs[i], now)) {
    561 			return 1;
    562 		}
    563 	}
    564 	return 0;
    565 }
    566 
    567 static int
    568 isover(struct quotaval *qv, time_t now)
    569 {
    570 	int ql_stat;
    571 
    572 	ql_stat = quota_check_limit(qv->qv_usage, 1,
    573 				    qv->qv_softlimit,
    574 				    qv->qv_hardlimit,
    575 				    qv->qv_expiretime, now);
    576 	switch(QL_STATUS(ql_stat)) {
    577 	    case QL_S_DENY_HARD:
    578 	    case QL_S_DENY_GRACE:
    579 	    case QL_S_ALLOW_SOFT:
    580 		return 1;
    581 	    default:
    582 		break;
    583 	}
    584 	return 0;
    585 }
    586 
    587 static const char *
    588 getovermsg(struct quotaval *qv, const char *what, time_t now)
    589 {
    590 	static char buf[64];
    591 	int ql_stat;
    592 
    593 	ql_stat = quota_check_limit(qv->qv_usage, 1,
    594 				    qv->qv_softlimit,
    595 				    qv->qv_hardlimit,
    596 				    qv->qv_expiretime, now);
    597 	switch(QL_STATUS(ql_stat)) {
    598 	    case QL_S_DENY_HARD:
    599 		snprintf(buf, sizeof(buf), "%c%s limit reached on",
    600 			 toupper((unsigned char)what[0]), what+1);
    601 		break;
    602 	    case QL_S_DENY_GRACE:
    603 		snprintf(buf, sizeof(buf), "Over %s quota on", what);
    604 		break;
    605 	    case QL_S_ALLOW_SOFT:
    606 		snprintf(buf, sizeof(buf), "In %s grace period on", what);
    607 		break;
    608 	    default:
    609 		return NULL;
    610 	}
    611 	return buf;
    612 }
    613