1/*	$NetBSD$	*/
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
43static char sccsid[] = "@(#)quota.c	8.4 (Berkeley) 4/28/95";
44#else
45__RCSID("$NetBSD$");
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
77struct 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
86static int	anyusage(struct quotaval *, unsigned);
87static int	anyover(struct quotaval *, unsigned, time_t);
88static const char *getovermsg(struct quotaval *, const char *, time_t);
89static struct quotause	*getprivs(id_t, int);
90static void	heading(int, const char *, id_t, const char *, const char *);
91static int	isover(struct quotaval *qv, time_t now);
92static void	printqv(struct quotaval *, int, time_t);
93static void	showgid(gid_t);
94static void	showgrpname(const char *);
95static void	showonequota(int, const char *, id_t, const char *,
96			     struct quotause *);
97static void	showquotas(int, const char *, id_t, const char *);
98static void	showuid(uid_t);
99static void	showusrname(const char *);
100static int	unlimited(struct quotaval *qvs, unsigned numqvs);
101static void	usage(void) __dead;
102
103static int	qflag = 0;
104static int	vflag = 0;
105static int	hflag = 0;
106static int	dflag = 0;
107static uid_t	myuid;
108static int needheading;
109
110int
111main(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
201static void
202usage(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 */
215static void
216showuid(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 */
235static void
236showusrname(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 */
254static void
255showgid(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 */
288static void
289showgrpname(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
319static void
320showquotas(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
337static void
338showonequota(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
407static void
408heading(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
436static void
437printqv(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 */
478static struct quotause *
479getprivs(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#if 0
506		if (strncmp(fst[i].f_fstypename, "nfs",
507		    sizeof(fst[i].f_fstypename)) == 0) {
508			version = 0;
509			qup->numqvs = QUOTA_NLIMITS;
510			qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
511			if (qup->qvs == NULL) {
512				err(1, "Out of memory");
513			}
514			if (getnfsquota(fst[i].f_mntfromname,
515			    qup->qvs, id, ufs_quota_class_names[idtype]) != 1)
516				continue;
517		} else if ((fst[i].f_flag & ST_QUOTA) != 0) {
518			qup->numqvs = QUOTA_NLIMITS;
519			qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
520			if (qup->qvs == NULL) {
521				err(1, "Out of memory");
522			}
523			if (getvfsquota(fst[i].f_mntonname, qup->qvs, &version,
524			    id, idtype, dflag, 0) != 1)
525				continue;
526		} else
527			continue;
528#else
529		qup->numqvs = quota_getnumidtypes(qup->qh);
530		qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
531		if (qup->qvs == NULL) {
532			err(1, "Out of memory");
533		}
534		qk.qk_idtype = idtype;
535		if (dflag) {
536			qk.qk_id = QUOTA_DEFAULTID;
537		} else {
538			qk.qk_id = id;
539		}
540		for (j=0; j<qup->numqvs; j++) {
541			qk.qk_objtype = j;
542			if (quota_get(qup->qh, &qk, &qup->qvs[j]) < 0) {
543				if (errno != ENOENT && errno != ENODEV) {
544					warn("%s: quota_get (objtype %u)",
545					     fst[i].f_mntonname, j);
546				}
547				quotaval_clear(&qup->qvs[j]);
548			}
549		}
550#endif
551		(void)strlcpy(qup->fsname, fst[i].f_mntonname,
552		    sizeof(qup->fsname));
553		if (quphead == NULL)
554			quphead = qup;
555		else
556			quptail->next = qup;
557		quptail = qup;
558		quptail->next = 0;
559		qup = NULL;
560	}
561	free(qup);
562	return quphead;
563}
564
565static int
566unlimited(struct quotaval *qvs, unsigned numqvs)
567{
568	unsigned i;
569
570	for (i=0; i<numqvs; i++) {
571		if (qvs[i].qv_softlimit != QUOTA_NOLIMIT ||
572		    qvs[i].qv_hardlimit != QUOTA_NOLIMIT) {
573			return 0;
574		}
575	}
576	return 1;
577}
578
579static int
580anyusage(struct quotaval *qvs, unsigned numqvs)
581{
582	unsigned i;
583
584	for (i=0; i<numqvs; i++) {
585		if (qvs[i].qv_usage > 0) {
586			return 1;
587		}
588	}
589	return 0;
590}
591
592static int
593anyover(struct quotaval *qvs, unsigned numqvs, time_t now)
594{
595	unsigned i;
596
597	for (i=0; i<numqvs; i++) {
598		if (isover(&qvs[i], now)) {
599			return 1;
600		}
601	}
602	return 0;
603}
604
605static int
606isover(struct quotaval *qv, time_t now)
607{
608	return (qv->qv_usage >= qv->qv_hardlimit ||
609		qv->qv_usage >= qv->qv_softlimit);
610}
611
612static const char *
613getovermsg(struct quotaval *qv, const char *what, time_t now)
614{
615	static char buf[64];
616
617	if (qv->qv_usage >= qv->qv_hardlimit) {
618		snprintf(buf, sizeof(buf), "%c%s limit reached on",
619			 toupper((unsigned char)what[0]), what+1);
620		return buf;
621	}
622
623	if (qv->qv_usage < qv->qv_softlimit) {
624		/* Ok */
625		return NULL;
626	}
627
628	if (now > qv->qv_expiretime) {
629		snprintf(buf, sizeof(buf), "Over %s quota on", what);
630		return buf;
631	}
632
633	snprintf(buf, sizeof(buf), "In %s grace period on", what);
634	return buf;
635}
636