xref: /netbsd/usr.bin/quota/quota.c (revision be02e2e5)
1 /*	$NetBSD: quota.c,v 1.50 2014/07/13 01:46:04 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.50 2014/07/13 01:46:04 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.h>
74 
75 #include "printquota.h"
76 
77 struct quotause {
78 	struct	quotause *next;
79 	uid_t	id;
80 	struct	quotaval *qvs;
81 	unsigned numqvs;
82 	char	fsname[MAXPATHLEN + 1];
83 	struct	quotahandle *qh;
84 };
85 
86 static int	anyusage(struct quotaval *, unsigned);
87 static int	anyover(struct quotaval *, unsigned, time_t);
88 static const char *getovermsg(struct quotaval *, const char *, time_t);
89 static struct quotause	*getprivs(id_t, int);
90 static void	heading(int, const char *, id_t, const char *, const char *);
91 static int	isover(struct quotaval *qv, time_t now);
92 static void	printqv(struct quotaval *, int, time_t);
93 static void	showgid(gid_t);
94 static void	showgrpname(const char *);
95 static void	showonequota(int, const char *, id_t, const char *,
96 			     struct quotause *);
97 static void	showquotas(int, const char *, id_t, const char *);
98 static void	showuid(uid_t);
99 static void	showusrname(const char *);
100 static int	unlimited(struct quotaval *qvs, unsigned numqvs);
101 static void	usage(void) __dead;
102 
103 static int	qflag = 0;
104 static int	vflag = 0;
105 static int	hflag = 0;
106 static int	dflag = 0;
107 static uid_t	myuid;
108 static int needheading;
109 
110 int
main(int argc,char * argv[])111 main(int argc, char *argv[])
112 {
113 	int ngroups;
114 	gid_t mygid, gidset[NGROUPS];
115 	int i, gflag = 0, uflag = 0;
116 	int ch;
117 
118 	myuid = getuid();
119 	while ((ch = getopt(argc, argv, "dhugvq")) != -1) {
120 		switch(ch) {
121 		case 'g':
122 			gflag++;
123 			break;
124 		case 'u':
125 			uflag++;
126 			break;
127 		case 'v':
128 			vflag++;
129 			break;
130 		case 'q':
131 			qflag++;
132 			break;
133 		case 'h':
134 			hflag++;
135 			break;
136 		case 'd':
137 			dflag++;
138 			break;
139 		default:
140 			usage();
141 		}
142 	}
143 	argc -= optind;
144 	argv += optind;
145 	if (!uflag && !gflag)
146 		uflag++;
147 	if (dflag) {
148 #if 0
149 		if (myuid != 0)
150 			errx(1, "-d: permission denied");
151 #endif
152 		if (uflag)
153 			showquotas(QUOTA_IDTYPE_USER, "user", 0, "");
154 		if (gflag)
155 			showquotas(QUOTA_IDTYPE_GROUP, "group", 0, "");
156 		return 0;
157 	}
158 	if (argc == 0) {
159 		if (uflag)
160 			showuid(myuid);
161 		if (gflag) {
162 			if (dflag)
163 				showgid(0);
164 			else {
165 				mygid = getgid();
166 				ngroups = getgroups(NGROUPS, gidset);
167 				if (ngroups < 0)
168 					err(1, "getgroups");
169 				showgid(mygid);
170 				for (i = 0; i < ngroups; i++)
171 					if (gidset[i] != mygid)
172 						showgid(gidset[i]);
173 			}
174 		}
175 		return 0;
176 	}
177 	if (uflag && gflag)
178 		usage();
179 	if (uflag) {
180 		for (; argc > 0; argc--, argv++) {
181 			if (alldigits(*argv))
182 				showuid((uid_t)atoi(*argv));
183 			else
184 				showusrname(*argv);
185 		}
186 		return 0;
187 	}
188 	if (gflag) {
189 		for (; argc > 0; argc--, argv++) {
190 			if (alldigits(*argv))
191 				showgid((gid_t)atoi(*argv));
192 			else
193 				showgrpname(*argv);
194 		}
195 		return 0;
196 	}
197 	/* NOTREACHED */
198 	return 0;
199 }
200 
201 static void
usage(void)202 usage(void)
203 {
204 	const char *p = getprogname();
205 	fprintf(stderr, "Usage: %s [-Dhguqv]\n"
206 	    "\t%s [-Dhqv] -u username ...\n"
207 	    "\t%s [-Dhqv] -g groupname ...\n"
208 	    "\t%s -d [-Dhguqv]\n", p, p, p, p);
209 	exit(1);
210 }
211 
212 /*
213  * Print out quotas for a specified user identifier.
214  */
215 static void
showuid(uid_t uid)216 showuid(uid_t uid)
217 {
218 	struct passwd *pwd = getpwuid(uid);
219 	const char *name;
220 
221 	if (pwd == NULL)
222 		name = "(no account)";
223 	else
224 		name = pwd->pw_name;
225 	if (uid != myuid && myuid != 0) {
226 		warnx("%s (uid %d): permission denied", name, uid);
227 		return;
228 	}
229 	showquotas(QUOTA_IDTYPE_USER, "user", uid, name);
230 }
231 
232 /*
233  * Print out quotas for a specified user name.
234  */
235 static void
showusrname(const char * name)236 showusrname(const char *name)
237 {
238 	struct passwd *pwd = getpwnam(name);
239 
240 	if (pwd == NULL) {
241 		warnx("%s: unknown user", name);
242 		return;
243 	}
244 	if (pwd->pw_uid != myuid && myuid != 0) {
245 		warnx("%s (uid %d): permission denied", name, pwd->pw_uid);
246 		return;
247 	}
248 	showquotas(QUOTA_IDTYPE_USER, "user", pwd->pw_uid, name);
249 }
250 
251 /*
252  * Print out quotas for a specified group identifier.
253  */
254 static void
showgid(gid_t gid)255 showgid(gid_t gid)
256 {
257 	struct group *grp = getgrgid(gid);
258 	int ngroups;
259 	gid_t mygid, gidset[NGROUPS];
260 	int i;
261 	const char *name;
262 
263 	if (grp == NULL)
264 		name = "(no entry)";
265 	else
266 		name = grp->gr_name;
267 	mygid = getgid();
268 	ngroups = getgroups(NGROUPS, gidset);
269 	if (ngroups < 0) {
270 		warn("getgroups");
271 		return;
272 	}
273 	if (gid != mygid) {
274 		for (i = 0; i < ngroups; i++)
275 			if (gid == gidset[i])
276 				break;
277 		if (i >= ngroups && myuid != 0) {
278 			warnx("%s (gid %d): permission denied", name, gid);
279 			return;
280 		}
281 	}
282 	showquotas(QUOTA_IDTYPE_GROUP, "group", gid, name);
283 }
284 
285 /*
286  * Print out quotas for a specified group name.
287  */
288 static void
showgrpname(const char * name)289 showgrpname(const char *name)
290 {
291 	struct group *grp = getgrnam(name);
292 	int ngroups;
293 	gid_t mygid, gidset[NGROUPS];
294 	int i;
295 
296 	if (grp == NULL) {
297 		warnx("%s: unknown group", name);
298 		return;
299 	}
300 	mygid = getgid();
301 	ngroups = getgroups(NGROUPS, gidset);
302 	if (ngroups < 0) {
303 		warn("getgroups");
304 		return;
305 	}
306 	if (grp->gr_gid != mygid) {
307 		for (i = 0; i < ngroups; i++)
308 			if (grp->gr_gid == gidset[i])
309 				break;
310 		if (i >= ngroups && myuid != 0) {
311 			warnx("%s (gid %d): permission denied",
312 			    name, grp->gr_gid);
313 			return;
314 		}
315 	}
316 	showquotas(QUOTA_IDTYPE_GROUP, "group", grp->gr_gid, name);
317 }
318 
319 static void
showquotas(int idtype,const char * idtypename,id_t id,const char * idname)320 showquotas(int idtype, const char *idtypename, id_t id, const char *idname)
321 {
322 	struct quotause *qup;
323 	struct quotause *quplist;
324 
325 	needheading = 1;
326 
327 	quplist = getprivs(id, idtype);
328 	for (qup = quplist; qup; qup = qup->next) {
329 		showonequota(idtype, idtypename, id, idname, qup);
330 	}
331 	if (!qflag) {
332 		/* In case nothing printed, issue a header saying "none" */
333 		heading(idtype, idtypename, id, idname, "none");
334 	}
335 }
336 
337 static void
showonequota(int idtype,const char * idtypename,id_t id,const char * idname,struct quotause * qup)338 showonequota(int idtype, const char *idtypename, id_t id, const char *idname,
339 	     struct quotause *qup)
340 {
341 	static time_t now;
342 	struct quotaval *qvs;
343 	unsigned numqvs, i;
344 	const char *msg;
345 
346 	qvs = qup->qvs;
347 	numqvs = qup->numqvs;
348 
349 	if (now == 0) {
350 		time(&now);
351 	}
352 
353 	if (!vflag && unlimited(qvs, numqvs)) {
354 		return;
355 	}
356 
357 	if (qflag) {
358 		for (i=0; i<numqvs; i++) {
359 			msg = getovermsg(&qvs[i],
360 					 quota_idtype_getname(qup->qh, i),
361 					 now);
362 			if (msg != NULL) {
363 				heading(idtype, idtypename, id, idname, "");
364 				printf("\t%s %s\n", msg, qup->fsname);
365 			}
366 		}
367 		return;
368 	}
369 
370 	/*
371 	 * XXX this behavior appears to be demanded by the ATF tests,
372 	 * although it seems to be at variance with the preexisting
373 	 * logic in quota.c.
374 	 */
375 	if (unlimited(qvs, numqvs) && !anyusage(qvs, numqvs)) {
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(idtype, idtypename, 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 		for (i=0; i<numqvs; i++) {
400 			printqv(&qvs[i],
401 				quota_objtype_isbytes(qup->qh, i), now);
402 		}
403 		printf("\n");
404 	}
405 }
406 
407 static void
heading(int idtype,const char * idtypename,id_t id,const char * idname,const char * tag)408 heading(int idtype, const char *idtypename, id_t id, const char *idname,
409 	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", idtypename, tag);
417 	else
418 		printf("Disk quotas for %s %s (%cid %u): %s\n",
419 		       idtypename, idname, idtypename[0], id, tag);
420 
421 	if (!qflag && tag[0] == '\0') {
422 		printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n"
423 		    , "Filesystem"
424 		    , "blocks"
425 		    , "quota"
426 		    , "limit"
427 		    , "grace"
428 		    , "files"
429 		    , "quota"
430 		    , "limit"
431 		    , "grace"
432 		);
433 	}
434 }
435 
436 static void
printqv(struct quotaval * qv,int isbytes,time_t now)437 printqv(struct quotaval *qv, int isbytes, time_t now)
438 {
439 	char buf[20];
440 	const char *str;
441 	int intprtflags, over, width;
442 
443 	/*
444 	 * The assorted finagling of width is to match the previous
445 	 * open-coded formatting for exactly two quota object types,
446 	 * which was chosen to make the default report fit in 80
447 	 * columns.
448 	 */
449 
450 	width = isbytes ? 9 : 8;
451 	intprtflags = isbytes ? HN_B : 0;
452 	over = isover(qv, now);
453 
454 	str = intprt(buf, width, qv->qv_usage, intprtflags, hflag);
455 	printf("%*s", width, str);
456 
457 	printf("%c", over ? '*' : ' ');
458 
459 	str = intprt(buf, width, qv->qv_softlimit, intprtflags, hflag);
460 	printf("%*s", width-1, str);
461 
462 	str = intprt(buf, width, qv->qv_hardlimit, intprtflags, hflag);
463 	printf("%*s", width, str);
464 
465 	if (over) {
466 		str = timeprt(buf, 9, now, qv->qv_expiretime);
467 	} else if (vflag && qv->qv_grace != QUOTA_NOTIME) {
468 		str = timeprt(buf, 9, 0, qv->qv_grace);
469 	} else {
470 		str = "";
471 	}
472 	printf("%8s", str);
473 }
474 
475 /*
476  * Collect the requested quota information.
477  */
478 static struct quotause *
getprivs(id_t id,int idtype)479 getprivs(id_t id, int idtype)
480 {
481 	struct quotause *qup, *quptail;
482 	struct quotause *quphead;
483 	struct statvfs *fst;
484 	struct quotakey qk;
485 	int nfst, i;
486 	unsigned j;
487 
488 	qup = quphead = quptail = NULL;
489 
490 	nfst = getmntinfo(&fst, MNT_WAIT);
491 	if (nfst == 0)
492 		errx(2, "no filesystems mounted!");
493 	for (i = 0; i < nfst; i++) {
494 		if (qup == NULL) {
495 			if ((qup = malloc(sizeof *qup)) == NULL)
496 				err(1, "Out of memory");
497 		}
498 		qup->qh = quota_open(fst[i].f_mntonname);
499 		if (qup->qh == NULL) {
500 			if (errno == EOPNOTSUPP || errno == ENXIO) {
501 				continue;
502 			}
503 			err(1, "%s: quota_open", fst[i].f_mntonname);
504 		}
505 		qup->numqvs = quota_getnumidtypes(qup->qh);
506 		qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
507 		if (qup->qvs == NULL) {
508 			err(1, "Out of memory");
509 		}
510 		qk.qk_idtype = idtype;
511 		if (dflag) {
512 			qk.qk_id = QUOTA_DEFAULTID;
513 		} else {
514 			qk.qk_id = id;
515 		}
516 		for (j=0; j<qup->numqvs; j++) {
517 			qk.qk_objtype = j;
518 			if (quota_get(qup->qh, &qk, &qup->qvs[j]) < 0) {
519 				if (errno != ENOENT && errno != ENODEV) {
520 					warn("%s: quota_get (objtype %u)",
521 					     fst[i].f_mntonname, j);
522 				}
523 				quotaval_clear(&qup->qvs[j]);
524 			}
525 		}
526 		(void)strlcpy(qup->fsname, fst[i].f_mntonname,
527 		    sizeof(qup->fsname));
528 		if (quphead == NULL)
529 			quphead = qup;
530 		else
531 			quptail->next = qup;
532 		quptail = qup;
533 		quptail->next = 0;
534 		qup = NULL;
535 	}
536 	free(qup);
537 	return quphead;
538 }
539 
540 static int
unlimited(struct quotaval * qvs,unsigned numqvs)541 unlimited(struct quotaval *qvs, unsigned numqvs)
542 {
543 	unsigned i;
544 
545 	for (i=0; i<numqvs; i++) {
546 		if (qvs[i].qv_softlimit != QUOTA_NOLIMIT ||
547 		    qvs[i].qv_hardlimit != QUOTA_NOLIMIT) {
548 			return 0;
549 		}
550 	}
551 	return 1;
552 }
553 
554 static int
anyusage(struct quotaval * qvs,unsigned numqvs)555 anyusage(struct quotaval *qvs, unsigned numqvs)
556 {
557 	unsigned i;
558 
559 	for (i=0; i<numqvs; i++) {
560 		if (qvs[i].qv_usage > 0) {
561 			return 1;
562 		}
563 	}
564 	return 0;
565 }
566 
567 static int
anyover(struct quotaval * qvs,unsigned numqvs,time_t now)568 anyover(struct quotaval *qvs, unsigned numqvs, time_t now)
569 {
570 	unsigned i;
571 
572 	for (i=0; i<numqvs; i++) {
573 		if (isover(&qvs[i], now)) {
574 			return 1;
575 		}
576 	}
577 	return 0;
578 }
579 
580 static int
isover(struct quotaval * qv,time_t now)581 isover(struct quotaval *qv, time_t now)
582 {
583 	return (qv->qv_usage >= qv->qv_hardlimit ||
584 		qv->qv_usage >= qv->qv_softlimit);
585 }
586 
587 static const char *
getovermsg(struct quotaval * qv,const char * what,time_t now)588 getovermsg(struct quotaval *qv, const char *what, time_t now)
589 {
590 	static char buf[64];
591 
592 	if (qv->qv_usage >= qv->qv_hardlimit) {
593 		snprintf(buf, sizeof(buf), "%c%s limit reached on",
594 			 toupper((unsigned char)what[0]), what+1);
595 		return buf;
596 	}
597 
598 	if (qv->qv_usage < qv->qv_softlimit) {
599 		/* Ok */
600 		return NULL;
601 	}
602 
603 	if (now > qv->qv_expiretime) {
604 		snprintf(buf, sizeof(buf), "Over %s quota on", what);
605 		return buf;
606 	}
607 
608 	snprintf(buf, sizeof(buf), "In %s grace period on", what);
609 	return buf;
610 }
611