repquota.c revision 1.38 1 /* $NetBSD: repquota.c,v 1.38 2012/01/25 01:24:53 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.38 2012/01/25 01:24:53 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/quota.h>
68 #include <quota/quotaprop.h>
69 #include <quota.h>
70
71 #include "printquota.h"
72
73 /*
74 * XXX. Ideally we shouldn't compile either of these in, but it's a
75 * nontrivial rework to avoid it and it'll work ok for now.
76 */
77 #define REPQUOTA_NUMIDTYPES 2
78 #define REPQUOTA_NUMOBJTYPES 2
79
80 struct fileusage {
81 struct fileusage *fu_next;
82 struct quotaval fu_qv[REPQUOTA_NUMOBJTYPES];
83 uint32_t fu_id;
84 char fu_name[1];
85 /* actually bigger */
86 };
87
88 #define FUHASH 1024 /* must be power of two */
89 static struct fileusage *fuhead[REPQUOTA_NUMIDTYPES][FUHASH];
90
91 /* highest addid()'ed identifier per idtype */
92 static uint32_t highid[REPQUOTA_NUMIDTYPES];
93
94 int valid[REPQUOTA_NUMIDTYPES];
95
96 static struct quotaval defaultqv[REPQUOTA_NUMIDTYPES][REPQUOTA_NUMOBJTYPES];
97
98 static int vflag = 0; /* verbose */
99 static int aflag = 0; /* all file systems */
100 static int Dflag = 0; /* debug */
101 static int hflag = 0; /* humanize */
102 static int xflag = 0; /* export */
103
104 /*
105 * XXX this should go away and be replaced with a call to
106 * quota_idtype_getname(), but that needs a quotahandle and requires
107 * the same nontrivial rework as getting rid of REPQUOTA_NUMIDTYPES.
108 */
109 static const char *const repquota_idtype_names[REPQUOTA_NUMIDTYPES] = {
110 "user",
111 "group",
112 };
113
114 static struct fileusage *addid(uint32_t, int, const char *);
115 static struct fileusage *lookup(uint32_t, int);
116 static struct fileusage *qremove(uint32_t, int);
117 static int repquota(struct quotahandle *, int);
118 static void usage(void) __attribute__((__noreturn__));
119 static void printquotas(int, struct quotahandle *);
120 static void exportquotas(void);
121 static int oneof(const char *, char *[], int cnt);
122
123 int
124 main(int argc, char **argv)
125 {
126 int gflag = 0, uflag = 0, errs = 0;
127 long i, argnum, done = 0;
128 int ch;
129 struct statvfs *fst;
130 int nfst;
131 struct quotahandle *qh;
132
133 while ((ch = getopt(argc, argv, "Daguhvx")) != -1) {
134 switch(ch) {
135 case 'a':
136 aflag++;
137 break;
138 case 'g':
139 gflag++;
140 break;
141 case 'u':
142 uflag++;
143 break;
144 case 'h':
145 hflag++;
146 break;
147 case 'v':
148 vflag++;
149 break;
150 case 'D':
151 Dflag++;
152 break;
153 case 'x':
154 xflag++;
155 break;
156 default:
157 usage();
158 }
159 }
160 argc -= optind;
161 argv += optind;
162 if (xflag && (argc != 1 || aflag))
163 usage();
164 if (argc == 0 && !aflag)
165 usage();
166 if (!gflag && !uflag) {
167 if (aflag)
168 gflag++;
169 uflag++;
170 }
171
172 nfst = getmntinfo(&fst, MNT_WAIT);
173 if (nfst == 0)
174 errx(1, "no filesystems mounted!");
175 for (i = 0; i < nfst; i++) {
176 if ((fst[i].f_flag & ST_QUOTA) == 0)
177 continue;
178 /* check if we want this volume */
179 if (!aflag) {
180 argnum = oneof(fst[i].f_mntonname, argv, argc);
181 if (argnum < 0) {
182 argnum = oneof(fst[i].f_mntfromname,
183 argv, argc);
184 }
185 if (argnum < 0) {
186 continue;
187 }
188 done |= 1U << argnum;
189 }
190
191 qh = quota_open(fst[i].f_mntonname);
192 if (qh == NULL) {
193 /* XXX: check this errno */
194 if (errno == EOPNOTSUPP || errno == ENXIO) {
195 continue;
196 }
197 warn("%s: quota_open", fst[i].f_mntonname);
198 continue;
199 }
200
201 if (gflag)
202 errs += repquota(qh, QUOTA_IDTYPE_GROUP);
203 if (uflag)
204 errs += repquota(qh, QUOTA_IDTYPE_USER);
205
206 quota_close(qh);
207 }
208 if (xflag)
209 exportquotas();
210 for (i = 0; i < argc; i++)
211 if ((done & (1U << i)) == 0)
212 warnx("%s not mounted", argv[i]);
213 return errs;
214 }
215
216 static void
217 usage(void)
218 {
219 const char *p = getprogname();
220 fprintf(stderr, "usage: %s [-D] [-v] [-g] [-u] -a\n"
221 "\t%s [-D] [-v] [-g] [-u] filesys ...\n"
222 "\t%s -x [-D] [-g] [-u] filesys\n", p, p, p);
223 exit(1);
224 }
225
226 static int
227 repquota(struct quotahandle *qh, int idtype)
228 {
229 struct quotacursor *qc;
230 struct quotakey qk;
231 struct quotaval qv;
232 struct quotaval *qvp;
233 struct fileusage *fup;
234
235 qc = quota_opencursor(qh);
236 if (qc == NULL) {
237 return 1;
238 }
239
240 if (idtype == QUOTA_IDTYPE_USER) {
241 quotacursor_skipidtype(qc, QUOTA_IDTYPE_GROUP);
242 }
243 if (idtype == QUOTA_IDTYPE_GROUP) {
244 quotacursor_skipidtype(qc, QUOTA_IDTYPE_USER);
245 }
246
247 valid[idtype] = 0;
248 while (!quotacursor_atend(qc)) {
249 if (quotacursor_get(qc, &qk, &qv)) {
250 err(1, "%s: quotacursor_get", quota_getmountpoint(qh));
251 }
252 if (qk.qk_idtype != idtype) {
253 continue;
254 }
255
256 valid[idtype] = 1;
257 if (qk.qk_id == QUOTA_DEFAULTID) {
258 qvp = defaultqv[idtype];
259 } else {
260 if ((fup = lookup(qk.qk_id, idtype)) == 0)
261 fup = addid(qk.qk_id, idtype, (char *)0);
262 qvp = fup->fu_qv;
263 }
264 if (qk.qk_objtype == QUOTA_OBJTYPE_BLOCKS) {
265 qvp[QUOTA_OBJTYPE_BLOCKS] = qv;
266 } else if (qk.qk_objtype == QUOTA_OBJTYPE_FILES) {
267 qvp[QUOTA_OBJTYPE_FILES] = qv;
268 }
269 }
270
271 if (xflag == 0 && valid[idtype])
272 printquotas(idtype, qh);
273
274 return 0;
275 }
276
277 static void
278 printquotas(int idtype, struct quotahandle *qh)
279 {
280 static int multiple = 0;
281 uint32_t id;
282 int i;
283 struct fileusage *fup;
284 struct quotaval *q;
285 const char *timemsg[REPQUOTA_NUMOBJTYPES];
286 char overchar[REPQUOTA_NUMOBJTYPES];
287 time_t now;
288 char b0[2][20], b1[20], b2[20], b3[20];
289 int ok, objtype;
290 int isbytes, width;
291
292 switch (idtype) {
293 case QUOTA_IDTYPE_GROUP:
294 {
295 struct group *gr;
296 setgrent();
297 while ((gr = getgrent()) != 0)
298 (void)addid(gr->gr_gid, idtype, gr->gr_name);
299 endgrent();
300 break;
301 }
302 case QUOTA_IDTYPE_USER:
303 {
304 struct passwd *pw;
305 setpwent();
306 while ((pw = getpwent()) != 0)
307 (void)addid(pw->pw_uid, idtype, pw->pw_name);
308 endpwent();
309 break;
310 }
311 default:
312 errx(1, "Unknown quota ID type %d", idtype);
313 }
314
315 time(&now);
316
317 if (multiple++)
318 printf("\n");
319 if (vflag)
320 printf("*** Report for %s quotas on %s (%s: %s)\n",
321 repquota_idtype_names[idtype], quota_getmountpoint(qh),
322 quota_getmountdevice(qh), quota_getimplname(qh));
323 printf(" Block limits "
324 "File limits\n");
325 printf(idtype == QUOTA_IDTYPE_USER ? "User " : "Group");
326 printf(" used soft hard grace used"
327 "soft hard grace\n");
328 for (id = 0; id <= highid[idtype]; id++) {
329 fup = qremove(id, idtype);
330 q = fup->fu_qv;
331 if (fup == 0)
332 continue;
333 for (i = 0; i < REPQUOTA_NUMOBJTYPES; i++) {
334 switch (QL_STATUS(quota_check_limit(q[i].qv_usage, 1,
335 q[i].qv_softlimit, q[i].qv_hardlimit,
336 q[i].qv_expiretime, now))) {
337 case QL_S_DENY_HARD:
338 case QL_S_DENY_GRACE:
339 case QL_S_ALLOW_SOFT:
340 timemsg[i] = timeprt(b0[i], 8, now,
341 q[i].qv_expiretime);
342 overchar[i] = '+';
343 break;
344 default:
345 if (vflag && q[i].qv_grace != QUOTA_NOTIME) {
346 timemsg[i] = timeprt(b0[i], 8, 0,
347 q[i].qv_grace);
348 } else {
349 timemsg[i] = "";
350 }
351 overchar[i] = '-';
352 break;
353 }
354 }
355
356 ok = 1;
357 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
358 if (q[objtype].qv_usage != 0 ||
359 overchar[objtype] != '-') {
360 ok = 0;
361 }
362 }
363 if (ok && vflag == 0)
364 continue;
365 if (strlen(fup->fu_name) > 9)
366 printf("%s ", fup->fu_name);
367 else
368 printf("%-10s", fup->fu_name);
369 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
370 printf("%c", overchar[objtype]);
371 }
372 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) {
373 isbytes = quota_objtype_isbytes(qh, objtype);
374 width = isbytes ? 9 : 8;
375 printf("%*s%*s%*s%7s",
376 width,
377 intprt(b1, width+1, q[objtype].qv_usage,
378 isbytes ? HN_B : 0, hflag),
379 width,
380 intprt(b2, width+1, q[objtype].qv_softlimit,
381 isbytes ? HN_B : 0, hflag),
382 width,
383 intprt(b3, width+1, q[objtype].qv_hardlimit,
384 isbytes ? HN_B : 0, hflag),
385 timemsg[objtype]);
386
387 if (objtype + 1 < REPQUOTA_NUMOBJTYPES) {
388 printf(" ");
389 } else {
390 printf("\n");
391 }
392 }
393 free(fup);
394 }
395 }
396
397 static void
398 exportquotas(void)
399 {
400 uint32_t id;
401 struct fileusage *fup;
402 prop_dictionary_t dict, data;
403 prop_array_t cmds, datas;
404 int idtype;
405 uint64_t *valuesp[QUOTA_NLIMITS];
406
407 dict = quota_prop_create();
408 cmds = prop_array_create();
409
410 if (dict == NULL || cmds == NULL) {
411 errx(1, "can't allocate proplist");
412 }
413
414
415 for (idtype = 0; idtype < REPQUOTA_NUMIDTYPES; idtype++) {
416 if (valid[idtype] == 0)
417 continue;
418 datas = prop_array_create();
419 if (datas == NULL)
420 errx(1, "can't allocate proplist");
421 valuesp[QUOTA_LIMIT_BLOCK] =
422 &defaultqv[idtype][QUOTA_LIMIT_BLOCK].qv_hardlimit;
423 valuesp[QUOTA_LIMIT_FILE] =
424 &defaultqv[idtype][QUOTA_LIMIT_FILE].qv_hardlimit;
425 data = quota64toprop(0, 1, valuesp,
426 ufs_quota_entry_names, UFS_QUOTA_NENTRIES,
427 ufs_quota_limit_names, QUOTA_NLIMITS);
428 if (data == NULL)
429 err(1, "quota64toprop(default)");
430 if (!prop_array_add_and_rel(datas, data))
431 err(1, "prop_array_add(data)");
432
433 for (id = 0; id <= highid[idtype]; id++) {
434 fup = qremove(id, idtype);
435 if (fup == 0)
436 continue;
437 valuesp[QUOTA_LIMIT_BLOCK] =
438 &fup->fu_qv[QUOTA_LIMIT_BLOCK].qv_hardlimit;
439 valuesp[QUOTA_LIMIT_FILE] =
440 &fup->fu_qv[QUOTA_LIMIT_FILE].qv_hardlimit;
441 data = quota64toprop(id, 0, valuesp,
442 ufs_quota_entry_names, UFS_QUOTA_NENTRIES,
443 ufs_quota_limit_names, QUOTA_NLIMITS);
444 if (data == NULL)
445 err(1, "quota64toprop(id)");
446 if (!prop_array_add_and_rel(datas, data))
447 err(1, "prop_array_add(data)");
448 free(fup);
449 }
450
451 if (!quota_prop_add_command(cmds, "set",
452 ufs_quota_class_names[idtype], datas))
453 err(1, "prop_add_command");
454 }
455
456 if (!prop_dictionary_set(dict, "commands", cmds))
457 err(1, "prop_dictionary_set(command)");
458
459 printf("%s\n", prop_dictionary_externalize(dict));
460 return;
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 *
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 *
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 *
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\n", 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
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