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