quota.c revision 1.42 1 /* $NetBSD: quota.c,v 1.42 2011/11/30 16:09:29 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.42 2011/11/30 16:09:29 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 qv[QUOTA_NLIMITS];
84 char fsname[MAXPATHLEN + 1];
85 };
86 #define FOUND 0x01
87 #define QUOTA2 0x02
88
89 static int anyusage(struct quotaval *, unsigned);
90 static int anyover(struct quotaval *, unsigned, time_t);
91 static const char *getovermsg(struct quotaval *, const char *, time_t);
92 static struct quotause *getprivs(id_t, int);
93 static void heading(int, id_t, const char *, const char *);
94 static int isover(struct quotaval *qv, time_t now);
95 static void printqv(struct quotaval *, int, int, time_t);
96 static void showgid(gid_t);
97 static void showgrpname(const char *);
98 static void showonequota(int, id_t, const char *, struct quotause *);
99 static void showquotas(int, id_t, const char *);
100 static void showuid(uid_t);
101 static void showusrname(const char *);
102 static int unlimited(struct quotaval *qvs, unsigned numqvs);
103 static void usage(void) __dead;
104
105 static int qflag = 0;
106 static int vflag = 0;
107 static int hflag = 0;
108 static int dflag = 0;
109 static int Dflag = 0;
110 static uid_t myuid;
111 static int needheading;
112
113 int
114 main(int argc, char *argv[])
115 {
116 int ngroups;
117 gid_t mygid, gidset[NGROUPS];
118 int i, gflag = 0, uflag = 0;
119 int ch;
120
121 myuid = getuid();
122 while ((ch = getopt(argc, argv, "Ddhugvq")) != -1) {
123 switch(ch) {
124 case 'g':
125 gflag++;
126 break;
127 case 'u':
128 uflag++;
129 break;
130 case 'v':
131 vflag++;
132 break;
133 case 'q':
134 qflag++;
135 break;
136 case 'h':
137 hflag++;
138 break;
139 case 'd':
140 dflag++;
141 break;
142 case 'D':
143 Dflag++;
144 break;
145 default:
146 usage();
147 }
148 }
149 argc -= optind;
150 argv += optind;
151 if (!uflag && !gflag)
152 uflag++;
153 if (dflag) {
154 #if 0
155 if (myuid != 0)
156 errx(1, "-d: permission denied");
157 #endif
158 if (uflag)
159 showquotas(QUOTA_CLASS_USER, 0, "");
160 if (gflag)
161 showquotas(QUOTA_CLASS_GROUP, 0, "");
162 return 0;
163 }
164 if (argc == 0) {
165 if (uflag)
166 showuid(myuid);
167 if (gflag) {
168 if (dflag)
169 showgid(0);
170 else {
171 mygid = getgid();
172 ngroups = getgroups(NGROUPS, gidset);
173 if (ngroups < 0)
174 err(1, "getgroups");
175 showgid(mygid);
176 for (i = 0; i < ngroups; i++)
177 if (gidset[i] != mygid)
178 showgid(gidset[i]);
179 }
180 }
181 return 0;
182 }
183 if (uflag && gflag)
184 usage();
185 if (uflag) {
186 for (; argc > 0; argc--, argv++) {
187 if (alldigits(*argv))
188 showuid((uid_t)atoi(*argv));
189 else
190 showusrname(*argv);
191 }
192 return 0;
193 }
194 if (gflag) {
195 for (; argc > 0; argc--, argv++) {
196 if (alldigits(*argv))
197 showgid((gid_t)atoi(*argv));
198 else
199 showgrpname(*argv);
200 }
201 return 0;
202 }
203 /* NOTREACHED */
204 return 0;
205 }
206
207 static void
208 usage(void)
209 {
210 const char *p = getprogname();
211 fprintf(stderr, "Usage: %s [-Dhguqv]\n"
212 "\t%s [-Dhqv] -u username ...\n"
213 "\t%s [-Dhqv] -g groupname ...\n"
214 "\t%s -d [-Dhguqv]\n", p, p, p, p);
215 exit(1);
216 }
217
218 /*
219 * Print out quotas for a specified user identifier.
220 */
221 static void
222 showuid(uid_t uid)
223 {
224 struct passwd *pwd = getpwuid(uid);
225 const char *name;
226
227 if (pwd == NULL)
228 name = "(no account)";
229 else
230 name = pwd->pw_name;
231 if (uid != myuid && myuid != 0) {
232 warnx("%s (uid %d): permission denied", name, uid);
233 return;
234 }
235 showquotas(QUOTA_CLASS_USER, uid, name);
236 }
237
238 /*
239 * Print out quotas for a specified user name.
240 */
241 static void
242 showusrname(const char *name)
243 {
244 struct passwd *pwd = getpwnam(name);
245
246 if (pwd == NULL) {
247 warnx("%s: unknown user", name);
248 return;
249 }
250 if (pwd->pw_uid != myuid && myuid != 0) {
251 warnx("%s (uid %d): permission denied", name, pwd->pw_uid);
252 return;
253 }
254 showquotas(QUOTA_CLASS_USER, pwd->pw_uid, name);
255 }
256
257 /*
258 * Print out quotas for a specified group identifier.
259 */
260 static void
261 showgid(gid_t gid)
262 {
263 struct group *grp = getgrgid(gid);
264 int ngroups;
265 gid_t mygid, gidset[NGROUPS];
266 int i;
267 const char *name;
268
269 if (grp == NULL)
270 name = "(no entry)";
271 else
272 name = grp->gr_name;
273 mygid = getgid();
274 ngroups = getgroups(NGROUPS, gidset);
275 if (ngroups < 0) {
276 warn("getgroups");
277 return;
278 }
279 if (gid != mygid) {
280 for (i = 0; i < ngroups; i++)
281 if (gid == gidset[i])
282 break;
283 if (i >= ngroups && myuid != 0) {
284 warnx("%s (gid %d): permission denied", name, gid);
285 return;
286 }
287 }
288 showquotas(QUOTA_CLASS_GROUP, gid, name);
289 }
290
291 /*
292 * Print out quotas for a specified group name.
293 */
294 static void
295 showgrpname(const char *name)
296 {
297 struct group *grp = getgrnam(name);
298 int ngroups;
299 gid_t mygid, gidset[NGROUPS];
300 int i;
301
302 if (grp == NULL) {
303 warnx("%s: unknown group", name);
304 return;
305 }
306 mygid = getgid();
307 ngroups = getgroups(NGROUPS, gidset);
308 if (ngroups < 0) {
309 warn("getgroups");
310 return;
311 }
312 if (grp->gr_gid != mygid) {
313 for (i = 0; i < ngroups; i++)
314 if (grp->gr_gid == gidset[i])
315 break;
316 if (i >= ngroups && myuid != 0) {
317 warnx("%s (gid %d): permission denied",
318 name, grp->gr_gid);
319 return;
320 }
321 }
322 showquotas(QUOTA_CLASS_GROUP, grp->gr_gid, name);
323 }
324
325 static void
326 showquotas(int type, id_t id, const char *idname)
327 {
328 struct quotause *qup;
329 struct quotause *quplist;
330
331 needheading = 1;
332
333 quplist = getprivs(id, type);
334 for (qup = quplist; qup; qup = qup->next) {
335 showonequota(type, id, idname, qup);
336 }
337 if (!qflag) {
338 /* In case nothing printed, issue a header saying "none" */
339 heading(type, id, idname, "none");
340 }
341 }
342
343 static void
344 showonequota(int type, id_t id, const char *idname, struct quotause *qup)
345 {
346 static const int isbytes[QUOTA_NLIMITS] = {
347 [QUOTA_LIMIT_BLOCK] = 1,
348 [QUOTA_LIMIT_FILE] = 0,
349 };
350 static time_t now;
351 struct quotaval *qvs;
352 unsigned numqvs, i;
353 const char *msg;
354 int isquota2;
355
356 qvs = qup->qv;
357 numqvs = QUOTA_NLIMITS;
358
359 if (now == 0) {
360 time(&now);
361 }
362
363 if (!vflag && unlimited(qvs, numqvs)) {
364 return;
365 }
366
367 if (qflag) {
368 for (i=0; i<numqvs; i++) {
369 msg = getovermsg(&qvs[i], ufs_quota_limit_names[i],
370 now);
371 if (msg != NULL) {
372 heading(type, id, idname, "");
373 printf("\t%s %s\n", msg, qup->fsname);
374 }
375 }
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(type, 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 isquota2 = (qup->flags & QUOTA2) != 0;
400
401 for (i=0; i<numqvs; i++) {
402 printqv(&qvs[i], isquota2, isbytes[i], now);
403 }
404 printf("\n");
405 }
406 }
407
408 static void
409 heading(int type, id_t id, const char *idname, 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",
417 ufs_quota_class_names[type], tag);
418 else
419 printf("Disk quotas for %s %s (%cid %u): %s\n",
420 ufs_quota_class_names[type], idname,
421 *ufs_quota_class_names[type], id, tag);
422
423 if (!qflag && tag[0] == '\0') {
424 printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n"
425 , "Filesystem"
426 , "blocks"
427 , "quota"
428 , "limit"
429 , "grace"
430 , "files"
431 , "quota"
432 , "limit"
433 , "grace"
434 );
435 }
436 }
437
438 static void
439 printqv(struct quotaval *qv, int isquota2, int isbytes, time_t now)
440 {
441 char buf[20];
442 const char *str;
443 int intprtflags, over, width;
444
445 /*
446 * The assorted finagling of width is to match the previous
447 * open-coded formatting for exactly two quota object types,
448 * which was chosen to make the default report fit in 80
449 * columns.
450 */
451
452 width = isbytes ? 9 : 8;
453 intprtflags = isbytes ? HN_B : 0;
454 over = isover(qv, now);
455
456 str = intprt(buf, width, qv->qv_usage, intprtflags, hflag);
457 printf("%*s", width, str);
458
459 printf("%c", over ? '*' : ' ');
460
461 str = intprt(buf, width, qv->qv_softlimit, intprtflags, hflag);
462 printf("%*s", width-1, str);
463
464 str = intprt(buf, width, qv->qv_hardlimit, intprtflags, hflag);
465 printf("%*s", width, str);
466
467 if (over) {
468 str = timeprt(buf, 9, now, qv->qv_expiretime);
469 } else if (isquota2 && vflag) {
470 str = timeprt(buf, 9, 0, qv->qv_grace);
471 } else {
472 str = "";
473 }
474 printf("%8s", str);
475 }
476
477 /*
478 * Collect the requested quota information.
479 */
480 static struct quotause *
481 getprivs(id_t id, int quotatype)
482 {
483 struct quotause *qup, *quptail;
484 struct quotause *quphead;
485 struct statvfs *fst;
486 int nfst, i;
487 int8_t version;
488
489 qup = quphead = quptail = NULL;
490
491 nfst = getmntinfo(&fst, MNT_WAIT);
492 if (nfst == 0)
493 errx(2, "no filesystems mounted!");
494 for (i = 0; i < nfst; i++) {
495 if (qup == NULL) {
496 if ((qup = malloc(sizeof *qup)) == NULL)
497 err(1, "out of memory");
498 }
499 if (strncmp(fst[i].f_fstypename, "nfs",
500 sizeof(fst[i].f_fstypename)) == 0) {
501 version = 0;
502 if (getnfsquota(fst[i].f_mntfromname,
503 qup->qv, id, ufs_quota_class_names[quotatype]) != 1)
504 continue;
505 } else if ((fst[i].f_flag & ST_QUOTA) != 0) {
506 if (getvfsquota(fst[i].f_mntonname, qup->qv, &version,
507 id, quotatype, dflag, Dflag) != 1)
508 continue;
509 } else
510 continue;
511 (void)strncpy(qup->fsname, fst[i].f_mntonname,
512 sizeof(qup->fsname) - 1);
513 if (version == 2)
514 qup->flags |= QUOTA2;
515 if (quphead == NULL)
516 quphead = qup;
517 else
518 quptail->next = qup;
519 quptail = qup;
520 quptail->next = 0;
521 qup = NULL;
522 }
523 free(qup);
524 return quphead;
525 }
526
527 static int
528 unlimited(struct quotaval *qvs, unsigned numqvs)
529 {
530 unsigned i;
531
532 for (i=0; i<numqvs; i++) {
533 if (qvs[i].qv_softlimit != UQUAD_MAX ||
534 qvs[i].qv_hardlimit != UQUAD_MAX) {
535 return 0;
536 }
537 }
538 return 1;
539 }
540
541 static int
542 anyusage(struct quotaval *qvs, unsigned numqvs)
543 {
544 unsigned i;
545
546 for (i=0; i<numqvs; i++) {
547 if (qvs[i].qv_usage > 0) {
548 return 1;
549 }
550 }
551 return 0;
552 }
553
554 static int
555 anyover(struct quotaval *qvs, unsigned numqvs, time_t now)
556 {
557 unsigned i;
558
559 for (i=0; i<numqvs; i++) {
560 if (isover(&qvs[i], now)) {
561 return 1;
562 }
563 }
564 return 0;
565 }
566
567 static int
568 isover(struct quotaval *qv, time_t now)
569 {
570 int ql_stat;
571
572 ql_stat = quota_check_limit(qv->qv_usage, 1,
573 qv->qv_softlimit,
574 qv->qv_hardlimit,
575 qv->qv_expiretime, now);
576 switch(QL_STATUS(ql_stat)) {
577 case QL_S_DENY_HARD:
578 case QL_S_DENY_GRACE:
579 case QL_S_ALLOW_SOFT:
580 return 1;
581 default:
582 break;
583 }
584 return 0;
585 }
586
587 static const char *
588 getovermsg(struct quotaval *qv, const char *what, time_t now)
589 {
590 static char buf[64];
591 int ql_stat;
592
593 ql_stat = quota_check_limit(qv->qv_usage, 1,
594 qv->qv_softlimit,
595 qv->qv_hardlimit,
596 qv->qv_expiretime, now);
597 switch(QL_STATUS(ql_stat)) {
598 case QL_S_DENY_HARD:
599 snprintf(buf, sizeof(buf), "%c%s limit reached on",
600 toupper((unsigned char)what[0]), what+1);
601 break;
602 case QL_S_DENY_GRACE:
603 snprintf(buf, sizeof(buf), "Over %s quota on", what);
604 break;
605 case QL_S_ALLOW_SOFT:
606 snprintf(buf, sizeof(buf), "In %s grace period on", what);
607 break;
608 default:
609 return NULL;
610 }
611 return buf;
612 }
613