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