repquota.c revision 1.26 1 /*
2 * Copyright (c) 1980, 1990, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Robert Elz at The University of Melbourne.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 #include <sys/cdefs.h>
34 #ifndef lint
35 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\
36 The Regents of the University of California. All rights reserved.");
37 #endif /* not lint */
38
39 #ifndef lint
40 #if 0
41 static char sccsid[] = "@(#)repquota.c 8.2 (Berkeley) 11/22/94";
42 #else
43 __RCSID("$NetBSD: repquota.c,v 1.26 2011/03/06 17:08:43 bouyer Exp $");
44 #endif
45 #endif /* not lint */
46
47 /*
48 * Quota report
49 */
50 #include <sys/param.h>
51 #include <sys/stat.h>
52 #include <sys/types.h>
53 #include <sys/statvfs.h>
54 #include <prop/proplib.h>
55 #include <sys/quota.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 <ufs/ufs/quota2_prop.h>
68 #include <ufs/ufs/quota1.h>
69
70 #include <printquota.h>
71
72 const char *qfextension[] = INITQFNAMES;
73 const char *qfname = QUOTAFILENAME;
74
75 struct fileusage {
76 struct fileusage *fu_next;
77 struct quota2_entry fu_q2e;
78 u_long fu_id;
79 char fu_name[1];
80 /* actually bigger */
81 };
82 #define FUHASH 1024 /* must be power of two */
83 struct fileusage *fuhead[MAXQUOTAS][FUHASH];
84 u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */
85 int valid[MAXQUOTAS];
86 struct quota2_entry defaultq2e[MAXQUOTAS];
87
88 int vflag = 0; /* verbose */
89 int aflag = 0; /* all file systems */
90 int Dflag = 0; /* debug */
91 int hflag = 0; /* humanize */
92 int xflag = 0; /* export */
93
94
95 struct fileusage *addid(u_long, int, const char *);
96 int hasquota(struct fstab *, int, char **);
97 struct fileusage *lookup(u_long, int);
98 struct fileusage *qremove(u_long, int);
99 int main(int, char **);
100 int oneof(const char *, char **, int);
101 int repquota(const struct statvfs *, int);
102 int repquota2(const struct statvfs *, int);
103 int repquota1(const struct statvfs *, int);
104 void usage(void);
105 void printquotas(int, const struct statvfs *, int);
106 void exportquotas(void);
107 void dqblk2q2e(const struct dqblk *, struct quota2_entry *);
108
109 int
110 main(argc, argv)
111 int argc;
112 char **argv;
113 {
114 int gflag = 0, uflag = 0, errs = 0;
115 long i, argnum, done = 0;
116 int ch;
117 struct statvfs *fst;
118 int nfst;
119
120 while ((ch = getopt(argc, argv, "Daguhvx")) != -1) {
121 switch(ch) {
122 case 'a':
123 aflag++;
124 break;
125 case 'g':
126 gflag++;
127 break;
128 case 'u':
129 uflag++;
130 break;
131 case 'h':
132 hflag++;
133 break;
134 case 'v':
135 vflag++;
136 break;
137 case 'D':
138 Dflag++;
139 break;
140 case 'x':
141 xflag++;
142 break;
143 default:
144 usage();
145 }
146 }
147 argc -= optind;
148 argv += optind;
149 if (xflag && (argc != 1 || aflag))
150 usage();
151 if (argc == 0 && !aflag)
152 usage();
153 if (!gflag && !uflag) {
154 if (aflag)
155 gflag++;
156 uflag++;
157 }
158
159 nfst = getmntinfo(&fst, MNT_WAIT);
160 if (nfst == 0)
161 errx(2, "no filesystems mounted!");
162 for (i = 0; i < nfst; i++) {
163 if ((fst[i].f_flag & ST_QUOTA) == 0)
164 continue;
165 if (aflag) {
166 if (gflag)
167 errs += repquota(&fst[i], GRPQUOTA);
168 if (uflag)
169 errs += repquota(&fst[i], USRQUOTA);
170 continue;
171 }
172 if ((argnum = oneof(fst[i].f_mntonname, argv, argc)) >= 0 ||
173 (argnum = oneof(fst[i].f_mntfromname, argv, argc)) >= 0) {
174 done |= 1 << argnum;
175 if (gflag)
176 errs += repquota(&fst[i], GRPQUOTA);
177 if (uflag)
178 errs += repquota(&fst[i], USRQUOTA);
179 }
180 }
181 if (xflag)
182 exportquotas();
183 for (i = 0; i < argc; i++)
184 if ((done & (1 << i)) == 0)
185 fprintf(stderr, "%s not mounted\n", argv[i]);
186 exit(errs);
187 }
188
189 void
190 usage()
191 {
192 fprintf(stderr, "usage:\n"
193 "\trepquota [-D] [-v] [-g] [-u] -a\n"
194 "\trepquota [-D] [-v] [-g] [-u] filesys ...\n"
195 "\trepquota -x [-D] [-g] [-u] filesys\n");
196 exit(1);
197 }
198
199 int
200 repquota(const struct statvfs *vfs, int type)
201 {
202 if (repquota2(vfs, type) != 0)
203 return (repquota1(vfs, type));
204 return 0;
205 }
206
207 int
208 repquota2(const struct statvfs *vfs, int type)
209 {
210 prop_dictionary_t dict, data, cmd;
211 prop_array_t cmds, datas;
212 struct plistref pref;
213 int error;
214 int8_t error8, version = 0;
215 prop_object_iterator_t cmditer, dataiter;
216 struct quota2_entry *q2ep;
217 struct fileusage *fup;
218 const char *strid;
219 uint32_t id;
220
221 dict = quota2_prop_create();
222 cmds = prop_array_create();
223 datas = prop_array_create();
224
225 if (dict == NULL || cmds == NULL || datas == NULL)
226 errx(1, "can't allocate proplist");
227 if (!quota2_prop_add_command(cmds, "getall", qfextension[type], datas))
228 err(1, "prop_add_command");
229 if (!quota2_prop_add_command(cmds, "get version", qfextension[type],
230 prop_array_create()))
231 err(1, "prop_add_command");
232 if (!prop_dictionary_set(dict, "commands", cmds))
233 err(1, "prop_dictionary_set(command)");
234 if (Dflag)
235 printf("message to kernel:\n%s\n",
236 prop_dictionary_externalize(dict));
237 if (!prop_dictionary_send_syscall(dict, &pref))
238 err(1, "prop_dictionary_send_syscall");
239 prop_object_release(dict);
240
241 if (quotactl(vfs->f_mntonname, &pref) != 0)
242 err(1, "quotactl");
243
244 if ((error = prop_dictionary_recv_syscall(&pref, &dict)) != 0) {
245 errx(1, "prop_dictionary_recv_syscall: %s\n",
246 strerror(error));
247 }
248 if (Dflag)
249 printf("reply from kernel:\n%s\n",
250 prop_dictionary_externalize(dict));
251 if ((error = quota2_get_cmds(dict, &cmds)) != 0) {
252 errx(1, "quota2_get_cmds: %s\n",
253 strerror(error));
254 }
255 cmditer = prop_array_iterator(cmds);
256 if (cmditer == NULL)
257 err(1, "prop_array_iterator(cmds)");
258
259 while ((cmd = prop_object_iterator_next(cmditer)) != NULL) {
260 const char *cmdstr;
261 if (!prop_dictionary_get_cstring_nocopy(cmd, "command",
262 &cmdstr))
263 err(1, "prop_get(command)");
264
265 if (!prop_dictionary_get_int8(cmd, "return", &error8))
266 err(1, "prop_get(return)");
267
268 if (error8) {
269 prop_object_release(dict);
270 if (error8 != EOPNOTSUPP) {
271 fprintf(stderr, "get %s quotas: %s\n",
272 qfextension[type], strerror(error8));
273 }
274 return (error8);
275 }
276 datas = prop_dictionary_get(cmd, "data");
277 if (datas == NULL)
278 err(1, "prop_dict_get(datas)");
279
280 if (strcmp("get version", cmdstr) == 0) {
281 data = prop_array_get(datas, 0);
282 if (data == NULL)
283 err(1, "prop_array_get(version)");
284 if (!prop_dictionary_get_int8(data, "version",
285 &version))
286 err(1, "prop_get_int8(version)");
287 continue;
288 }
289 dataiter = prop_array_iterator(datas);
290 if (dataiter == NULL)
291 err(1, "prop_array_iterator");
292
293 valid[type] = 1;
294 while ((data = prop_object_iterator_next(dataiter)) != NULL) {
295 strid = NULL;
296 if (!prop_dictionary_get_uint32(data, "id", &id)) {
297 if (!prop_dictionary_get_cstring_nocopy(data,
298 "id", &strid))
299 errx(1, "can't find id in quota entry");
300 if (strcmp(strid, "default") != 0) {
301 errx(1,
302 "wrong id string %s in quota entry",
303 strid);
304 }
305 q2ep = &defaultq2e[type];
306 } else {
307 if ((fup = lookup(id, type)) == 0)
308 fup = addid(id, type, (char *)0);
309 q2ep = &fup->fu_q2e;
310 q2ep->q2e_uid = id;
311 }
312
313 error = quota2_dict_get_q2e_usage(data, q2ep);
314 if (error) {
315 errx(1, "quota2_dict_get_q2e_usage: %s\n",
316 strerror(error));
317 }
318 }
319 prop_object_iterator_release(dataiter);
320 }
321 prop_object_iterator_release(cmditer);
322 prop_object_release(dict);
323 if (xflag == 0)
324 printquotas(type, vfs, version);
325 return (0);
326 }
327
328 int repquota1(const struct statvfs *vfs, int type)
329 {
330 char *qfpathname;
331 struct fstab *fs;
332 struct fileusage *fup;
333 FILE *qf;
334 u_long id;
335 struct dqblk dqbuf;
336 time_t bgrace = MAX_DQ_TIME, igrace = MAX_DQ_TIME;
337
338 setfsent();
339 while ((fs = getfsent()) != NULL) {
340 if (strcmp(fs->fs_vfstype, "ffs") == 0 &&
341 strcmp(fs->fs_file, vfs->f_mntonname) == 0)
342 break;
343 }
344 endfsent();
345 if (fs == NULL) {
346 fprintf(stderr, "%s not found in fstab\n", vfs->f_mntonname);
347 return 1;
348 }
349 if (!hasquota(fs, type, &qfpathname))
350 return 0;
351
352 if ((qf = fopen(qfpathname, "r")) == NULL) {
353 perror(qfpathname);
354 return (1);
355 }
356 for (id = 0; ; id++) {
357 fread(&dqbuf, sizeof(struct dqblk), 1, qf);
358 if (feof(qf))
359 break;
360 if (id == 0) {
361 if (dqbuf.dqb_btime > 0)
362 bgrace = dqbuf.dqb_btime;
363 if (dqbuf.dqb_itime > 0)
364 igrace = dqbuf.dqb_itime;
365 }
366 if (dqbuf.dqb_curinodes == 0 && dqbuf.dqb_curblocks == 0 &&
367 dqbuf.dqb_bsoftlimit == 0 && dqbuf.dqb_bhardlimit == 0 &&
368 dqbuf.dqb_isoftlimit == 0 && dqbuf.dqb_ihardlimit == 0)
369 continue;
370 if ((fup = lookup(id, type)) == 0)
371 fup = addid(id, type, (char *)0);
372 dqblk2q2e(&dqbuf, &fup->fu_q2e);
373 fup->fu_q2e.q2e_val[QL_BLOCK].q2v_grace = bgrace;
374 fup->fu_q2e.q2e_val[QL_FILE].q2v_grace = igrace;
375 }
376 defaultq2e[type].q2e_val[QL_BLOCK].q2v_grace = bgrace;
377 defaultq2e[type].q2e_val[QL_FILE].q2v_grace = igrace;
378 defaultq2e[type].q2e_val[QL_BLOCK].q2v_softlimit =
379 defaultq2e[type].q2e_val[QL_BLOCK].q2v_hardlimit =
380 defaultq2e[type].q2e_val[QL_FILE].q2v_softlimit =
381 defaultq2e[type].q2e_val[QL_FILE].q2v_hardlimit = UQUAD_MAX;
382 fclose(qf);
383 valid[type] = 1;
384 if (xflag == 0)
385 printquotas(type, vfs, 1);
386 return (0);
387 }
388
389 void
390 printquotas(int type, const struct statvfs *vfs, int version)
391 {
392 static int multiple = 0;
393 u_long id;
394 int i;
395 struct fileusage *fup;
396 const char *timemsg[N_QL];
397 char overchar[N_QL];
398 static time_t now;
399
400 switch(type) {
401 case GRPQUOTA:
402 {
403 struct group *gr;
404 setgrent();
405 while ((gr = getgrent()) != 0)
406 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name);
407 endgrent();
408 break;
409 }
410 case USRQUOTA:
411 {
412 struct passwd *pw;
413 setpwent();
414 while ((pw = getpwent()) != 0)
415 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name);
416 endpwent();
417 break;
418 }
419 default:
420 errx(1, "unknown quota type %d\n", type);
421 }
422
423 if (now == 0)
424 time(&now);
425
426 if (multiple++)
427 printf("\n");
428 if (vflag)
429 fprintf(stdout,
430 "*** Report for %s quotas on %s (%s, version %d)\n",
431 qfextension[type], vfs->f_mntonname, vfs->f_mntfromname,
432 version);
433 printf(" Block limits File limits\n");
434 printf(type == USRQUOTA ? "User " : "Group");
435 printf(" used soft hard grace used soft hard grace\n");
436 for (id = 0; id <= highid[type]; id++) {
437 fup = qremove(id, type);
438 if (fup == 0)
439 continue;
440 for (i = 0; i < N_QL; i++) {
441 switch (QL_STATUS(quota2_check_limit(
442 &fup->fu_q2e.q2e_val[i], 1, now))) {
443 case QL_S_DENY_HARD:
444 case QL_S_DENY_GRACE:
445 case QL_S_ALLOW_SOFT:
446 timemsg[i] = timeprt(now,
447 fup->fu_q2e.q2e_val[i].q2v_time, 7);
448 overchar[i] = '+';
449 break;
450 default:
451 timemsg[i] = (vflag && version == 2) ?
452 timeprt(0,
453 fup->fu_q2e.q2e_val[i].q2v_grace, 7):
454 "";
455 overchar[i] = '-';
456 break;
457 }
458 }
459
460 if (fup->fu_q2e.q2e_val[QL_BLOCK].q2v_cur == 0 &&
461 fup->fu_q2e.q2e_val[QL_FILE].q2v_cur == 0 && vflag == 0 &&
462 overchar[QL_BLOCK] == '-' && overchar[QL_FILE] == '-')
463 continue;
464 if (strlen(fup->fu_name) > 9)
465 printf("%s ", fup->fu_name);
466 else
467 printf("%-10s", fup->fu_name);
468 printf("%c%c%9s%9s%9s%7s",
469 overchar[QL_BLOCK], overchar[QL_FILE],
470 intprt(fup->fu_q2e.q2e_val[QL_BLOCK].q2v_cur,
471 HN_B, hflag, 9),
472 intprt(fup->fu_q2e.q2e_val[QL_BLOCK].q2v_softlimit,
473 HN_B, hflag, 9),
474 intprt(fup->fu_q2e.q2e_val[QL_BLOCK].q2v_hardlimit,
475 HN_B, hflag, 9),
476 timemsg[QL_BLOCK]);
477 printf(" %8s%8s%8s%7s\n",
478 intprt(fup->fu_q2e.q2e_val[QL_FILE].q2v_cur,
479 0, hflag, 8),
480 intprt(fup->fu_q2e.q2e_val[QL_FILE].q2v_softlimit,
481 0, hflag, 8),
482 intprt(fup->fu_q2e.q2e_val[QL_FILE].q2v_hardlimit,
483 0, hflag, 8),
484 timemsg[QL_FILE]);
485 free(fup);
486 }
487 }
488
489 void
490 exportquotas()
491 {
492 u_long id;
493 struct fileusage *fup;
494 prop_dictionary_t dict, data;
495 prop_array_t cmds, datas;
496 int type;
497
498 dict = quota2_prop_create();
499 cmds = prop_array_create();
500
501 if (dict == NULL || cmds == NULL) {
502 errx(1, "can't allocate proplist");
503 }
504
505
506 for (type = 0; type < MAXQUOTAS; type++) {
507 if (valid[type] == 0)
508 continue;
509 datas = prop_array_create();
510 if (datas == NULL)
511 errx(1, "can't allocate proplist");
512 data = q2etoprop(&defaultq2e[type], 1);
513 if (data == NULL)
514 err(1, "q2etoprop(default)");
515 if (!prop_array_add_and_rel(datas, data))
516 err(1, "prop_array_add(data)");
517
518 for (id = 0; id <= highid[type]; id++) {
519 fup = qremove(id, type);
520 if (fup == 0)
521 continue;
522 fup->fu_q2e.q2e_uid = id;
523 data = q2etoprop(&fup->fu_q2e, 0);
524 if (data == NULL)
525 err(1, "q2etoprop(default)");
526 if (!prop_array_add_and_rel(datas, data))
527 err(1, "prop_array_add(data)");
528 free(fup);
529 }
530
531 if (!quota2_prop_add_command(cmds, "set",
532 qfextension[type], datas))
533 err(1, "prop_add_command");
534 }
535
536 if (!prop_dictionary_set(dict, "commands", cmds))
537 err(1, "prop_dictionary_set(command)");
538
539 printf("%s\n", prop_dictionary_externalize(dict));
540 return;
541 }
542
543 /*
544 * Check to see if target appears in list of size cnt.
545 */
546 int
547 oneof(target, list, cnt)
548 const char *target;
549 char *list[];
550 int cnt;
551 {
552 int i;
553
554 for (i = 0; i < cnt; i++)
555 if (strcmp(target, list[i]) == 0)
556 return (i);
557 return (-1);
558 }
559
560 /*
561 * Check to see if a particular quota is to be enabled.
562 */
563 int
564 hasquota(fs, type, qfnamep)
565 struct fstab *fs;
566 int type;
567 char **qfnamep;
568 {
569 char *opt;
570 char *cp = NULL;
571 static char initname, usrname[100], grpname[100];
572 static char buf[BUFSIZ];
573
574 if (!initname) {
575 sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
576 sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
577 initname = 1;
578 }
579 strcpy(buf, fs->fs_mntops);
580 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
581 if ((cp = strchr(opt, '=')) != NULL)
582 *cp++ = '\0';
583 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
584 break;
585 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
586 break;
587 }
588 if (!opt)
589 return (0);
590 if (cp) {
591 *qfnamep = cp;
592 return (1);
593 }
594 (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
595 *qfnamep = buf;
596 return (1);
597 }
598
599 /*
600 * Routines to manage the file usage table.
601 *
602 * Lookup an id of a specific type.
603 */
604 struct fileusage *
605 lookup(id, type)
606 u_long id;
607 int type;
608 {
609 struct fileusage *fup;
610
611 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
612 if (fup->fu_id == id)
613 return (fup);
614 return ((struct fileusage *)0);
615 }
616 /*
617 * Lookup and remove an id of a specific type.
618 */
619 struct fileusage *
620 qremove(id, type)
621 u_long id;
622 int type;
623 {
624 struct fileusage *fup, **fupp;
625
626 for (fupp = &fuhead[type][id & (FUHASH-1)]; *fupp != 0;) {
627 fup = *fupp;
628 if (fup->fu_id == id) {
629 *fupp = fup->fu_next;
630 return (fup);
631 }
632 fupp = &fup->fu_next;
633 }
634 return ((struct fileusage *)0);
635 }
636
637 /*
638 * Add a new file usage id if it does not already exist.
639 */
640 struct fileusage *
641 addid(id, type, name)
642 u_long id;
643 int type;
644 const char *name;
645 {
646 struct fileusage *fup, **fhp;
647 struct group *gr = NULL;
648 struct passwd *pw = NULL;
649 int len;
650
651 if ((fup = lookup(id, type)) != NULL) {
652 return (fup);
653 }
654 if (name == NULL) {
655 switch(type) {
656 case GRPQUOTA:
657 gr = getgrgid(id);
658
659 if (gr != NULL)
660 name = gr->gr_name;
661 break;
662 case USRQUOTA:
663 pw = getpwuid(id);
664 if (pw)
665 name = pw->pw_name;
666 break;
667 default:
668 errx(1, "unknown quota type %d\n", type);
669 }
670 }
671
672 if (name)
673 len = strlen(name);
674 else
675 len = 10;
676 if ((fup = (struct fileusage *)calloc(1, sizeof(*fup) + len)) == NULL) {
677 fprintf(stderr, "out of memory for fileusage structures\n");
678 exit(1);
679 }
680 fhp = &fuhead[type][id & (FUHASH - 1)];
681 fup->fu_next = *fhp;
682 *fhp = fup;
683 fup->fu_id = id;
684 if (id > highid[type])
685 highid[type] = id;
686 if (name) {
687 memmove(fup->fu_name, name, len + 1);
688 } else {
689 sprintf(fup->fu_name, "%lu", (u_long)id);
690 }
691 fup->fu_q2e = defaultq2e[type];
692 return (fup);
693 }
694