quota.c revision 1.35 1 /* $NetBSD: quota.c,v 1.35 2011/03/06 20:47:59 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.35 2011/03/06 20:47:59 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 <getvfsquota.h>
81
82 struct quotause {
83 struct quotause *next;
84 long flags;
85 struct quota2_entry q2e;
86 char fsname[MAXPATHLEN + 1];
87 };
88 #define FOUND 0x01
89 #define QUOTA2 0x02
90
91 static int alldigits(const char *);
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 if (!vflag &&
339 qup->q2e.q2e_val[QL_BLOCK].q2v_softlimit == UQUAD_MAX &&
340 qup->q2e.q2e_val[QL_BLOCK].q2v_hardlimit == UQUAD_MAX &&
341 qup->q2e.q2e_val[QL_FILE].q2v_softlimit == UQUAD_MAX &&
342 qup->q2e.q2e_val[QL_FILE].q2v_hardlimit == UQUAD_MAX)
343 continue;
344 ql_stat = quota2_check_limit(&qup->q2e.q2e_val[QL_FILE],
345 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(&qup->q2e.q2e_val[QL_BLOCK],
360 1, now);
361 switch(QL_STATUS(ql_stat)) {
362 case QL_S_DENY_HARD:
363 msgb = "Block limit reached on";
364 break;
365 case QL_S_DENY_GRACE:
366 msgb = "Over block quota on";
367 break;
368 case QL_S_ALLOW_SOFT:
369 msgb = "In block grace period on";
370 break;
371 default:
372 msgb = NULL;
373 }
374 if (qflag) {
375 if ((msgi != NULL || msgb != NULL) &&
376 lines++ == 0)
377 heading(type, id, name, "");
378 if (msgi != NULL)
379 printf("\t%s %s\n", msgi, qup->fsname);
380 if (msgb != NULL)
381 printf("\t%s %s\n", msgb, qup->fsname);
382 continue;
383 }
384 if (vflag || dflag || msgi || msgb ||
385 qup->q2e.q2e_val[QL_BLOCK].q2v_cur ||
386 qup->q2e.q2e_val[QL_FILE].q2v_cur) {
387 if (lines++ == 0)
388 heading(type, id, name, "");
389 nam = qup->fsname;
390 if (strlen(qup->fsname) > 4) {
391 printf("%s\n", qup->fsname);
392 nam = "";
393 }
394 if (msgb)
395 timemsg = timeprt(b0, 9, now,
396 qup->q2e.q2e_val[QL_BLOCK].q2v_time);
397 else if ((qup->flags & QUOTA2) != 0 && vflag)
398 timemsg = timeprt(b0, 9, 0,
399 qup->q2e.q2e_val[QL_BLOCK].q2v_grace);
400 else
401 timemsg = "";
402
403 printf("%12s%9s%c%8s%9s%8s",
404 nam,
405 intprt(b1, 9,
406 qup->q2e.q2e_val[QL_BLOCK].q2v_cur,
407 HN_B, hflag),
408 (msgb == NULL) ? ' ' : '*',
409 intprt(b2, 9,
410 qup->q2e.q2e_val[QL_BLOCK].q2v_softlimit,
411 HN_B, hflag),
412 intprt(b3, 9,
413 qup->q2e.q2e_val[QL_BLOCK].q2v_hardlimit,
414 HN_B, hflag),
415 timemsg);
416
417 if (msgi)
418 timemsg = timeprt(b0, 9, now,
419 qup->q2e.q2e_val[QL_FILE].q2v_time);
420 else if ((qup->flags & QUOTA2) != 0 && vflag)
421 timemsg = timeprt(b0, 9, 0,
422 qup->q2e.q2e_val[QL_FILE].q2v_grace);
423 else
424 timemsg = "";
425
426 printf("%8s%c%7s%8s%8s\n",
427 intprt(b1, 8,
428 qup->q2e.q2e_val[QL_FILE].q2v_cur, 0, hflag),
429 (msgi == NULL) ? ' ' : '*',
430 intprt(b2, 8,
431 qup->q2e.q2e_val[QL_FILE].q2v_softlimit,
432 0, hflag),
433 intprt(b3, 8,
434 qup->q2e.q2e_val[QL_FILE].q2v_hardlimit,
435 0, hflag),
436 timemsg);
437 continue;
438 }
439 }
440 if (!qflag && lines == 0)
441 heading(type, id, name, "none");
442 }
443
444 static void
445 heading(int type, uint32_t id, const char *name, const char *tag)
446 {
447 if (dflag)
448 printf("Default %s disk quotas: %s\n",
449 qfextension[type], tag);
450 else
451 printf("Disk quotas for %s %s (%cid %u): %s\n",
452 qfextension[type], name, *qfextension[type],
453 id, tag);
454
455 if (!qflag && tag[0] == '\0') {
456 printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n"
457 , "Filesystem"
458 , "blocks"
459 , "quota"
460 , "limit"
461 , "grace"
462 , "files"
463 , "quota"
464 , "limit"
465 , "grace"
466 );
467 }
468 }
469
470 /*
471 * Collect the requested quota information.
472 */
473 static struct quotause *
474 getprivs(uint32_t id, int quotatype)
475 {
476 struct quotause *qup, *quptail;
477 struct quotause *quphead;
478 struct statvfs *fst;
479 int nfst, i;
480 int8_t version;
481
482 qup = quphead = quptail = NULL;
483
484 nfst = getmntinfo(&fst, MNT_WAIT);
485 if (nfst == 0)
486 errx(2, "no filesystems mounted!");
487 for (i = 0; i < nfst; i++) {
488 if (qup == NULL) {
489 if ((qup = malloc(sizeof *qup)) == NULL)
490 err(1, "out of memory");
491 }
492 if (strncmp(fst[i].f_fstypename, "nfs",
493 sizeof(fst[i].f_fstypename)) == 0) {
494 version = 0;
495 if (getnfsquota(&fst[i], qup, id, quotatype) == 0)
496 continue;
497 } else if ((fst[i].f_flag & ST_QUOTA) != 0) {
498 if (getvfsquota(fst[i].f_mntonname, &qup->q2e, &version,
499 id, quotatype, dflag, Dflag) == 0)
500 continue;
501 } else
502 continue;
503 (void)strncpy(qup->fsname, fst[i].f_mntonname,
504 sizeof(qup->fsname) - 1);
505 if (version == 2)
506 qup->flags |= QUOTA2;
507 if (quphead == NULL)
508 quphead = qup;
509 else
510 quptail->next = qup;
511 quptail = qup;
512 quptail->next = 0;
513 qup = NULL;
514 }
515 free(qup);
516 return quphead;
517 }
518
519 static int
520 getnfsquota(struct statvfs *fst, struct quotause *qup,
521 uint32_t id, int quotatype)
522 {
523 struct getquota_args gq_args;
524 struct ext_getquota_args ext_gq_args;
525 struct getquota_rslt gq_rslt;
526 struct quota2_entry *q2e = &qup->q2e;
527 struct dqblk dqblk;
528 struct timeval tv;
529 char *cp;
530 int ret;
531
532 if (fst->f_flag & MNT_LOCAL)
533 return 0;
534
535 /*
536 * must be some form of "hostname:/path"
537 */
538 cp = strchr(fst->f_mntfromname, ':');
539 if (cp == NULL) {
540 warnx("cannot find hostname for %s", fst->f_mntfromname);
541 return 0;
542 }
543
544 *cp = '\0';
545 if (*(cp+1) != '/') {
546 *cp = ':';
547 return 0;
548 }
549
550 ext_gq_args.gqa_pathp = cp + 1;
551 ext_gq_args.gqa_id = id;
552 ext_gq_args.gqa_type =
553 (quotatype == USRQUOTA) ? RQUOTA_USRQUOTA : RQUOTA_GRPQUOTA;
554 ret = callaurpc(fst->f_mntfromname, RQUOTAPROG, EXT_RQUOTAVERS,
555 RQUOTAPROC_GETQUOTA, xdr_ext_getquota_args, &ext_gq_args,
556 xdr_getquota_rslt, &gq_rslt);
557 if (ret == RPC_PROGVERSMISMATCH) {
558 if (quotatype != USRQUOTA) {
559 *cp = ':';
560 return 0;
561 }
562 /* try RQUOTAVERS */
563 gq_args.gqa_pathp = cp + 1;
564 gq_args.gqa_uid = id;
565 ret = callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
566 RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args,
567 xdr_getquota_rslt, &gq_rslt);
568 }
569 if (ret != RPC_SUCCESS) {
570 *cp = ':';
571 return 0;
572 }
573
574 switch (gq_rslt.status) {
575 case Q_NOQUOTA:
576 break;
577 case Q_EPERM:
578 warnx("quota permission error, host: %s", fst->f_mntfromname);
579 break;
580 case Q_OK:
581 gettimeofday(&tv, NULL);
582
583 /* blocks*/
584 q2e->q2e_val[QL_BLOCK].q2v_hardlimit =
585 dqblk.dqb_bhardlimit =
586 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
587 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
588 dqblk.dqb_bsoftlimit =
589 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
590 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
591 dqblk.dqb_curblocks =
592 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
593 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
594
595 /* inodes */
596 dqblk.dqb_ihardlimit =
597 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
598 dqblk.dqb_isoftlimit =
599 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
600 dqblk.dqb_curinodes =
601 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
602
603 /* grace times */
604 dqblk.dqb_btime = (int)(tv.tv_sec +
605 gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft);
606 dqblk.dqb_itime = (int)(tv.tv_sec +
607 gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft);
608 dqblk2q2e(&dqblk, q2e);
609 *cp = ':';
610 return 1;
611 default:
612 warnx("bad rpc result, host: %s", fst->f_mntfromname);
613 break;
614 }
615 *cp = ':';
616 return 0;
617 }
618
619 static int
620 callaurpc(const char *host, rpcprog_t prognum, rpcvers_t versnum,
621 rpcproc_t procnum, xdrproc_t inproc, void *in, xdrproc_t outproc, void *out)
622 {
623 struct sockaddr_in server_addr;
624 enum clnt_stat clnt_stat;
625 struct hostent *hp;
626 struct timeval timeout, tottimeout;
627
628 CLIENT *client = NULL;
629 int sock = RPC_ANYSOCK;
630
631 if ((hp = gethostbyname(host)) == NULL)
632 return (int) RPC_UNKNOWNHOST;
633 timeout.tv_usec = 0;
634 timeout.tv_sec = 6;
635 memmove(&server_addr.sin_addr, hp->h_addr, hp->h_length);
636 server_addr.sin_family = AF_INET;
637 server_addr.sin_port = 0;
638
639 if ((client = clntudp_create(&server_addr, prognum,
640 versnum, timeout, &sock)) == NULL)
641 return (int) rpc_createerr.cf_stat;
642
643 client->cl_auth = authunix_create_default();
644 tottimeout.tv_sec = 25;
645 tottimeout.tv_usec = 0;
646 clnt_stat = clnt_call(client, procnum, inproc, in,
647 outproc, out, tottimeout);
648
649 return (int) clnt_stat;
650 }
651
652 static int
653 alldigits(const char *s)
654 {
655 unsigned char c;
656
657 c = *s++;
658 do {
659 if (!isdigit(c))
660 return 0;
661 } while ((c = *s++) != 0);
662 return 1;
663 }
664