1 /* $NetBSD: repquota.c,v 1.45 2015/06/16 23:04:14 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[] = "@(#)repquota.c 8.2 (Berkeley) 11/22/94";
44 #else
45 __RCSID("$NetBSD: repquota.c,v 1.45 2015/06/16 23:04:14 christos Exp $");
46 #endif
47 #endif /* not lint */
48
49 /*
50 * Quota report
51 */
52 #include <sys/param.h>
53 #include <sys/stat.h>
54 #include <sys/types.h>
55 #include <sys/statvfs.h>
56
57 #include <errno.h>
58 #include <err.h>
59 #include <fstab.h>
60 #include <grp.h>
61 #include <pwd.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <unistd.h>
66
67 #include <quota.h>
68
69 #include "printquota.h"
70
71 /*
72 * XXX. Ideally we shouldn't compile either of these in, but it's a
73 * nontrivial rework to avoid it and it'll work ok for now.
74 */
75 #define REPQUOTA_NUMIDTYPES 2
76 #define REPQUOTA_NUMOBJTYPES 2
77
78 struct fileusage {
79 struct fileusage *fu_next;
80 struct quotaval fu_qv[REPQUOTA_NUMOBJTYPES];
81 uint32_t fu_id;
82 char fu_name[1];
83 /* actually bigger */
84 };
85
86 #define FUHASH 1024 /* must be power of two */
87 static struct fileusage *fuhead[REPQUOTA_NUMIDTYPES][FUHASH];
88
89 /* highest addid()'ed identifier per idtype */
90 static uint32_t highid[REPQUOTA_NUMIDTYPES];
91
92 int valid[REPQUOTA_NUMIDTYPES];
93
94 static struct quotaval defaultqv[REPQUOTA_NUMIDTYPES][REPQUOTA_NUMOBJTYPES];
95
96 static int vflag = 0; /* verbose */
97 static int aflag = 0; /* all file systems */
98 static int hflag = 0; /* humanize */
99 static int xflag = 0; /* export */
100
101 /*
102 * XXX this should go away and be replaced with a call to
103 * quota_idtype_getname(), but that needs a quotahandle and requires
104 * the same nontrivial rework as getting rid of REPQUOTA_NUMIDTYPES.
105 */
106 static const char *const repquota_idtype_names[REPQUOTA_NUMIDTYPES] = {
107 "user",
108 "group",
109 };
110
111 static struct fileusage *addid(uint32_t, int, const char *);
112 static struct fileusage *lookup(uint32_t, int);
113 static struct fileusage *qremove(uint32_t, int);
114 static int repquota(struct quotahandle *, int);
115 static void usage(void) __attribute__((__noreturn__));
116 static void printquotas(int, struct quotahandle *);
117 static void exportquotas(void);
118 static int oneof(const char *, char *[], int cnt);
119 static int isover(struct quotaval *qv, time_t now);
120
121 int
main(int argc,char ** argv)122 main(int argc, char **argv)
123 {
124 int gflag = 0, uflag = 0, errs = 0;
125 long i, argnum, done = 0;
126 int ch;
127 struct statvfs *fst;
128 int nfst;
129 struct quotahandle *qh;
130
131 if (!strcmp(getprogname(), "quotadump")) {
132 xflag = 1;
133 }
134
135 while ((ch = getopt(argc, argv, "aguhvx")) != -1) {
136 switch(ch) {
137 case 'a':
138 aflag++;
139 break;
140 case 'g':
141 gflag++;
142 break;
143 case 'u':
144 uflag++;
145 break;
146 case 'h':
147 hflag++;
148 break;
149 case 'v':
150 vflag++;
151 break;
152 case 'x':
153 xflag++;
154 break;
155 default:
156 usage();
157 }
158 }
159 argc -= optind;
160 argv += optind;
161 if (xflag && (argc != 1 || aflag))
162 usage();
163 if (argc == 0 && !aflag)
164 usage();
165 if (!gflag && !uflag) {
166 if (aflag)
167 gflag++;
168 uflag++;
169 }
170
171 nfst = getmntinfo(&fst, MNT_WAIT);
172 if (nfst == 0)
173 errx(1, "no filesystems mounted!");
174 for (i = 0; i < nfst; i++) {
175 if ((fst[i].f_flag & ST_QUOTA) == 0)
176 continue;
177 /* check if we want this volume */
178 if (!aflag) {
179 argnum = oneof(fst[i].f_mntonname, argv, argc);
180 if (argnum < 0) {
181 argnum = oneof(fst[i].f_mntfromname,
182 argv, argc);
183 }
184 if (argnum < 0) {
185 continue;
186 }
187 done |= 1U << argnum;
188 }
189
190 qh = quota_open(fst[i].f_mntonname);
191 if (qh == NULL) {
192 /* XXX: check this errno */
193 if (errno == EOPNOTSUPP || errno == ENXIO) {
194 continue;
195 }
196 warn("%s: quota_open", fst[i].f_mntonname);
197 continue;
198 }
199
200 if (gflag)
201 errs += repquota(qh, QUOTA_IDTYPE_GROUP);
202 if (uflag)
203 errs += repquota(qh, QUOTA_IDTYPE_USER);
204
205 quota_close(qh);
206 }
207 if (xflag)
208 exportquotas();
209 for (i = 0; i < argc; i++)
210 if ((done & (1U << i)) == 0)
211 warnx("%s not mounted", argv[i]);
212 return errs;
213 }
214
215 static void
usage(void)216 usage(void)
217 {
218 const char *p = getprogname();
219 fprintf(stderr, "usage: %s [-D] [-v] [-g] [-u] -a\n"
220 "\t%s [-D] [-v] [-g] [-u] filesys ...\n"
221 "\t%s -x [-D] [-g] [-u] filesys\n", p, p, p);
222 exit(1);
223 }
224
225 static int
repquota(struct quotahandle * qh,int idtype)226 repquota(struct quotahandle *qh, int idtype)
227 {
228 struct quotacursor *qc;
229 struct quotakey qk;
230 struct quotaval qv;
231 struct quotaval *qvp;
232 struct fileusage *fup;
233
234 qc = quota_opencursor(qh);
235 if (qc == NULL) {
236 return 1;
237 }
238
239 if (idtype == QUOTA_IDTYPE_USER) {
240 quotacursor_skipidtype(qc, QUOTA_IDTYPE_GROUP);
241 }
242 if (idtype == QUOTA_IDTYPE_GROUP) {
243 quotacursor_skipidtype(qc, QUOTA_IDTYPE_USER);
244 }
245
246 valid[idtype] = 0;
247 while (!quotacursor_atend(qc)) {
248 if (quotacursor_get(qc, &qk, &qv)) {
249 err(1, "%s: quotacursor_get", quota_getmountpoint(qh));
250 }
251 if (qk.qk_idtype != idtype) {
252 continue;
253 }
254
255 valid[idtype] = 1;
256 if (qk.qk_id == QUOTA_DEFAULTID) {
257 qvp = defaultqv[idtype];
258 } else {
259 if ((fup = lookup(qk.qk_id, idtype)) == 0)
260 fup = addid(qk.qk_id, idtype, (char *)0);
261 qvp = fup->fu_qv;
262 }
263 if (qk.qk_objtype == QUOTA_OBJTYPE_BLOCKS) {
264 qvp[QUOTA_OBJTYPE_BLOCKS] = qv;
265 } else if (qk.qk_objtype == QUOTA_OBJTYPE_FILES) {
266 qvp[QUOTA_OBJTYPE_FILES] = qv;
267 }
268 }
269
270 if (xflag == 0 && valid[idtype])
271 printquotas(idtype, qh);
272
273 return 0;
274 }
275
276 static void
printquotas(int idtype,struct quotahandle * qh)277 printquotas(int idtype, struct quotahandle *qh)
278 {
279 static int multiple = 0;
280 uint32_t id;
281 int i;
282 struct fileusage *fup;
283 struct quotaval *q;
284 const char *timemsg[REPQUOTA_NUMOBJTYPES];
285 char overchar[REPQUOTA_NUMOBJTYPES];
286 time_t now;
287 char b0[2][20], b1[20], b2[20], b3[20];
288 int ok, objtype;
289 int isbytes, width;
290
291 switch (idtype) {
292 case QUOTA_IDTYPE_GROUP:
293 {
294 struct group *gr;
295 setgrent();
296 while ((gr = getgrent()) != 0)
297 (void)addid(gr->gr_gid, idtype, gr->gr_name);
298 endgrent();
299 break;
300 }
301 case QUOTA_IDTYPE_USER:
302 {
303 struct passwd *pw;
304 setpwent();
305 while ((pw = getpwent()) != 0)
306 (void)addid(pw->pw_uid, idtype, pw->pw_name);
307 endpwent();
308 break;
309 }
310 default:
311 errx(1, "Unknown quota ID type %d", idtype);
312 }
313
314 time(&now);
315
316 if (multiple++)
317 printf("\n");
318 if (vflag)
319 printf("*** Report for %s quotas on %s (%s: %s)\n",
320 repquota_idtype_names[idtype], quota_getmountpoint(qh),
321 quota_getmountdevice(qh), quota_getimplname(qh));
322 printf(" Block limits "
323 "File limits\n");
324 printf(idtype == QUOTA_IDTYPE_USER ? "User " : "Group");
325 printf(" used soft hard grace used"
326 " soft hard grace\n");
327 for (id = 0; id <= highid[idtype]; id++) {
328 fup = qremove(id, idtype);
329 q = fup->fu_qv;
330 if (fup == 0)
331 continue;
332 for (i = 0; i < REPQUOTA_NUMOBJTYPES; i++) {
333 if (isover(&q[i], now)) {
334 timemsg[i] = timeprt(b0[i], 8, now,
335 q[i].qv_expiretime);
336 overchar[i] = '+';
337 } else {
338 if (vflag && q[i].qv_grace != QUOTA_NOTIME) {
339 timemsg[i] = timeprt(b0[i], 8, 0,
340 q[i].qv_grace);
341 } else {
342 timemsg[i] = "";
343 }
344 overchar[i] = '-';
345 }
346 }
347
348 ok = 1;
349 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
350 if (q[objtype].qv_usage != 0 ||
351 overchar[objtype] != '-') {
352 ok = 0;
353 }
354 }
355 if (ok && vflag == 0)
356 continue;
357 if (strlen(fup->fu_name) > 9)
358 printf("%s ", fup->fu_name);
359 else
360 printf("%-10s", fup->fu_name);
361 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
362 printf("%c", overchar[objtype]);
363 }
364 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
365 isbytes = quota_objtype_isbytes(qh, objtype);
366 width = isbytes ? 9 : 8;
367 printf("%*s%*s%*s%7s",
368 width,
369 intprt(b1, width+1, q[objtype].qv_usage,
370 isbytes ? HN_B : 0, hflag),
371 width,
372 intprt(b2, width+1, q[objtype].qv_softlimit,
373 isbytes ? HN_B : 0, hflag),
374 width,
375 intprt(b3, width+1, q[objtype].qv_hardlimit,
376 isbytes ? HN_B : 0, hflag),
377 timemsg[objtype]);
378
379 if (objtype + 1 < REPQUOTA_NUMOBJTYPES) {
380 printf(" ");
381 } else {
382 printf("\n");
383 }
384 }
385 free(fup);
386 }
387 }
388
389 static void
exportquotaval(const struct quotaval * qv)390 exportquotaval(const struct quotaval *qv)
391 {
392 if (qv->qv_hardlimit == QUOTA_NOLIMIT) {
393 printf(" -");
394 } else {
395 printf(" %llu", (unsigned long long)qv->qv_hardlimit);
396 }
397
398 if (qv->qv_softlimit == QUOTA_NOLIMIT) {
399 printf(" -");
400 } else {
401 printf(" %llu", (unsigned long long)qv->qv_softlimit);
402 }
403
404 printf(" %llu", (unsigned long long)qv->qv_usage);
405
406 if (qv->qv_expiretime == QUOTA_NOTIME) {
407 printf(" -");
408 } else {
409 printf(" %lld", (long long)qv->qv_expiretime);
410 }
411
412 if (qv->qv_grace == QUOTA_NOTIME) {
413 printf(" -");
414 } else {
415 printf(" %lld", (long long)qv->qv_grace);
416 }
417 }
418
419 static void
exportquotas(void)420 exportquotas(void)
421 {
422 int idtype;
423 id_t id;
424 struct fileusage *fup;
425
426 /* header */
427 printf("@format netbsd-quota-dump v1\n");
428 printf("# idtype id objtype hard soft usage expire grace\n");
429
430 for (idtype = 0; idtype < REPQUOTA_NUMIDTYPES; idtype++) {
431 if (valid[idtype] == 0)
432 continue;
433
434 printf("%s default block ", repquota_idtype_names[idtype]);
435 exportquotaval(&defaultqv[idtype][QUOTA_OBJTYPE_BLOCKS]);
436 printf("\n");
437
438 printf("%s default file ", repquota_idtype_names[idtype]);
439 exportquotaval(&defaultqv[idtype][QUOTA_OBJTYPE_FILES]);
440 printf("\n");
441
442 for (id = 0; id <= highid[idtype]; id++) {
443 fup = qremove(id, idtype);
444 if (fup == 0)
445 continue;
446
447 printf("%s %u block ", repquota_idtype_names[idtype],
448 id);
449 exportquotaval(&fup->fu_qv[QUOTA_OBJTYPE_BLOCKS]);
450 printf("\n");
451
452 printf("%s %u file ", repquota_idtype_names[idtype],
453 id);
454 exportquotaval(&fup->fu_qv[QUOTA_OBJTYPE_FILES]);
455 printf("\n");
456
457 free(fup);
458 }
459 }
460 printf("@end\n");
461 }
462
463 /*
464 * Routines to manage the file usage table.
465 *
466 * Lookup an id of a specific id type.
467 */
468 struct fileusage *
lookup(uint32_t id,int idtype)469 lookup(uint32_t id, int idtype)
470 {
471 struct fileusage *fup;
472
473 for (fup = fuhead[idtype][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
474 if (fup->fu_id == id)
475 return fup;
476 return NULL;
477 }
478 /*
479 * Lookup and remove an id of a specific id type.
480 */
481 static struct fileusage *
qremove(uint32_t id,int idtype)482 qremove(uint32_t id, int idtype)
483 {
484 struct fileusage *fup, **fupp;
485
486 for (fupp = &fuhead[idtype][id & (FUHASH-1)]; *fupp != 0;) {
487 fup = *fupp;
488 if (fup->fu_id == id) {
489 *fupp = fup->fu_next;
490 return fup;
491 }
492 fupp = &fup->fu_next;
493 }
494 return NULL;
495 }
496
497 /*
498 * Add a new file usage id if it does not already exist.
499 */
500 static struct fileusage *
addid(uint32_t id,int idtype,const char * name)501 addid(uint32_t id, int idtype, const char *name)
502 {
503 struct fileusage *fup, **fhp;
504 struct group *gr = NULL;
505 struct passwd *pw = NULL;
506 size_t len;
507
508 if ((fup = lookup(id, idtype)) != NULL) {
509 return fup;
510 }
511 if (name == NULL) {
512 switch(idtype) {
513 case QUOTA_IDTYPE_GROUP:
514 gr = getgrgid(id);
515
516 if (gr != NULL)
517 name = gr->gr_name;
518 break;
519 case QUOTA_IDTYPE_USER:
520 pw = getpwuid(id);
521 if (pw)
522 name = pw->pw_name;
523 break;
524 default:
525 errx(1, "Unknown quota ID type %d", idtype);
526 }
527 }
528
529 if (name)
530 len = strlen(name);
531 else
532 len = 10;
533 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
534 err(1, "out of memory for fileusage structures");
535 fhp = &fuhead[idtype][id & (FUHASH - 1)];
536 fup->fu_next = *fhp;
537 *fhp = fup;
538 fup->fu_id = id;
539 if (id > highid[idtype])
540 highid[idtype] = id;
541 if (name) {
542 memmove(fup->fu_name, name, len + 1);
543 } else {
544 snprintf(fup->fu_name, len + 1, "%u", id);
545 }
546 /*
547 * XXX nothing guarantees the default limits have been loaded yet
548 */
549 fup->fu_qv[QUOTA_OBJTYPE_BLOCKS] = defaultqv[idtype][QUOTA_OBJTYPE_BLOCKS];
550 fup->fu_qv[QUOTA_OBJTYPE_FILES] = defaultqv[idtype][QUOTA_OBJTYPE_FILES];
551 return fup;
552 }
553
554 /*
555 * Check to see if target appears in list of size cnt.
556 */
557 static int
oneof(const char * target,char * list[],int cnt)558 oneof(const char *target, char *list[], int cnt)
559 {
560 int i;
561
562 for (i = 0; i < cnt; i++)
563 if (strcmp(target, list[i]) == 0)
564 return i;
565 return -1;
566 }
567
568 static int
isover(struct quotaval * qv,time_t now)569 isover(struct quotaval *qv, time_t now)
570 {
571 return (qv->qv_usage >= qv->qv_hardlimit ||
572 qv->qv_usage >= qv->qv_softlimit);
573 }
574