xref: /netbsd/usr.bin/quota/quota.c (revision e03ec6e9)
1 /*	$NetBSD: quota.c,v 1.36 2011/03/06 22:36:07 christos 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.36 2011/03/06 22:36:07 christos 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 <ctype.h>
60 #include <err.h>
61 #include <errno.h>
62 #include <fstab.h>
63 #include <grp.h>
64 #include <netdb.h>
65 #include <pwd.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <time.h>
70 #include <unistd.h>
71 
72 #include <ufs/ufs/quota2.h>
73 #include <ufs/ufs/quota1.h>
74 
75 #include <rpc/rpc.h>
76 #include <rpc/pmap_prot.h>
77 #include <rpcsvc/rquota.h>
78 
79 #include "printquota.h"
80 #include "quotautil.h"
81 #include "getvfsquota.h"
82 
83 struct quotause {
84 	struct	quotause *next;
85 	long	flags;
86 	struct	quota2_entry q2e;
87 	char	fsname[MAXPATHLEN + 1];
88 };
89 #define	FOUND	0x01
90 #define	QUOTA2	0x02
91 
92 static int	callaurpc(const char *, rpcprog_t, rpcvers_t, rpcproc_t,
93     xdrproc_t, void *, xdrproc_t, void *);
94 static int	getnfsquota(struct statvfs *, struct quotause *, uint32_t, int);
95 static struct quotause	*getprivs(uint32_t, int);
96 static void	heading(int, uint32_t, const char *, const char *);
97 static void	showgid(gid_t);
98 static void	showgrpname(const char *);
99 static void	showquotas(int, uint32_t, const char *);
100 static void	showuid(uid_t);
101 static void	showusrname(const char *);
102 static void	usage(void) __attribute__((__noreturn__));
103 
104 static int	qflag = 0;
105 static int	vflag = 0;
106 static int	hflag = 0;
107 static int	dflag = 0;
108 static int	Dflag = 0;
109 static uid_t	myuid;
110 
111 int
112 main(int argc, char *argv[])
113 {
114 	int ngroups;
115 	gid_t mygid, gidset[NGROUPS];
116 	int i, gflag = 0, uflag = 0;
117 	int ch;
118 
119 	myuid = getuid();
120 	while ((ch = getopt(argc, argv, "Ddhugvq")) != -1) {
121 		switch(ch) {
122 		case 'g':
123 			gflag++;
124 			break;
125 		case 'u':
126 			uflag++;
127 			break;
128 		case 'v':
129 			vflag++;
130 			break;
131 		case 'q':
132 			qflag++;
133 			break;
134 		case 'h':
135 			hflag++;
136 			break;
137 		case 'd':
138 			dflag++;
139 			break;
140 		case 'D':
141 			Dflag++;
142 			break;
143 		default:
144 			usage();
145 		}
146 	}
147 	argc -= optind;
148 	argv += optind;
149 	if (!uflag && !gflag)
150 		uflag++;
151 	if (dflag) {
152 #if 0
153 		if (myuid != 0)
154 			errx(1, "-d: permission denied");
155 #endif
156 		if (uflag)
157 			showquotas(USRQUOTA, 0, "");
158 		if (gflag)
159 			showquotas(GRPQUOTA, 0, "");
160 		return 0;
161 	}
162 	if (argc == 0) {
163 		if (uflag)
164 			showuid(myuid);
165 		if (gflag) {
166 			if (dflag)
167 				showgid(0);
168 			else {
169 				mygid = getgid();
170 				ngroups = getgroups(NGROUPS, gidset);
171 				if (ngroups < 0)
172 					err(1, "getgroups");
173 				showgid(mygid);
174 				for (i = 0; i < ngroups; i++)
175 					if (gidset[i] != mygid)
176 						showgid(gidset[i]);
177 			}
178 		}
179 		return 0;
180 	}
181 	if (uflag && gflag)
182 		usage();
183 	if (uflag) {
184 		for (; argc > 0; argc--, argv++) {
185 			if (alldigits(*argv))
186 				showuid((uid_t)atoi(*argv));
187 			else
188 				showusrname(*argv);
189 		}
190 		return 0;
191 	}
192 	if (gflag) {
193 		for (; argc > 0; argc--, argv++) {
194 			if (alldigits(*argv))
195 				showgid((gid_t)atoi(*argv));
196 			else
197 				showgrpname(*argv);
198 		}
199 		return 0;
200 	}
201 	/* NOTREACHED */
202 	return 0;
203 }
204 
205 static void
206 usage(void)
207 {
208 	const char *p = getprogname();
209 	fprintf(stderr, "Usage: %s [-Dhguqv]\n"
210 	    "\t%s [-Dhqv] -u username ...\n"
211 	    "\t%s [-Dhqv] -g groupname ...\n"
212 	    "\t%s -d [-Dhguqv]\n", p, p, p, p);
213 	exit(1);
214 }
215 
216 /*
217  * Print out quotas for a specified user identifier.
218  */
219 static void
220 showuid(uid_t uid)
221 {
222 	struct passwd *pwd = getpwuid(uid);
223 	const char *name;
224 
225 	if (pwd == NULL)
226 		name = "(no account)";
227 	else
228 		name = pwd->pw_name;
229 	if (uid != myuid && myuid != 0) {
230 		warnx("%s (uid %d): permission denied", name, uid);
231 		return;
232 	}
233 	showquotas(USRQUOTA, uid, name);
234 }
235 
236 /*
237  * Print out quotas for a specified user name.
238  */
239 static void
240 showusrname(const char *name)
241 {
242 	struct passwd *pwd = getpwnam(name);
243 
244 	if (pwd == NULL) {
245 		warnx("%s: unknown user", name);
246 		return;
247 	}
248 	if (pwd->pw_uid != myuid && myuid != 0) {
249 		warnx("%s (uid %d): permission denied", name, pwd->pw_uid);
250 		return;
251 	}
252 	showquotas(USRQUOTA, pwd->pw_uid, name);
253 }
254 
255 /*
256  * Print out quotas for a specified group identifier.
257  */
258 static void
259 showgid(gid_t gid)
260 {
261 	struct group *grp = getgrgid(gid);
262 	int ngroups;
263 	gid_t mygid, gidset[NGROUPS];
264 	int i;
265 	const char *name;
266 
267 	if (grp == NULL)
268 		name = "(no entry)";
269 	else
270 		name = grp->gr_name;
271 	mygid = getgid();
272 	ngroups = getgroups(NGROUPS, gidset);
273 	if (ngroups < 0) {
274 		warn("getgroups");
275 		return;
276 	}
277 	if (gid != mygid) {
278 		for (i = 0; i < ngroups; i++)
279 			if (gid == gidset[i])
280 				break;
281 		if (i >= ngroups && myuid != 0) {
282 			warnx("%s (gid %d): permission denied", name, gid);
283 			return;
284 		}
285 	}
286 	showquotas(GRPQUOTA, gid, name);
287 }
288 
289 /*
290  * Print out quotas for a specified group name.
291  */
292 static void
293 showgrpname(const char *name)
294 {
295 	struct group *grp = getgrnam(name);
296 	int ngroups;
297 	gid_t mygid, gidset[NGROUPS];
298 	int i;
299 
300 	if (grp == NULL) {
301 		warnx("%s: unknown group", name);
302 		return;
303 	}
304 	mygid = getgid();
305 	ngroups = getgroups(NGROUPS, gidset);
306 	if (ngroups < 0) {
307 		warn("getgroups");
308 		return;
309 	}
310 	if (grp->gr_gid != mygid) {
311 		for (i = 0; i < ngroups; i++)
312 			if (grp->gr_gid == gidset[i])
313 				break;
314 		if (i >= ngroups && myuid != 0) {
315 			warnx("%s (gid %d): permission denied",
316 			    name, grp->gr_gid);
317 			return;
318 		}
319 	}
320 	showquotas(GRPQUOTA, grp->gr_gid, name);
321 }
322 
323 static void
324 showquotas(int type, uint32_t id, const char *name)
325 {
326 	struct quotause *qup;
327 	struct quotause *quplist;
328 	const char *msgi, *msgb, *nam, *timemsg;
329 	int lines = 0;
330 	static time_t now;
331 	char b0[20], b1[20], b2[20], b3[20];
332 
333 	if (now == 0)
334 		time(&now);
335 	quplist = getprivs(id, type);
336 	for (qup = quplist; qup; qup = qup->next) {
337 		int ql_stat;
338 		struct quota2_val *q = qup->q2e.q2e_val;
339 		if (!vflag &&
340 		    q[QL_BLOCK].q2v_softlimit == UQUAD_MAX &&
341 		    q[QL_BLOCK].q2v_hardlimit == UQUAD_MAX &&
342 		    q[QL_FILE].q2v_softlimit == UQUAD_MAX &&
343 		    q[QL_FILE].q2v_hardlimit == UQUAD_MAX)
344 			continue;
345 		ql_stat = quota2_check_limit(&q[QL_FILE], 1, now);
346 		switch(QL_STATUS(ql_stat)) {
347 		case QL_S_DENY_HARD:
348 			msgi = "File limit reached on";
349 			break;
350 		case QL_S_DENY_GRACE:
351 			msgi = "Over file quota on";
352 			break;
353 		case QL_S_ALLOW_SOFT:
354 			msgi = "In file grace period on";
355 			break;
356 		default:
357 			msgi = NULL;
358 		}
359 		ql_stat = quota2_check_limit(&q[QL_BLOCK], 1, now);
360 		switch(QL_STATUS(ql_stat)) {
361 		case QL_S_DENY_HARD:
362 			msgb = "Block limit reached on";
363 			break;
364 		case QL_S_DENY_GRACE:
365 			msgb = "Over block quota on";
366 			break;
367 		case QL_S_ALLOW_SOFT:
368 			msgb = "In block grace period on";
369 			break;
370 		default:
371 			msgb = NULL;
372 		}
373 		if (qflag) {
374 			if ((msgi != NULL || msgb != NULL) &&
375 			    lines++ == 0)
376 				heading(type, id, name, "");
377 			if (msgi != NULL)
378 				printf("\t%s %s\n", msgi, qup->fsname);
379 			if (msgb != NULL)
380 				printf("\t%s %s\n", msgb, qup->fsname);
381 			continue;
382 		}
383 		if (vflag || dflag || msgi || msgb || q[QL_BLOCK].q2v_cur ||
384 		    q[QL_FILE].q2v_cur) {
385 			if (lines++ == 0)
386 				heading(type, id, name, "");
387 			nam = qup->fsname;
388 			if (strlen(qup->fsname) > 4) {
389 				printf("%s\n", qup->fsname);
390 				nam = "";
391 			}
392 			if (msgb)
393 				timemsg = timeprt(b0, 9, now,
394 				    q[QL_BLOCK].q2v_time);
395 			else if ((qup->flags & QUOTA2) != 0 && vflag)
396 				timemsg = timeprt(b0, 9, 0,
397 				    q[QL_BLOCK].q2v_grace);
398 			else
399 				timemsg = "";
400 
401 			printf("%12s%9s%c%8s%9s%8s",
402 			    nam,
403 			    intprt(b1, 9, q[QL_BLOCK].q2v_cur,
404 			    HN_B, hflag),
405 			    (msgb == NULL) ? ' ' : '*',
406 			    intprt(b2, 9, q[QL_BLOCK].q2v_softlimit,
407 			    HN_B, hflag),
408 			    intprt(b3, 9, q[QL_BLOCK].q2v_hardlimit,
409 			    HN_B, hflag),
410 			    timemsg);
411 
412 			if (msgi)
413 				timemsg = timeprt(b0, 9, now,
414 				    q[QL_FILE].q2v_time);
415 			else if ((qup->flags & QUOTA2) != 0 && vflag)
416 				timemsg = timeprt(b0, 9, 0,
417 				    q[QL_FILE].q2v_grace);
418 			else
419 				timemsg = "";
420 
421 			printf("%8s%c%7s%8s%8s\n",
422 			    intprt(b1, 8, q[QL_FILE].q2v_cur, 0, hflag),
423 			    (msgi == NULL) ? ' ' : '*',
424 			    intprt(b2, 8, q[QL_FILE].q2v_softlimit, 0, hflag),
425 			    intprt(b3, 8, q[QL_FILE].q2v_hardlimit, 0, hflag),
426 			    timemsg);
427 			continue;
428 		}
429 	}
430 	if (!qflag && lines == 0)
431 		heading(type, id, name, "none");
432 }
433 
434 static void
435 heading(int type, uint32_t id, const char *name, const char *tag)
436 {
437 	if (dflag)
438 		printf("Default %s disk quotas: %s\n",
439 		    qfextension[type], tag);
440 	else
441 		printf("Disk quotas for %s %s (%cid %u): %s\n",
442 		    qfextension[type], name, *qfextension[type],
443 		    id, tag);
444 
445 	if (!qflag && tag[0] == '\0') {
446 		printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n"
447 		    , "Filesystem"
448 		    , "blocks"
449 		    , "quota"
450 		    , "limit"
451 		    , "grace"
452 		    , "files"
453 		    , "quota"
454 		    , "limit"
455 		    , "grace"
456 		);
457 	}
458 }
459 
460 /*
461  * Collect the requested quota information.
462  */
463 static struct quotause *
464 getprivs(uint32_t id, int quotatype)
465 {
466 	struct quotause *qup, *quptail;
467 	struct quotause *quphead;
468 	struct statvfs *fst;
469 	int nfst, i;
470 	int8_t version;
471 
472 	qup = quphead = quptail = NULL;
473 
474 	nfst = getmntinfo(&fst, MNT_WAIT);
475 	if (nfst == 0)
476 		errx(2, "no filesystems mounted!");
477 	for (i = 0; i < nfst; i++) {
478 		if (qup == NULL) {
479 			if ((qup = malloc(sizeof *qup)) == NULL)
480 				err(1, "out of memory");
481 		}
482 		if (strncmp(fst[i].f_fstypename, "nfs",
483 		    sizeof(fst[i].f_fstypename)) == 0) {
484 			version = 0;
485 			if (getnfsquota(&fst[i], qup, id, quotatype) == 0)
486 				continue;
487 		} else if ((fst[i].f_flag & ST_QUOTA) != 0) {
488 			if (getvfsquota(fst[i].f_mntonname, &qup->q2e, &version,
489 			    id, quotatype, dflag, Dflag) == 0)
490 				continue;
491 		} else
492 			continue;
493 		(void)strncpy(qup->fsname, fst[i].f_mntonname,
494 		    sizeof(qup->fsname) - 1);
495 		if (version == 2)
496 			qup->flags |= QUOTA2;
497 		if (quphead == NULL)
498 			quphead = qup;
499 		else
500 			quptail->next = qup;
501 		quptail = qup;
502 		quptail->next = 0;
503 		qup = NULL;
504 	}
505 	free(qup);
506 	return quphead;
507 }
508 
509 static int
510 getnfsquota(struct statvfs *fst, struct quotause *qup,
511     uint32_t id, int quotatype)
512 {
513 	struct getquota_args gq_args;
514 	struct ext_getquota_args ext_gq_args;
515 	struct getquota_rslt gq_rslt;
516 	struct quota2_entry *q2e = &qup->q2e;
517 	struct dqblk dqblk;
518 	struct timeval tv;
519 	char *cp;
520 	int ret;
521 
522 	if (fst->f_flag & MNT_LOCAL)
523 		return 0;
524 
525 	/*
526 	 * must be some form of "hostname:/path"
527 	 */
528 	cp = strchr(fst->f_mntfromname, ':');
529 	if (cp == NULL) {
530 		warnx("cannot find hostname for %s", fst->f_mntfromname);
531 		return 0;
532 	}
533 
534 	*cp = '\0';
535 	if (*(cp+1) != '/') {
536 		*cp = ':';
537 		return 0;
538 	}
539 
540 	ext_gq_args.gqa_pathp = cp + 1;
541 	ext_gq_args.gqa_id = id;
542 	ext_gq_args.gqa_type =
543 	    (quotatype == USRQUOTA) ? RQUOTA_USRQUOTA : RQUOTA_GRPQUOTA;
544 	ret = callaurpc(fst->f_mntfromname, RQUOTAPROG, EXT_RQUOTAVERS,
545 	    RQUOTAPROC_GETQUOTA, xdr_ext_getquota_args, &ext_gq_args,
546 	    xdr_getquota_rslt, &gq_rslt);
547 	if (ret == RPC_PROGVERSMISMATCH) {
548 		if (quotatype != USRQUOTA) {
549 			*cp = ':';
550 			return 0;
551 		}
552 		/* try RQUOTAVERS */
553 		gq_args.gqa_pathp = cp + 1;
554 		gq_args.gqa_uid = id;
555 		ret = callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
556 		    RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args,
557 		    xdr_getquota_rslt, &gq_rslt);
558 	}
559 	if (ret != RPC_SUCCESS) {
560 		*cp = ':';
561 		return 0;
562 	}
563 
564 	switch (gq_rslt.status) {
565 	case Q_NOQUOTA:
566 		break;
567 	case Q_EPERM:
568 		warnx("quota permission error, host: %s", fst->f_mntfromname);
569 		break;
570 	case Q_OK:
571 		gettimeofday(&tv, NULL);
572 
573 		/* blocks*/
574 		q2e->q2e_val[QL_BLOCK].q2v_hardlimit =
575 		dqblk.dqb_bhardlimit =
576 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
577 		    (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
578 		dqblk.dqb_bsoftlimit =
579 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
580 		    (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
581 		dqblk.dqb_curblocks =
582 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
583 		    (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
584 
585 		/* inodes */
586 		dqblk.dqb_ihardlimit =
587 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
588 		dqblk.dqb_isoftlimit =
589 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
590 		dqblk.dqb_curinodes =
591 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
592 
593 		/* grace times */
594 		dqblk.dqb_btime = (int)(tv.tv_sec +
595 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft);
596 		dqblk.dqb_itime = (int)(tv.tv_sec +
597 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft);
598 		dqblk2q2e(&dqblk, q2e);
599 		*cp = ':';
600 		return 1;
601 	default:
602 		warnx("bad rpc result, host: %s", fst->f_mntfromname);
603 		break;
604 	}
605 	*cp = ':';
606 	return 0;
607 }
608 
609 static int
610 callaurpc(const char *host, rpcprog_t prognum, rpcvers_t versnum,
611     rpcproc_t procnum, xdrproc_t inproc, void *in, xdrproc_t outproc, void *out)
612 {
613 	struct sockaddr_in server_addr;
614 	enum clnt_stat clnt_stat;
615 	struct hostent *hp;
616 	struct timeval timeout, tottimeout;
617 
618 	CLIENT *client = NULL;
619 	int sock = RPC_ANYSOCK;
620 
621 	if ((hp = gethostbyname(host)) == NULL)
622 		return (int) RPC_UNKNOWNHOST;
623 	timeout.tv_usec = 0;
624 	timeout.tv_sec = 6;
625 	memmove(&server_addr.sin_addr, hp->h_addr, hp->h_length);
626 	server_addr.sin_family = AF_INET;
627 	server_addr.sin_port =  0;
628 
629 	if ((client = clntudp_create(&server_addr, prognum,
630 	    versnum, timeout, &sock)) == NULL)
631 		return (int) rpc_createerr.cf_stat;
632 
633 	client->cl_auth = authunix_create_default();
634 	tottimeout.tv_sec = 25;
635 	tottimeout.tv_usec = 0;
636 	clnt_stat = clnt_call(client, procnum, inproc, in,
637 	    outproc, out, tottimeout);
638 
639 	return (int) clnt_stat;
640 }
641