quota.c revision 1.43 1 /* $NetBSD: quota.c,v 1.43 2011/11/30 16:12:32 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.43 2011/11/30 16:12:32 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/quotaprop.h>
74 #include <quota/quota.h>
75
76 #include "printquota.h"
77 #include "getvfsquota.h"
78
79 struct quotause {
80 struct quotause *next;
81 long flags;
82 uid_t id;
83 struct quotaval *qvs;
84 unsigned numqvs;
85 char fsname[MAXPATHLEN + 1];
86 };
87 #define FOUND 0x01
88 #define QUOTA2 0x02
89
90 static int anyusage(struct quotaval *, unsigned);
91 static int anyover(struct quotaval *, unsigned, time_t);
92 static const char *getovermsg(struct quotaval *, const char *, time_t);
93 static struct quotause *getprivs(id_t, int);
94 static void heading(int, id_t, const char *, const char *);
95 static int isover(struct quotaval *qv, time_t now);
96 static void printqv(struct quotaval *, int, int, time_t);
97 static void showgid(gid_t);
98 static void showgrpname(const char *);
99 static void showonequota(int, id_t, const char *, struct quotause *);
100 static void showquotas(int, id_t, const char *);
101 static void showuid(uid_t);
102 static void showusrname(const char *);
103 static int unlimited(struct quotaval *qvs, unsigned numqvs);
104 static void usage(void) __dead;
105
106 static int qflag = 0;
107 static int vflag = 0;
108 static int hflag = 0;
109 static int dflag = 0;
110 static int Dflag = 0;
111 static uid_t myuid;
112 static int needheading;
113
114 int
115 main(int argc, char *argv[])
116 {
117 int ngroups;
118 gid_t mygid, gidset[NGROUPS];
119 int i, gflag = 0, uflag = 0;
120 int ch;
121
122 myuid = getuid();
123 while ((ch = getopt(argc, argv, "Ddhugvq")) != -1) {
124 switch(ch) {
125 case 'g':
126 gflag++;
127 break;
128 case 'u':
129 uflag++;
130 break;
131 case 'v':
132 vflag++;
133 break;
134 case 'q':
135 qflag++;
136 break;
137 case 'h':
138 hflag++;
139 break;
140 case 'd':
141 dflag++;
142 break;
143 case 'D':
144 Dflag++;
145 break;
146 default:
147 usage();
148 }
149 }
150 argc -= optind;
151 argv += optind;
152 if (!uflag && !gflag)
153 uflag++;
154 if (dflag) {
155 #if 0
156 if (myuid != 0)
157 errx(1, "-d: permission denied");
158 #endif
159 if (uflag)
160 showquotas(QUOTA_CLASS_USER, 0, "");
161 if (gflag)
162 showquotas(QUOTA_CLASS_GROUP, 0, "");
163 return 0;
164 }
165 if (argc == 0) {
166 if (uflag)
167 showuid(myuid);
168 if (gflag) {
169 if (dflag)
170 showgid(0);
171 else {
172 mygid = getgid();
173 ngroups = getgroups(NGROUPS, gidset);
174 if (ngroups < 0)
175 err(1, "getgroups");
176 showgid(mygid);
177 for (i = 0; i < ngroups; i++)
178 if (gidset[i] != mygid)
179 showgid(gidset[i]);
180 }
181 }
182 return 0;
183 }
184 if (uflag && gflag)
185 usage();
186 if (uflag) {
187 for (; argc > 0; argc--, argv++) {
188 if (alldigits(*argv))
189 showuid((uid_t)atoi(*argv));
190 else
191 showusrname(*argv);
192 }
193 return 0;
194 }
195 if (gflag) {
196 for (; argc > 0; argc--, argv++) {
197 if (alldigits(*argv))
198 showgid((gid_t)atoi(*argv));
199 else
200 showgrpname(*argv);
201 }
202 return 0;
203 }
204 /* NOTREACHED */
205 return 0;
206 }
207
208 static void
209 usage(void)
210 {
211 const char *p = getprogname();
212 fprintf(stderr, "Usage: %s [-Dhguqv]\n"
213 "\t%s [-Dhqv] -u username ...\n"
214 "\t%s [-Dhqv] -g groupname ...\n"
215 "\t%s -d [-Dhguqv]\n", p, p, p, p);
216 exit(1);
217 }
218
219 /*
220 * Print out quotas for a specified user identifier.
221 */
222 static void
223 showuid(uid_t uid)
224 {
225 struct passwd *pwd = getpwuid(uid);
226 const char *name;
227
228 if (pwd == NULL)
229 name = "(no account)";
230 else
231 name = pwd->pw_name;
232 if (uid != myuid && myuid != 0) {
233 warnx("%s (uid %d): permission denied", name, uid);
234 return;
235 }
236 showquotas(QUOTA_CLASS_USER, uid, name);
237 }
238
239 /*
240 * Print out quotas for a specified user name.
241 */
242 static void
243 showusrname(const char *name)
244 {
245 struct passwd *pwd = getpwnam(name);
246
247 if (pwd == NULL) {
248 warnx("%s: unknown user", name);
249 return;
250 }
251 if (pwd->pw_uid != myuid && myuid != 0) {
252 warnx("%s (uid %d): permission denied", name, pwd->pw_uid);
253 return;
254 }
255 showquotas(QUOTA_CLASS_USER, pwd->pw_uid, name);
256 }
257
258 /*
259 * Print out quotas for a specified group identifier.
260 */
261 static void
262 showgid(gid_t gid)
263 {
264 struct group *grp = getgrgid(gid);
265 int ngroups;
266 gid_t mygid, gidset[NGROUPS];
267 int i;
268 const char *name;
269
270 if (grp == NULL)
271 name = "(no entry)";
272 else
273 name = grp->gr_name;
274 mygid = getgid();
275 ngroups = getgroups(NGROUPS, gidset);
276 if (ngroups < 0) {
277 warn("getgroups");
278 return;
279 }
280 if (gid != mygid) {
281 for (i = 0; i < ngroups; i++)
282 if (gid == gidset[i])
283 break;
284 if (i >= ngroups && myuid != 0) {
285 warnx("%s (gid %d): permission denied", name, gid);
286 return;
287 }
288 }
289 showquotas(QUOTA_CLASS_GROUP, gid, name);
290 }
291
292 /*
293 * Print out quotas for a specified group name.
294 */
295 static void
296 showgrpname(const char *name)
297 {
298 struct group *grp = getgrnam(name);
299 int ngroups;
300 gid_t mygid, gidset[NGROUPS];
301 int i;
302
303 if (grp == NULL) {
304 warnx("%s: unknown group", name);
305 return;
306 }
307 mygid = getgid();
308 ngroups = getgroups(NGROUPS, gidset);
309 if (ngroups < 0) {
310 warn("getgroups");
311 return;
312 }
313 if (grp->gr_gid != mygid) {
314 for (i = 0; i < ngroups; i++)
315 if (grp->gr_gid == gidset[i])
316 break;
317 if (i >= ngroups && myuid != 0) {
318 warnx("%s (gid %d): permission denied",
319 name, grp->gr_gid);
320 return;
321 }
322 }
323 showquotas(QUOTA_CLASS_GROUP, grp->gr_gid, name);
324 }
325
326 static void
327 showquotas(int type, id_t id, const char *idname)
328 {
329 struct quotause *qup;
330 struct quotause *quplist;
331
332 needheading = 1;
333
334 quplist = getprivs(id, type);
335 for (qup = quplist; qup; qup = qup->next) {
336 showonequota(type, id, idname, qup);
337 }
338 if (!qflag) {
339 /* In case nothing printed, issue a header saying "none" */
340 heading(type, id, idname, "none");
341 }
342 }
343
344 static void
345 showonequota(int type, id_t id, const char *idname, struct quotause *qup)
346 {
347 static const int isbytes[QUOTA_NLIMITS] = {
348 [QUOTA_LIMIT_BLOCK] = 1,
349 [QUOTA_LIMIT_FILE] = 0,
350 };
351 static time_t now;
352 struct quotaval *qvs;
353 unsigned numqvs, i;
354 const char *msg;
355 int isquota2;
356
357 qvs = qup->qvs;
358 numqvs = qup->numqvs;
359
360 if (now == 0) {
361 time(&now);
362 }
363
364 if (!vflag && unlimited(qvs, numqvs)) {
365 return;
366 }
367
368 if (qflag) {
369 for (i=0; i<numqvs; i++) {
370 msg = getovermsg(&qvs[i], ufs_quota_limit_names[i],
371 now);
372 if (msg != NULL) {
373 heading(type, id, idname, "");
374 printf("\t%s %s\n", msg, qup->fsname);
375 }
376 }
377 return;
378 }
379
380 /*
381 * XXX: anyover can in fact be true if anyusage is not true,
382 * if there's a quota of zero set on some volume. This is
383 * because the check we do checks if adding one more thing
384 * will go over. That is reasonable, I suppose, but Arguably
385 * the resulting behavior with usage 0 is a bug. (Also, what
386 * reason do we have to believe that the reported grace expire
387 * time is valid if we aren't in fact over yet?)
388 */
389
390 if (vflag || dflag || anyover(qvs, numqvs, now) ||
391 anyusage(qvs, numqvs)) {
392 heading(type, id, idname, "");
393 if (strlen(qup->fsname) > 4) {
394 printf("%s\n", qup->fsname);
395 printf("%12s", "");
396 } else {
397 printf("%12s", qup->fsname);
398 }
399
400 isquota2 = (qup->flags & QUOTA2) != 0;
401
402 for (i=0; i<numqvs; i++) {
403 printqv(&qvs[i], isquota2,
404 i >= QUOTA_NLIMITS ? 0 : isbytes[i], now);
405 }
406 printf("\n");
407 }
408 }
409
410 static void
411 heading(int type, id_t id, const char *idname, const char *tag)
412 {
413 if (needheading == 0)
414 return;
415 needheading = 0;
416
417 if (dflag)
418 printf("Default %s disk quotas: %s\n",
419 ufs_quota_class_names[type], tag);
420 else
421 printf("Disk quotas for %s %s (%cid %u): %s\n",
422 ufs_quota_class_names[type], idname,
423 *ufs_quota_class_names[type], 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 isquota2, 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 (isquota2 && vflag) {
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 quotatype)
484 {
485 struct quotause *qup, *quptail;
486 struct quotause *quphead;
487 struct statvfs *fst;
488 int nfst, i;
489 int8_t version;
490
491 qup = quphead = quptail = NULL;
492
493 nfst = getmntinfo(&fst, MNT_WAIT);
494 if (nfst == 0)
495 errx(2, "no filesystems mounted!");
496 for (i = 0; i < nfst; i++) {
497 if (qup == NULL) {
498 if ((qup = malloc(sizeof *qup)) == NULL)
499 err(1, "Out of memory");
500 }
501 if (strncmp(fst[i].f_fstypename, "nfs",
502 sizeof(fst[i].f_fstypename)) == 0) {
503 version = 0;
504 qup->numqvs = QUOTA_NLIMITS;
505 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0]));
506 if (qup->qvs == NULL) {
507 err(1, "Out of memory");
508 }
509 if (getnfsquota(fst[i].f_mntfromname,
510 qup->qvs, id, ufs_quota_class_names[quotatype]) != 1)
511 continue;
512 } else if ((fst[i].f_flag & ST_QUOTA) != 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 (getvfsquota(fst[i].f_mntonname, qup->qvs, &version,
519 id, quotatype, dflag, Dflag) != 1)
520 continue;
521 } else
522 continue;
523 (void)strncpy(qup->fsname, fst[i].f_mntonname,
524 sizeof(qup->fsname) - 1);
525 if (version == 2)
526 qup->flags |= QUOTA2;
527 if (quphead == NULL)
528 quphead = qup;
529 else
530 quptail->next = qup;
531 quptail = qup;
532 quptail->next = 0;
533 qup = NULL;
534 }
535 free(qup);
536 return quphead;
537 }
538
539 static int
540 unlimited(struct quotaval *qvs, unsigned numqvs)
541 {
542 unsigned i;
543
544 for (i=0; i<numqvs; i++) {
545 if (qvs[i].qv_softlimit != UQUAD_MAX ||
546 qvs[i].qv_hardlimit != UQUAD_MAX) {
547 return 0;
548 }
549 }
550 return 1;
551 }
552
553 static int
554 anyusage(struct quotaval *qvs, unsigned numqvs)
555 {
556 unsigned i;
557
558 for (i=0; i<numqvs; i++) {
559 if (qvs[i].qv_usage > 0) {
560 return 1;
561 }
562 }
563 return 0;
564 }
565
566 static int
567 anyover(struct quotaval *qvs, unsigned numqvs, time_t now)
568 {
569 unsigned i;
570
571 for (i=0; i<numqvs; i++) {
572 if (isover(&qvs[i], now)) {
573 return 1;
574 }
575 }
576 return 0;
577 }
578
579 static int
580 isover(struct quotaval *qv, time_t now)
581 {
582 int ql_stat;
583
584 ql_stat = quota_check_limit(qv->qv_usage, 1,
585 qv->qv_softlimit,
586 qv->qv_hardlimit,
587 qv->qv_expiretime, now);
588 switch(QL_STATUS(ql_stat)) {
589 case QL_S_DENY_HARD:
590 case QL_S_DENY_GRACE:
591 case QL_S_ALLOW_SOFT:
592 return 1;
593 default:
594 break;
595 }
596 return 0;
597 }
598
599 static const char *
600 getovermsg(struct quotaval *qv, const char *what, time_t now)
601 {
602 static char buf[64];
603 int ql_stat;
604
605 ql_stat = quota_check_limit(qv->qv_usage, 1,
606 qv->qv_softlimit,
607 qv->qv_hardlimit,
608 qv->qv_expiretime, now);
609 switch(QL_STATUS(ql_stat)) {
610 case QL_S_DENY_HARD:
611 snprintf(buf, sizeof(buf), "%c%s limit reached on",
612 toupper((unsigned char)what[0]), what+1);
613 break;
614 case QL_S_DENY_GRACE:
615 snprintf(buf, sizeof(buf), "Over %s quota on", what);
616 break;
617 case QL_S_ALLOW_SOFT:
618 snprintf(buf, sizeof(buf), "In %s grace period on", what);
619 break;
620 default:
621 return NULL;
622 }
623 return buf;
624 }
625