quota.c revision 1.37.4.1 1 /* $NetBSD: quota.c,v 1.37.4.1 2012/04/17 00:09:38 yamt 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.37.4.1 2012/04/17 00:09:38 yamt 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 int Dflag = 0;
108 static uid_t myuid;
109 static int needheading;
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(QUOTA_IDTYPE_USER, "user", 0, "");
158 if (gflag)
159 showquotas(QUOTA_IDTYPE_GROUP, "group", 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(QUOTA_IDTYPE_USER, "user", 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(QUOTA_IDTYPE_USER, "user", 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(QUOTA_IDTYPE_GROUP, "group", 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(QUOTA_IDTYPE_GROUP, "group", grp->gr_gid, name);
321 }
322
323 static void
324 showquotas(int idtype, const char *idtypename, id_t id, const char *idname)
325 {
326 struct quotause *qup;
327 struct quotause *quplist;
328
329 needheading = 1;
330
331 quplist = getprivs(id, idtype);
332 for (qup = quplist; qup; qup = qup->next) {
333 showonequota(idtype, idtypename, id, idname, qup);
334 }
335 if (!qflag) {
336 /* In case nothing printed, issue a header saying "none" */
337 heading(idtype, idtypename, id, idname, "none");
338 }
339 }
340
341 static void
342 showonequota(int idtype, const char *idtypename, id_t id, const char *idname,
343 struct quotause *qup)
344 {
345 static time_t now;
346 struct quotaval *qvs;
347 unsigned numqvs, i;
348 const char *msg;
349
350 qvs = qup->qvs;
351 numqvs = qup->numqvs;
352
353 if (now == 0) {
354 time(&now);
355 }
356
357 if (!vflag && unlimited(qvs, numqvs)) {
358 return;
359 }
360
361 if (qflag) {
362 for (i=0; i<numqvs; i++) {
363 msg = getovermsg(&qvs[i],
364 quota_idtype_getname(qup->qh, i),
365 now);
366 if (msg != NULL) {
367 heading(idtype, idtypename, id, idname, "");
368 printf("\t%s %s\n", msg, qup->fsname);
369 }
370 }
371 return;
372 }
373
374 /*
375 * XXX this behavior appears to be demanded by the ATF tests,
376 * although it seems to be at variance with the preexisting
377 * logic in quota.c.
378 */
379 if (unlimited(qvs, numqvs) && !anyusage(qvs, numqvs)) {
380 return;
381 }
382
383 /*
384 * XXX: anyover can in fact be true if anyusage is not true,
385 * if there's a quota of zero set on some volume. This is
386 * because the check we do checks if adding one more thing
387 * will go over. That is reasonable, I suppose, but arguably
388 * the resulting behavior with usage 0 is a bug. (Also, what
389 * reason do we have to believe that the reported grace expire
390 * time is valid if we aren't in fact over yet?)
391 */
392
393 if (vflag || dflag || anyover(qvs, numqvs, now) ||
394 anyusage(qvs, numqvs)) {
395 heading(idtype, idtypename, id, idname, "");
396 if (strlen(qup->fsname) > 4) {
397 printf("%s\n", qup->fsname);
398 printf("%12s", "");
399 } else {
400 printf("%12s", qup->fsname);
401 }
402
403 for (i=0; i<numqvs; i++) {
404 printqv(&qvs[i],
405 quota_objtype_isbytes(qup->qh, i), now);
406 }
407 printf("\n");
408 }
409 }
410
411 static void
412 heading(int idtype, const char *idtypename, id_t id, const char *idname,
413 const char *tag)
414 {
415 if (needheading == 0)
416 return;
417 needheading = 0;
418
419 if (dflag)
420 printf("Default %s disk quotas: %s\n", idtypename, tag);
421 else
422 printf("Disk quotas for %s %s (%cid %u): %s\n",
423 idtypename, idname, idtypename[0], id, tag);
424
425 if (!qflag && tag[0] == '\0') {
426 printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n"
427 , "Filesystem"
428 , "blocks"
429 , "quota"
430 , "limit"
431 , "grace"
432 , "files"
433 , "quota"
434 , "limit"
435 , "grace"
436 );
437 }
438 }
439
440 static void
441 printqv(struct quotaval *qv, int isbytes, time_t now)
442 {
443 char buf[20];
444 const char *str;
445 int intprtflags, over, width;
446
447 /*
448 * The assorted finagling of width is to match the previous
449 * open-coded formatting for exactly two quota object types,
450 * which was chosen to make the default report fit in 80
451 * columns.
452 */
453
454 width = isbytes ? 9 : 8;
455 intprtflags = isbytes ? HN_B : 0;
456 over = isover(qv, now);
457
458 str = intprt(buf, width, qv->qv_usage, intprtflags, hflag);
459 printf("%*s", width, str);
460
461 printf("%c", over ? '*' : ' ');
462
463 str = intprt(buf, width, qv->qv_softlimit, intprtflags, hflag);
464 printf("%*s", width-1, str);
465
466 str = intprt(buf, width, qv->qv_hardlimit, intprtflags, hflag);
467 printf("%*s", width, str);
468
469 if (over) {
470 str = timeprt(buf, 9, now, qv->qv_expiretime);
471 } else if (vflag && qv->qv_grace != QUOTA_NOTIME) {
472 str = timeprt(buf, 9, 0, qv->qv_grace);
473 } else {
474 str = "";
475 }
476 printf("%8s", str);
477 }
478
479 /*
480 * Collect the requested quota information.
481 */
482 static struct quotause *
483 getprivs(id_t id, int idtype)
484 {
485 struct quotause *qup, *quptail;
486 struct quotause *quphead;
487 struct statvfs *fst;
488 struct quotakey qk;
489 int nfst, i;
490 unsigned j;
491
492 qup = quphead = quptail = NULL;
493
494 nfst = getmntinfo(&fst, MNT_WAIT);
495 if (nfst == 0)
496 errx(2, "no filesystems mounted!");
497 for (i = 0; i < nfst; i++) {
498 if (qup == NULL) {
499 if ((qup = malloc(sizeof *qup)) == NULL)
500 err(1, "Out of memory");
501 }
502 qup->qh = quota_open(fst[i].f_mntonname);
503 if (qup->qh == NULL) {
504 if (errno == EOPNOTSUPP || errno == ENXIO) {
505 continue;
506 }
507 err(1, "%s: quota_open", fst[i].f_mntonname);
508 }
509 #if 0
510 if (strncmp(fst[i].f_fstypename, "nfs",
511 sizeof(fst[i].f_fstypename)) == 0) {
512 version = 0;
513 qup->numqvs = QUOTA_NLIMITS;
514 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
515 if (qup->qvs == NULL) {
516 err(1, "Out of memory");
517 }
518 if (getnfsquota(fst[i].f_mntfromname,
519 qup->qvs, id, ufs_quota_class_names[idtype]) != 1)
520 continue;
521 } else if ((fst[i].f_flag & ST_QUOTA) != 0) {
522 qup->numqvs = QUOTA_NLIMITS;
523 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
524 if (qup->qvs == NULL) {
525 err(1, "Out of memory");
526 }
527 if (getvfsquota(fst[i].f_mntonname, qup->qvs, &version,
528 id, idtype, dflag, Dflag) != 1)
529 continue;
530 } else
531 continue;
532 #else
533 qup->numqvs = quota_getnumidtypes(qup->qh);
534 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
535 if (qup->qvs == NULL) {
536 err(1, "Out of memory");
537 }
538 qk.qk_idtype = idtype;
539 if (dflag) {
540 qk.qk_id = QUOTA_DEFAULTID;
541 } else {
542 qk.qk_id = id;
543 }
544 for (j=0; j<qup->numqvs; j++) {
545 qk.qk_objtype = j;
546 if (quota_get(qup->qh, &qk, &qup->qvs[j]) < 0) {
547 if (errno != ENOENT && errno != ENODEV) {
548 warn("%s: quota_get (objtype %u)",
549 fst[i].f_mntonname, j);
550 }
551 quotaval_clear(&qup->qvs[j]);
552 }
553 }
554 #endif
555 (void)strlcpy(qup->fsname, fst[i].f_mntonname,
556 sizeof(qup->fsname));
557 if (quphead == NULL)
558 quphead = qup;
559 else
560 quptail->next = qup;
561 quptail = qup;
562 quptail->next = 0;
563 qup = NULL;
564 }
565 free(qup);
566 return quphead;
567 }
568
569 static int
570 unlimited(struct quotaval *qvs, unsigned numqvs)
571 {
572 unsigned i;
573
574 for (i=0; i<numqvs; i++) {
575 if (qvs[i].qv_softlimit != QUOTA_NOLIMIT ||
576 qvs[i].qv_hardlimit != QUOTA_NOLIMIT) {
577 return 0;
578 }
579 }
580 return 1;
581 }
582
583 static int
584 anyusage(struct quotaval *qvs, unsigned numqvs)
585 {
586 unsigned i;
587
588 for (i=0; i<numqvs; i++) {
589 if (qvs[i].qv_usage > 0) {
590 return 1;
591 }
592 }
593 return 0;
594 }
595
596 static int
597 anyover(struct quotaval *qvs, unsigned numqvs, time_t now)
598 {
599 unsigned i;
600
601 for (i=0; i<numqvs; i++) {
602 if (isover(&qvs[i], now)) {
603 return 1;
604 }
605 }
606 return 0;
607 }
608
609 static int
610 isover(struct quotaval *qv, time_t now)
611 {
612 return (qv->qv_usage >= qv->qv_hardlimit ||
613 qv->qv_usage >= qv->qv_softlimit);
614 }
615
616 static const char *
617 getovermsg(struct quotaval *qv, const char *what, time_t now)
618 {
619 static char buf[64];
620
621 if (qv->qv_usage >= qv->qv_hardlimit) {
622 snprintf(buf, sizeof(buf), "%c%s limit reached on",
623 toupper((unsigned char)what[0]), what+1);
624 return buf;
625 }
626
627 if (qv->qv_usage < qv->qv_softlimit) {
628 /* Ok */
629 return NULL;
630 }
631
632 if (now > qv->qv_expiretime) {
633 snprintf(buf, sizeof(buf), "Over %s quota on", what);
634 return buf;
635 }
636
637 snprintf(buf, sizeof(buf), "In %s grace period on", what);
638 return buf;
639 }
640