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