Deleted Added
full compact
quotacheck.c (166458) quotacheck.c (166485)
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 * 4. 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#if 0
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1980, 1990, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
42#endif /* not lint */
43#endif
44#include <sys/cdefs.h>
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 * 4. 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#if 0
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1980, 1990, 1993\n\
37 The Regents of the University of California. All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
42#endif /* not lint */
43#endif
44#include <sys/cdefs.h>
45__FBSDID("$FreeBSD: head/sbin/quotacheck/quotacheck.c 166458 2007-02-03 11:20:28Z mpp $");
45__FBSDID("$FreeBSD: head/sbin/quotacheck/quotacheck.c 166485 2007-02-04 06:33:15Z mpp $");
46
47/*
48 * Fix up / report on disk quotas & usage
49 */
50#include <sys/param.h>
51#include <sys/disklabel.h>
46
47/*
48 * Fix up / report on disk quotas & usage
49 */
50#include <sys/param.h>
51#include <sys/disklabel.h>
52#include <sys/mount.h>
52#include <sys/stat.h>
53
54#include <ufs/ufs/dinode.h>
55#include <ufs/ufs/quota.h>
56#include <ufs/ffs/fs.h>
57
58#include <err.h>
59#include <errno.h>
60#include <fcntl.h>
61#include <fstab.h>
62#include <grp.h>
63#include <pwd.h>
64#include <stdio.h>
65#include <stdlib.h>
66#include <string.h>
67#include <unistd.h>
68
69char *qfname = QUOTAFILENAME;
70char *qfextension[] = INITQFNAMES;
71char *quotagroup = QUOTAGROUP;
72
73union {
74 struct fs sblk;
75 char dummy[MAXBSIZE];
76} sb_un;
77#define sblock sb_un.sblk
78union {
79 struct cg cgblk;
80 char dummy[MAXBSIZE];
81} cg_un;
82#define cgblk cg_un.cgblk
83long dev_bsize = 1;
84ino_t maxino;
85
86union dinode {
87 struct ufs1_dinode dp1;
88 struct ufs2_dinode dp2;
89};
90#define DIP(dp, field) \
91 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
92 (dp)->dp1.field : (dp)->dp2.field)
93
94struct quotaname {
95 long flags;
96 char grpqfname[PATH_MAX];
97 char usrqfname[PATH_MAX];
98};
99#define HASUSR 1
100#define HASGRP 2
101
102struct fileusage {
103 struct fileusage *fu_next;
104 u_long fu_curinodes;
105 u_long fu_curblocks;
106 u_long fu_id;
107 char fu_name[1];
108 /* actually bigger */
109};
110#define FUHASH 1024 /* must be power of two */
111struct fileusage *fuhead[MAXQUOTAS][FUHASH];
112
113int aflag; /* all file systems */
114int gflag; /* check group quotas */
115int uflag; /* check user quotas */
116int vflag; /* verbose */
117int fi; /* open disk file descriptor */
118
119struct fileusage *
120 addid(u_long, int, char *, char *);
121char *blockcheck(char *);
122void bread(ufs2_daddr_t, char *, long);
123extern int checkfstab(int, int, void * (*)(struct fstab *),
124 int (*)(char *, char *, struct quotaname *));
125int chkquota(char *, char *, struct quotaname *);
126void freeinodebuf(void);
127union dinode *
128 getnextinode(ino_t);
129int getquotagid(void);
130int hasquota(struct fstab *, int, char **);
131struct fileusage *
132 lookup(u_long, int);
133void *needchk(struct fstab *);
134int oneof(char *, char*[], int);
135void printchanges(char *, int, struct dqblk *, struct fileusage *, u_long);
136void setinodebuf(ino_t);
137int update(char *, char *, int);
138void usage(void);
139
140int
141main(argc, argv)
142 int argc;
143 char *argv[];
144{
145 struct fstab *fs;
146 struct passwd *pw;
147 struct group *gr;
148 struct quotaname *auxdata;
149 int i, argnum, maxrun, errs, ch;
150 long done = 0;
151 char *name;
152
153 errs = maxrun = 0;
154 while ((ch = getopt(argc, argv, "aguvl:")) != -1) {
155 switch(ch) {
156 case 'a':
157 aflag++;
158 break;
159 case 'g':
160 gflag++;
161 break;
162 case 'u':
163 uflag++;
164 break;
165 case 'v':
166 vflag++;
167 break;
168 case 'l':
169 maxrun = atoi(optarg);
170 break;
171 default:
172 usage();
173 }
174 }
175 argc -= optind;
176 argv += optind;
177 if ((argc == 0 && !aflag) || (argc > 0 && aflag))
178 usage();
179 if (!gflag && !uflag) {
180 gflag++;
181 uflag++;
182 }
183 if (gflag) {
184 setgrent();
185 while ((gr = getgrent()) != NULL)
186 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name,
187 NULL);
188 endgrent();
189 }
190 if (uflag) {
191 setpwent();
192 while ((pw = getpwent()) != NULL)
193 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name,
194 NULL);
195 endpwent();
196 }
197 /*
198 * Setting maxrun (-l) makes no sense without the -a flag.
199 * Historically this was never an error, so we just warn.
200 */
201 if (maxrun > 0 && !aflag)
202 warnx("ignoring -l without -a");
203 if (aflag)
204 exit(checkfstab(1, maxrun, needchk, chkquota));
205 if (setfsent() == 0)
206 errx(1, "%s: can't open", FSTAB);
207 while ((fs = getfsent()) != NULL) {
208 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
209 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
210 (auxdata = needchk(fs)) &&
211 (name = blockcheck(fs->fs_spec))) {
212 done |= 1 << argnum;
213 errs += chkquota(name, fs->fs_file, auxdata);
214 }
215 }
216 endfsent();
217 for (i = 0; i < argc; i++)
218 if ((done & (1 << i)) == 0)
219 fprintf(stderr, "%s not found in %s\n",
220 argv[i], FSTAB);
221 exit(errs);
222}
223
224void
225usage()
226{
227 (void)fprintf(stderr, "%s\n%s\n",
228 "usage: quotacheck [-guv] [-l maxrun] -a",
229 " quotacheck [-guv] filesystem ...");
230 exit(1);
231}
232
233void *
234needchk(fs)
235 struct fstab *fs;
236{
237 struct quotaname *qnp;
238 char *qfnp;
239
240 if (strcmp(fs->fs_vfstype, "ufs") ||
241 strcmp(fs->fs_type, FSTAB_RW))
242 return (NULL);
243 if ((qnp = malloc(sizeof(*qnp))) == NULL)
244 errx(1, "malloc failed");
245 qnp->flags = 0;
246 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) {
247 strcpy(qnp->grpqfname, qfnp);
248 qnp->flags |= HASGRP;
249 }
250 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) {
251 strcpy(qnp->usrqfname, qfnp);
252 qnp->flags |= HASUSR;
253 }
254 if (qnp->flags)
255 return (qnp);
256 free(qnp);
257 return (NULL);
258}
259
260/*
261 * Possible superblock locations ordered from most to least likely.
262 */
263static int sblock_try[] = SBLOCKSEARCH;
264
265/*
266 * Scan the specified file system to check quota(s) present on it.
267 */
268int
269chkquota(fsname, mntpt, qnp)
270 char *fsname, *mntpt;
271 struct quotaname *qnp;
272{
273 struct fileusage *fup;
274 union dinode *dp;
275 int cg, i, mode, errs = 0;
276 ino_t ino, inosused, userino = 0, groupino = 0;
277 char *cp;
278 struct stat sb;
279
280 if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
281 warn("%s", fsname);
282 return (1);
283 }
284 if (vflag) {
285 (void)printf("*** Checking ");
286 if (qnp->flags & HASUSR)
287 (void)printf("%s%s", qfextension[USRQUOTA],
288 (qnp->flags & HASGRP) ? " and " : "");
289 if (qnp->flags & HASGRP)
290 (void)printf("%s", qfextension[GRPQUOTA]);
291 (void)printf(" quotas for %s (%s)\n", fsname, mntpt);
292 }
293 if (qnp->flags & HASUSR) {
294 if (stat(qnp->usrqfname, &sb) == 0)
295 userino = sb.st_ino;
296 }
297 if (qnp->flags & HASGRP) {
298 if (stat(qnp->grpqfname, &sb) == 0)
299 groupino = sb.st_ino;
300 }
301 sync();
302 dev_bsize = 1;
303 for (i = 0; sblock_try[i] != -1; i++) {
304 bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE);
305 if ((sblock.fs_magic == FS_UFS1_MAGIC ||
306 (sblock.fs_magic == FS_UFS2_MAGIC &&
307 sblock.fs_sblockloc == sblock_try[i])) &&
308 sblock.fs_bsize <= MAXBSIZE &&
309 sblock.fs_bsize >= sizeof(struct fs))
310 break;
311 }
312 if (sblock_try[i] == -1) {
313 warn("Cannot find file system superblock");
314 return (1);
315 }
316 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
317 maxino = sblock.fs_ncg * sblock.fs_ipg;
318 for (cg = 0; cg < sblock.fs_ncg; cg++) {
319 ino = cg * sblock.fs_ipg;
320 setinodebuf(ino);
321 bread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk),
322 sblock.fs_cgsize);
323 if (sblock.fs_magic == FS_UFS2_MAGIC)
324 inosused = cgblk.cg_initediblk;
325 else
326 inosused = sblock.fs_ipg;
327 /*
328 * If we are using soft updates, then we can trust the
329 * cylinder group inode allocation maps to tell us which
330 * inodes are allocated. We will scan the used inode map
331 * to find the inodes that are really in use, and then
332 * read only those inodes in from disk.
333 */
334 if (sblock.fs_flags & FS_DOSOFTDEP) {
335 if (!cg_chkmagic(&cgblk))
336 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg);
337 cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT];
338 for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
339 if (*cp == 0)
340 continue;
341 for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
342 if (*cp & i)
343 break;
344 inosused--;
345 }
346 break;
347 }
348 if (inosused <= 0)
349 continue;
350 }
351 for (i = 0; i < inosused; i++, ino++) {
352 if ((dp = getnextinode(ino)) == NULL || ino < ROOTINO ||
353 (mode = DIP(dp, di_mode) & IFMT) == 0)
354 continue;
355 /*
356 * XXX: Do not account for UIDs or GIDs that appear
357 * to be negative to prevent generating 100GB+
358 * quota files.
359 */
360 if ((int)DIP(dp, di_uid) < 0 ||
361 (int)DIP(dp, di_gid) < 0) {
362 if (vflag) {
363 if (aflag)
364 (void)printf("%s: ", mntpt);
365 (void)printf("out of range UID/GID (%u/%u) ino=%u\n",
366 DIP(dp, di_uid), DIP(dp,di_gid),
367 ino);
368 }
369 continue;
370 }
371
372 /*
373 * Do not account for file system snapshot files
374 * or the actual quota data files to be consistent
375 * with how they are handled inside the kernel.
376 */
377#ifdef SF_SNAPSHOT
378 if (DIP(dp, di_flags) & SF_SNAPSHOT)
379 continue;
380#endif
381 if (ino == userino || ino == groupino)
382 continue;
383 if (qnp->flags & HASGRP) {
384 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA,
385 (char *)0, mntpt);
386 fup->fu_curinodes++;
387 if (mode == IFREG || mode == IFDIR ||
388 mode == IFLNK)
389 fup->fu_curblocks += DIP(dp, di_blocks);
390 }
391 if (qnp->flags & HASUSR) {
392 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA,
393 (char *)0, mntpt);
394 fup->fu_curinodes++;
395 if (mode == IFREG || mode == IFDIR ||
396 mode == IFLNK)
397 fup->fu_curblocks += DIP(dp, di_blocks);
398 }
399 }
400 }
401 freeinodebuf();
402 if (qnp->flags & HASUSR)
403 errs += update(mntpt, qnp->usrqfname, USRQUOTA);
404 if (qnp->flags & HASGRP)
405 errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
406 close(fi);
407 return (errs);
408}
409
410/*
411 * Update a specified quota file.
412 */
413int
414update(fsname, quotafile, type)
415 char *fsname, *quotafile;
416 int type;
417{
418 struct fileusage *fup;
419 FILE *qfi, *qfo;
420 u_long id, lastid, highid = 0;
421 off_t offset;
422 int i;
423 struct dqblk dqbuf;
424 struct stat sb;
425 static int warned = 0;
426 static struct dqblk zerodqbuf;
427 static struct fileusage zerofileusage;
428
429 if ((qfo = fopen(quotafile, "r+")) == NULL) {
430 if (errno == ENOENT)
431 qfo = fopen(quotafile, "w+");
432 if (qfo) {
433 warnx("creating quota file %s", quotafile);
434#define MODE (S_IRUSR|S_IWUSR|S_IRGRP)
435 (void) fchown(fileno(qfo), getuid(), getquotagid());
436 (void) fchmod(fileno(qfo), MODE);
437 } else {
438 warn("%s", quotafile);
439 return (1);
440 }
441 }
442 if ((qfi = fopen(quotafile, "r")) == NULL) {
443 warn("%s", quotafile);
444 (void) fclose(qfo);
445 return (1);
446 }
447 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 &&
448 errno == EOPNOTSUPP && !warned && vflag) {
449 warned++;
450 (void)printf("*** Warning: %s\n",
451 "Quotas are not compiled into this kernel");
452 }
453 if (fstat(fileno(qfi), &sb) < 0) {
454 warn("Cannot fstat quota file %s\n", quotafile);
455 (void) fclose(qfo);
456 (void) fclose(qfi);
457 return (1);
458 }
459 if ((sb.st_size % sizeof(struct dqblk)) != 0)
460 warn("%s size is not a multiple of dqblk\n", quotafile);
461
462 /*
463 * Scan the on-disk quota file and record any usage changes.
464 */
465
466 if (sb.st_size != 0)
467 lastid = (sb.st_size / sizeof(struct dqblk)) - 1;
468 else
469 lastid = 0;
470 for (id = 0, offset = 0; id <= lastid;
471 id++, offset += sizeof(struct dqblk)) {
472 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
473 dqbuf = zerodqbuf;
474 if ((fup = lookup(id, type)) == NULL)
475 fup = &zerofileusage;
476 if (fup->fu_curinodes || fup->fu_curblocks ||
477 dqbuf.dqb_bsoftlimit || dqbuf.dqb_bhardlimit ||
478 dqbuf.dqb_isoftlimit || dqbuf.dqb_ihardlimit)
479 highid = id;
480 if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
481 dqbuf.dqb_curblocks == fup->fu_curblocks) {
482 fup->fu_curinodes = 0;
483 fup->fu_curblocks = 0;
484 continue;
485 }
486 printchanges(fsname, type, &dqbuf, fup, id);
487 /*
488 * Reset time limit if have a soft limit and were
489 * previously under it, but are now over it.
490 */
491 if (dqbuf.dqb_bsoftlimit && id != 0 &&
492 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
493 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit)
494 dqbuf.dqb_btime = 0;
495 if (dqbuf.dqb_isoftlimit && id != 0 &&
496 dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
497 fup->fu_curinodes >= dqbuf.dqb_isoftlimit)
498 dqbuf.dqb_itime = 0;
499 dqbuf.dqb_curinodes = fup->fu_curinodes;
500 dqbuf.dqb_curblocks = fup->fu_curblocks;
501 if (fseeko(qfo, offset, SEEK_SET) < 0) {
502 warn("%s: seek failed", quotafile);
503 return(1);
504 }
505 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
506 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
507 (caddr_t)&dqbuf);
508 fup->fu_curinodes = 0;
509 fup->fu_curblocks = 0;
510 }
511
512 /*
513 * Walk the hash table looking for ids with non-zero usage
514 * that are not currently recorded in the quota file. E.g.
515 * ids that are past the end of the current file.
516 */
517
518 for (i = 0; i < FUHASH; i++) {
519 for (fup = fuhead[type][i]; fup != NULL; fup = fup->fu_next) {
520 if (fup->fu_id <= lastid)
521 continue;
522 if (fup->fu_curinodes == 0 && fup->fu_curblocks == 0)
523 continue;
524 bzero(&dqbuf, sizeof(struct dqblk));
525 if (fup->fu_id > highid)
526 highid = fup->fu_id;
527 printchanges(fsname, type, &dqbuf, fup, id);
528 dqbuf.dqb_curinodes = fup->fu_curinodes;
529 dqbuf.dqb_curblocks = fup->fu_curblocks;
530 offset = (off_t)fup->fu_id * sizeof(struct dqblk);
531 if (fseeko(qfo, offset, SEEK_SET) < 0) {
532 warn("%s: seek failed", quotafile);
533 return(1);
534 }
535 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
536 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
537 (caddr_t)&dqbuf);
538 fup->fu_curinodes = 0;
539 fup->fu_curblocks = 0;
540 }
541 }
542 fclose(qfi);
543 fflush(qfo);
544 ftruncate(fileno(qfo),
545 (((off_t)highid + 1) * sizeof(struct dqblk)));
546 fclose(qfo);
547 return (0);
548}
549
550/*
551 * Check to see if target appears in list of size cnt.
552 */
553int
554oneof(target, list, cnt)
555 char *target, *list[];
556 int cnt;
557{
558 int i;
559
560 for (i = 0; i < cnt; i++)
561 if (strcmp(target, list[i]) == 0)
562 return (i);
563 return (-1);
564}
565
566/*
567 * Determine the group identifier for quota files.
568 */
569int
570getquotagid()
571{
572 struct group *gr;
573
574 if ((gr = getgrnam(quotagroup)) != NULL)
575 return (gr->gr_gid);
576 return (-1);
577}
578
579/*
580 * Check to see if a particular quota is to be enabled.
581 */
582int
583hasquota(fs, type, qfnamep)
584 struct fstab *fs;
585 int type;
586 char **qfnamep;
587{
588 char *opt;
589 char *cp;
53#include <sys/stat.h>
54
55#include <ufs/ufs/dinode.h>
56#include <ufs/ufs/quota.h>
57#include <ufs/ffs/fs.h>
58
59#include <err.h>
60#include <errno.h>
61#include <fcntl.h>
62#include <fstab.h>
63#include <grp.h>
64#include <pwd.h>
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
68#include <unistd.h>
69
70char *qfname = QUOTAFILENAME;
71char *qfextension[] = INITQFNAMES;
72char *quotagroup = QUOTAGROUP;
73
74union {
75 struct fs sblk;
76 char dummy[MAXBSIZE];
77} sb_un;
78#define sblock sb_un.sblk
79union {
80 struct cg cgblk;
81 char dummy[MAXBSIZE];
82} cg_un;
83#define cgblk cg_un.cgblk
84long dev_bsize = 1;
85ino_t maxino;
86
87union dinode {
88 struct ufs1_dinode dp1;
89 struct ufs2_dinode dp2;
90};
91#define DIP(dp, field) \
92 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \
93 (dp)->dp1.field : (dp)->dp2.field)
94
95struct quotaname {
96 long flags;
97 char grpqfname[PATH_MAX];
98 char usrqfname[PATH_MAX];
99};
100#define HASUSR 1
101#define HASGRP 2
102
103struct fileusage {
104 struct fileusage *fu_next;
105 u_long fu_curinodes;
106 u_long fu_curblocks;
107 u_long fu_id;
108 char fu_name[1];
109 /* actually bigger */
110};
111#define FUHASH 1024 /* must be power of two */
112struct fileusage *fuhead[MAXQUOTAS][FUHASH];
113
114int aflag; /* all file systems */
115int gflag; /* check group quotas */
116int uflag; /* check user quotas */
117int vflag; /* verbose */
118int fi; /* open disk file descriptor */
119
120struct fileusage *
121 addid(u_long, int, char *, char *);
122char *blockcheck(char *);
123void bread(ufs2_daddr_t, char *, long);
124extern int checkfstab(int, int, void * (*)(struct fstab *),
125 int (*)(char *, char *, struct quotaname *));
126int chkquota(char *, char *, struct quotaname *);
127void freeinodebuf(void);
128union dinode *
129 getnextinode(ino_t);
130int getquotagid(void);
131int hasquota(struct fstab *, int, char **);
132struct fileusage *
133 lookup(u_long, int);
134void *needchk(struct fstab *);
135int oneof(char *, char*[], int);
136void printchanges(char *, int, struct dqblk *, struct fileusage *, u_long);
137void setinodebuf(ino_t);
138int update(char *, char *, int);
139void usage(void);
140
141int
142main(argc, argv)
143 int argc;
144 char *argv[];
145{
146 struct fstab *fs;
147 struct passwd *pw;
148 struct group *gr;
149 struct quotaname *auxdata;
150 int i, argnum, maxrun, errs, ch;
151 long done = 0;
152 char *name;
153
154 errs = maxrun = 0;
155 while ((ch = getopt(argc, argv, "aguvl:")) != -1) {
156 switch(ch) {
157 case 'a':
158 aflag++;
159 break;
160 case 'g':
161 gflag++;
162 break;
163 case 'u':
164 uflag++;
165 break;
166 case 'v':
167 vflag++;
168 break;
169 case 'l':
170 maxrun = atoi(optarg);
171 break;
172 default:
173 usage();
174 }
175 }
176 argc -= optind;
177 argv += optind;
178 if ((argc == 0 && !aflag) || (argc > 0 && aflag))
179 usage();
180 if (!gflag && !uflag) {
181 gflag++;
182 uflag++;
183 }
184 if (gflag) {
185 setgrent();
186 while ((gr = getgrent()) != NULL)
187 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name,
188 NULL);
189 endgrent();
190 }
191 if (uflag) {
192 setpwent();
193 while ((pw = getpwent()) != NULL)
194 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name,
195 NULL);
196 endpwent();
197 }
198 /*
199 * Setting maxrun (-l) makes no sense without the -a flag.
200 * Historically this was never an error, so we just warn.
201 */
202 if (maxrun > 0 && !aflag)
203 warnx("ignoring -l without -a");
204 if (aflag)
205 exit(checkfstab(1, maxrun, needchk, chkquota));
206 if (setfsent() == 0)
207 errx(1, "%s: can't open", FSTAB);
208 while ((fs = getfsent()) != NULL) {
209 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
210 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
211 (auxdata = needchk(fs)) &&
212 (name = blockcheck(fs->fs_spec))) {
213 done |= 1 << argnum;
214 errs += chkquota(name, fs->fs_file, auxdata);
215 }
216 }
217 endfsent();
218 for (i = 0; i < argc; i++)
219 if ((done & (1 << i)) == 0)
220 fprintf(stderr, "%s not found in %s\n",
221 argv[i], FSTAB);
222 exit(errs);
223}
224
225void
226usage()
227{
228 (void)fprintf(stderr, "%s\n%s\n",
229 "usage: quotacheck [-guv] [-l maxrun] -a",
230 " quotacheck [-guv] filesystem ...");
231 exit(1);
232}
233
234void *
235needchk(fs)
236 struct fstab *fs;
237{
238 struct quotaname *qnp;
239 char *qfnp;
240
241 if (strcmp(fs->fs_vfstype, "ufs") ||
242 strcmp(fs->fs_type, FSTAB_RW))
243 return (NULL);
244 if ((qnp = malloc(sizeof(*qnp))) == NULL)
245 errx(1, "malloc failed");
246 qnp->flags = 0;
247 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) {
248 strcpy(qnp->grpqfname, qfnp);
249 qnp->flags |= HASGRP;
250 }
251 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) {
252 strcpy(qnp->usrqfname, qfnp);
253 qnp->flags |= HASUSR;
254 }
255 if (qnp->flags)
256 return (qnp);
257 free(qnp);
258 return (NULL);
259}
260
261/*
262 * Possible superblock locations ordered from most to least likely.
263 */
264static int sblock_try[] = SBLOCKSEARCH;
265
266/*
267 * Scan the specified file system to check quota(s) present on it.
268 */
269int
270chkquota(fsname, mntpt, qnp)
271 char *fsname, *mntpt;
272 struct quotaname *qnp;
273{
274 struct fileusage *fup;
275 union dinode *dp;
276 int cg, i, mode, errs = 0;
277 ino_t ino, inosused, userino = 0, groupino = 0;
278 char *cp;
279 struct stat sb;
280
281 if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
282 warn("%s", fsname);
283 return (1);
284 }
285 if (vflag) {
286 (void)printf("*** Checking ");
287 if (qnp->flags & HASUSR)
288 (void)printf("%s%s", qfextension[USRQUOTA],
289 (qnp->flags & HASGRP) ? " and " : "");
290 if (qnp->flags & HASGRP)
291 (void)printf("%s", qfextension[GRPQUOTA]);
292 (void)printf(" quotas for %s (%s)\n", fsname, mntpt);
293 }
294 if (qnp->flags & HASUSR) {
295 if (stat(qnp->usrqfname, &sb) == 0)
296 userino = sb.st_ino;
297 }
298 if (qnp->flags & HASGRP) {
299 if (stat(qnp->grpqfname, &sb) == 0)
300 groupino = sb.st_ino;
301 }
302 sync();
303 dev_bsize = 1;
304 for (i = 0; sblock_try[i] != -1; i++) {
305 bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE);
306 if ((sblock.fs_magic == FS_UFS1_MAGIC ||
307 (sblock.fs_magic == FS_UFS2_MAGIC &&
308 sblock.fs_sblockloc == sblock_try[i])) &&
309 sblock.fs_bsize <= MAXBSIZE &&
310 sblock.fs_bsize >= sizeof(struct fs))
311 break;
312 }
313 if (sblock_try[i] == -1) {
314 warn("Cannot find file system superblock");
315 return (1);
316 }
317 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
318 maxino = sblock.fs_ncg * sblock.fs_ipg;
319 for (cg = 0; cg < sblock.fs_ncg; cg++) {
320 ino = cg * sblock.fs_ipg;
321 setinodebuf(ino);
322 bread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk),
323 sblock.fs_cgsize);
324 if (sblock.fs_magic == FS_UFS2_MAGIC)
325 inosused = cgblk.cg_initediblk;
326 else
327 inosused = sblock.fs_ipg;
328 /*
329 * If we are using soft updates, then we can trust the
330 * cylinder group inode allocation maps to tell us which
331 * inodes are allocated. We will scan the used inode map
332 * to find the inodes that are really in use, and then
333 * read only those inodes in from disk.
334 */
335 if (sblock.fs_flags & FS_DOSOFTDEP) {
336 if (!cg_chkmagic(&cgblk))
337 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg);
338 cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT];
339 for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) {
340 if (*cp == 0)
341 continue;
342 for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) {
343 if (*cp & i)
344 break;
345 inosused--;
346 }
347 break;
348 }
349 if (inosused <= 0)
350 continue;
351 }
352 for (i = 0; i < inosused; i++, ino++) {
353 if ((dp = getnextinode(ino)) == NULL || ino < ROOTINO ||
354 (mode = DIP(dp, di_mode) & IFMT) == 0)
355 continue;
356 /*
357 * XXX: Do not account for UIDs or GIDs that appear
358 * to be negative to prevent generating 100GB+
359 * quota files.
360 */
361 if ((int)DIP(dp, di_uid) < 0 ||
362 (int)DIP(dp, di_gid) < 0) {
363 if (vflag) {
364 if (aflag)
365 (void)printf("%s: ", mntpt);
366 (void)printf("out of range UID/GID (%u/%u) ino=%u\n",
367 DIP(dp, di_uid), DIP(dp,di_gid),
368 ino);
369 }
370 continue;
371 }
372
373 /*
374 * Do not account for file system snapshot files
375 * or the actual quota data files to be consistent
376 * with how they are handled inside the kernel.
377 */
378#ifdef SF_SNAPSHOT
379 if (DIP(dp, di_flags) & SF_SNAPSHOT)
380 continue;
381#endif
382 if (ino == userino || ino == groupino)
383 continue;
384 if (qnp->flags & HASGRP) {
385 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA,
386 (char *)0, mntpt);
387 fup->fu_curinodes++;
388 if (mode == IFREG || mode == IFDIR ||
389 mode == IFLNK)
390 fup->fu_curblocks += DIP(dp, di_blocks);
391 }
392 if (qnp->flags & HASUSR) {
393 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA,
394 (char *)0, mntpt);
395 fup->fu_curinodes++;
396 if (mode == IFREG || mode == IFDIR ||
397 mode == IFLNK)
398 fup->fu_curblocks += DIP(dp, di_blocks);
399 }
400 }
401 }
402 freeinodebuf();
403 if (qnp->flags & HASUSR)
404 errs += update(mntpt, qnp->usrqfname, USRQUOTA);
405 if (qnp->flags & HASGRP)
406 errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
407 close(fi);
408 return (errs);
409}
410
411/*
412 * Update a specified quota file.
413 */
414int
415update(fsname, quotafile, type)
416 char *fsname, *quotafile;
417 int type;
418{
419 struct fileusage *fup;
420 FILE *qfi, *qfo;
421 u_long id, lastid, highid = 0;
422 off_t offset;
423 int i;
424 struct dqblk dqbuf;
425 struct stat sb;
426 static int warned = 0;
427 static struct dqblk zerodqbuf;
428 static struct fileusage zerofileusage;
429
430 if ((qfo = fopen(quotafile, "r+")) == NULL) {
431 if (errno == ENOENT)
432 qfo = fopen(quotafile, "w+");
433 if (qfo) {
434 warnx("creating quota file %s", quotafile);
435#define MODE (S_IRUSR|S_IWUSR|S_IRGRP)
436 (void) fchown(fileno(qfo), getuid(), getquotagid());
437 (void) fchmod(fileno(qfo), MODE);
438 } else {
439 warn("%s", quotafile);
440 return (1);
441 }
442 }
443 if ((qfi = fopen(quotafile, "r")) == NULL) {
444 warn("%s", quotafile);
445 (void) fclose(qfo);
446 return (1);
447 }
448 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 &&
449 errno == EOPNOTSUPP && !warned && vflag) {
450 warned++;
451 (void)printf("*** Warning: %s\n",
452 "Quotas are not compiled into this kernel");
453 }
454 if (fstat(fileno(qfi), &sb) < 0) {
455 warn("Cannot fstat quota file %s\n", quotafile);
456 (void) fclose(qfo);
457 (void) fclose(qfi);
458 return (1);
459 }
460 if ((sb.st_size % sizeof(struct dqblk)) != 0)
461 warn("%s size is not a multiple of dqblk\n", quotafile);
462
463 /*
464 * Scan the on-disk quota file and record any usage changes.
465 */
466
467 if (sb.st_size != 0)
468 lastid = (sb.st_size / sizeof(struct dqblk)) - 1;
469 else
470 lastid = 0;
471 for (id = 0, offset = 0; id <= lastid;
472 id++, offset += sizeof(struct dqblk)) {
473 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
474 dqbuf = zerodqbuf;
475 if ((fup = lookup(id, type)) == NULL)
476 fup = &zerofileusage;
477 if (fup->fu_curinodes || fup->fu_curblocks ||
478 dqbuf.dqb_bsoftlimit || dqbuf.dqb_bhardlimit ||
479 dqbuf.dqb_isoftlimit || dqbuf.dqb_ihardlimit)
480 highid = id;
481 if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
482 dqbuf.dqb_curblocks == fup->fu_curblocks) {
483 fup->fu_curinodes = 0;
484 fup->fu_curblocks = 0;
485 continue;
486 }
487 printchanges(fsname, type, &dqbuf, fup, id);
488 /*
489 * Reset time limit if have a soft limit and were
490 * previously under it, but are now over it.
491 */
492 if (dqbuf.dqb_bsoftlimit && id != 0 &&
493 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
494 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit)
495 dqbuf.dqb_btime = 0;
496 if (dqbuf.dqb_isoftlimit && id != 0 &&
497 dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
498 fup->fu_curinodes >= dqbuf.dqb_isoftlimit)
499 dqbuf.dqb_itime = 0;
500 dqbuf.dqb_curinodes = fup->fu_curinodes;
501 dqbuf.dqb_curblocks = fup->fu_curblocks;
502 if (fseeko(qfo, offset, SEEK_SET) < 0) {
503 warn("%s: seek failed", quotafile);
504 return(1);
505 }
506 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
507 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
508 (caddr_t)&dqbuf);
509 fup->fu_curinodes = 0;
510 fup->fu_curblocks = 0;
511 }
512
513 /*
514 * Walk the hash table looking for ids with non-zero usage
515 * that are not currently recorded in the quota file. E.g.
516 * ids that are past the end of the current file.
517 */
518
519 for (i = 0; i < FUHASH; i++) {
520 for (fup = fuhead[type][i]; fup != NULL; fup = fup->fu_next) {
521 if (fup->fu_id <= lastid)
522 continue;
523 if (fup->fu_curinodes == 0 && fup->fu_curblocks == 0)
524 continue;
525 bzero(&dqbuf, sizeof(struct dqblk));
526 if (fup->fu_id > highid)
527 highid = fup->fu_id;
528 printchanges(fsname, type, &dqbuf, fup, id);
529 dqbuf.dqb_curinodes = fup->fu_curinodes;
530 dqbuf.dqb_curblocks = fup->fu_curblocks;
531 offset = (off_t)fup->fu_id * sizeof(struct dqblk);
532 if (fseeko(qfo, offset, SEEK_SET) < 0) {
533 warn("%s: seek failed", quotafile);
534 return(1);
535 }
536 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
537 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
538 (caddr_t)&dqbuf);
539 fup->fu_curinodes = 0;
540 fup->fu_curblocks = 0;
541 }
542 }
543 fclose(qfi);
544 fflush(qfo);
545 ftruncate(fileno(qfo),
546 (((off_t)highid + 1) * sizeof(struct dqblk)));
547 fclose(qfo);
548 return (0);
549}
550
551/*
552 * Check to see if target appears in list of size cnt.
553 */
554int
555oneof(target, list, cnt)
556 char *target, *list[];
557 int cnt;
558{
559 int i;
560
561 for (i = 0; i < cnt; i++)
562 if (strcmp(target, list[i]) == 0)
563 return (i);
564 return (-1);
565}
566
567/*
568 * Determine the group identifier for quota files.
569 */
570int
571getquotagid()
572{
573 struct group *gr;
574
575 if ((gr = getgrnam(quotagroup)) != NULL)
576 return (gr->gr_gid);
577 return (-1);
578}
579
580/*
581 * Check to see if a particular quota is to be enabled.
582 */
583int
584hasquota(fs, type, qfnamep)
585 struct fstab *fs;
586 int type;
587 char **qfnamep;
588{
589 char *opt;
590 char *cp;
591 struct statfs sfb;
590 static char initname, usrname[100], grpname[100];
591 static char buf[BUFSIZ];
592
593 if (!initname) {
592 static char initname, usrname[100], grpname[100];
593 static char buf[BUFSIZ];
594
595 if (!initname) {
594 (void)snprintf(usrname, sizeof(usrname),
595 "%s%s", qfextension[USRQUOTA], qfname);
596 (void)snprintf(grpname, sizeof(grpname),
597 "%s%s", qfextension[GRPQUOTA], qfname);
596 (void)snprintf(usrname, sizeof(usrname), "%s%s",
597 qfextension[USRQUOTA], qfname);
598 (void)snprintf(grpname, sizeof(grpname), "%s%s",
599 qfextension[GRPQUOTA], qfname);
598 initname = 1;
599 }
600 strcpy(buf, fs->fs_mntops);
601 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
602 if ((cp = index(opt, '=')) != NULL)
603 *cp++ = '\0';
604 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
605 break;
606 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
607 break;
608 }
609 if (!opt)
610 return (0);
611 if (cp)
612 *qfnamep = cp;
613 else {
600 initname = 1;
601 }
602 strcpy(buf, fs->fs_mntops);
603 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
604 if ((cp = index(opt, '=')) != NULL)
605 *cp++ = '\0';
606 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
607 break;
608 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
609 break;
610 }
611 if (!opt)
612 return (0);
613 if (cp)
614 *qfnamep = cp;
615 else {
614 (void)snprintf(buf, sizeof(buf),
615 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
616 (void)snprintf(buf, sizeof(buf), "%s/%s.%s", fs->fs_file,
617 qfname, qfextension[type]);
616 *qfnamep = buf;
617 }
618 *qfnamep = buf;
619 }
620 if (statfs(fs->fs_file, &sfb) != 0) {
621 warn("cannot statfs mount point %s", fs->fs_file);
622 return (0);
623 }
624 if (strcmp(fs->fs_file, sfb.f_mntonname)) {
625 warnx("%s not mounted for %s quotas", fs->fs_file,
626 type == USRQUOTA ? "user" : "group");
627 return (0);
628 }
618 return (1);
619}
620
621/*
622 * Routines to manage the file usage table.
623 *
624 * Lookup an id of a specific type.
625 */
626struct fileusage *
627lookup(id, type)
628 u_long id;
629 int type;
630{
631 struct fileusage *fup;
632
633 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
634 if (fup->fu_id == id)
635 return (fup);
636 return (NULL);
637}
638
639/*
640 * Add a new file usage id if it does not already exist.
641 */
642struct fileusage *
643addid(id, type, name, fsname)
644 u_long id;
645 int type;
646 char *name;
647 char *fsname;
648{
649 struct fileusage *fup, **fhp;
650 int len;
651
652 if ((fup = lookup(id, type)) != NULL)
653 return (fup);
654 if (name)
655 len = strlen(name);
656 else
657 len = 0;
658 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
659 errx(1, "calloc failed");
660 fhp = &fuhead[type][id & (FUHASH - 1)];
661 fup->fu_next = *fhp;
662 *fhp = fup;
663 fup->fu_id = id;
664 if (name)
665 bcopy(name, fup->fu_name, len + 1);
666 else {
667 (void)sprintf(fup->fu_name, "%lu", id);
668 if (vflag) {
669 if (aflag && fsname != NULL)
670 (void)printf("%s: ", fsname);
671 printf("unknown %cid: %lu\n",
672 type == USRQUOTA ? 'u' : 'g', id);
673 }
674 }
675 return (fup);
676}
677
678/*
679 * Special purpose version of ginode used to optimize pass
680 * over all the inodes in numerical order.
681 */
682static ino_t nextino, lastinum, lastvalidinum;
683static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
684static caddr_t inodebuf;
685#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
686
687union dinode *
688getnextinode(ino_t inumber)
689{
690 long size;
691 ufs2_daddr_t dblk;
692 union dinode *dp;
693 static caddr_t nextinop;
694
695 if (inumber != nextino++ || inumber > lastvalidinum)
696 errx(1, "bad inode number %d to nextinode", inumber);
697 if (inumber >= lastinum) {
698 readcnt++;
699 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
700 if (readcnt % readpercg == 0) {
701 size = partialsize;
702 lastinum += partialcnt;
703 } else {
704 size = inobufsize;
705 lastinum += fullcnt;
706 }
707 /*
708 * If bread returns an error, it will already have zeroed
709 * out the buffer, so we do not need to do so here.
710 */
711 bread(dblk, inodebuf, size);
712 nextinop = inodebuf;
713 }
714 dp = (union dinode *)nextinop;
715 if (sblock.fs_magic == FS_UFS1_MAGIC)
716 nextinop += sizeof(struct ufs1_dinode);
717 else
718 nextinop += sizeof(struct ufs2_dinode);
719 return (dp);
720}
721
722/*
723 * Prepare to scan a set of inodes.
724 */
725void
726setinodebuf(ino_t inum)
727{
728
729 if (inum % sblock.fs_ipg != 0)
730 errx(1, "bad inode number %d to setinodebuf", inum);
731 lastvalidinum = inum + sblock.fs_ipg - 1;
732 nextino = inum;
733 lastinum = inum;
734 readcnt = 0;
735 if (inodebuf != NULL)
736 return;
737 inobufsize = blkroundup(&sblock, INOBUFSIZE);
738 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ?
739 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
740 readpercg = sblock.fs_ipg / fullcnt;
741 partialcnt = sblock.fs_ipg % fullcnt;
742 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ?
743 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
744 if (partialcnt != 0) {
745 readpercg++;
746 } else {
747 partialcnt = fullcnt;
748 partialsize = inobufsize;
749 }
750 if ((inodebuf = malloc((unsigned)inobufsize)) == NULL)
751 errx(1, "cannot allocate space for inode buffer");
752}
753
754/*
755 * Free up data structures used to scan inodes.
756 */
757void
758freeinodebuf()
759{
760
761 if (inodebuf != NULL)
762 free(inodebuf);
763 inodebuf = NULL;
764}
765
766/*
767 * Read specified disk blocks.
768 */
769void
770bread(bno, buf, cnt)
771 ufs2_daddr_t bno;
772 char *buf;
773 long cnt;
774{
775
776 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
777 read(fi, buf, cnt) != cnt)
778 errx(1, "bread failed on block %ld", (long)bno);
779}
780
781/*
782 * Display updated block and i-node counts.
783 */
784void
785printchanges(fsname, type, dp, fup, id)
786 char *fsname;
787 int type;
788 struct dqblk *dp;
789 struct fileusage *fup;
790 u_long id;
791{
792 if (!vflag)
793 return;
794 if (aflag)
795 (void)printf("%s: ", fsname);
796 if (fup->fu_name[0] == '\0')
797 (void)printf("%-8lu fixed ", id);
798 else
799 (void)printf("%-8s fixed ", fup->fu_name);
800 switch (type) {
801
802 case GRPQUOTA:
803 (void)printf("(group):");
804 break;
805
806 case USRQUOTA:
807 (void)printf("(user): ");
808 break;
809
810 default:
811 (void)printf("(unknown quota type %d)", type);
812 break;
813 }
814 if (dp->dqb_curinodes != fup->fu_curinodes)
815 (void)printf("\tinodes %lu -> %lu", (u_long)dp->dqb_curinodes,
816 (u_long)fup->fu_curinodes);
817 if (dp->dqb_curblocks != fup->fu_curblocks)
818 (void)printf("\tblocks %lu -> %lu",
819 (u_long)dp->dqb_curblocks,
820 (u_long)fup->fu_curblocks);
821 (void)printf("\n");
822}
629 return (1);
630}
631
632/*
633 * Routines to manage the file usage table.
634 *
635 * Lookup an id of a specific type.
636 */
637struct fileusage *
638lookup(id, type)
639 u_long id;
640 int type;
641{
642 struct fileusage *fup;
643
644 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
645 if (fup->fu_id == id)
646 return (fup);
647 return (NULL);
648}
649
650/*
651 * Add a new file usage id if it does not already exist.
652 */
653struct fileusage *
654addid(id, type, name, fsname)
655 u_long id;
656 int type;
657 char *name;
658 char *fsname;
659{
660 struct fileusage *fup, **fhp;
661 int len;
662
663 if ((fup = lookup(id, type)) != NULL)
664 return (fup);
665 if (name)
666 len = strlen(name);
667 else
668 len = 0;
669 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
670 errx(1, "calloc failed");
671 fhp = &fuhead[type][id & (FUHASH - 1)];
672 fup->fu_next = *fhp;
673 *fhp = fup;
674 fup->fu_id = id;
675 if (name)
676 bcopy(name, fup->fu_name, len + 1);
677 else {
678 (void)sprintf(fup->fu_name, "%lu", id);
679 if (vflag) {
680 if (aflag && fsname != NULL)
681 (void)printf("%s: ", fsname);
682 printf("unknown %cid: %lu\n",
683 type == USRQUOTA ? 'u' : 'g', id);
684 }
685 }
686 return (fup);
687}
688
689/*
690 * Special purpose version of ginode used to optimize pass
691 * over all the inodes in numerical order.
692 */
693static ino_t nextino, lastinum, lastvalidinum;
694static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
695static caddr_t inodebuf;
696#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
697
698union dinode *
699getnextinode(ino_t inumber)
700{
701 long size;
702 ufs2_daddr_t dblk;
703 union dinode *dp;
704 static caddr_t nextinop;
705
706 if (inumber != nextino++ || inumber > lastvalidinum)
707 errx(1, "bad inode number %d to nextinode", inumber);
708 if (inumber >= lastinum) {
709 readcnt++;
710 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
711 if (readcnt % readpercg == 0) {
712 size = partialsize;
713 lastinum += partialcnt;
714 } else {
715 size = inobufsize;
716 lastinum += fullcnt;
717 }
718 /*
719 * If bread returns an error, it will already have zeroed
720 * out the buffer, so we do not need to do so here.
721 */
722 bread(dblk, inodebuf, size);
723 nextinop = inodebuf;
724 }
725 dp = (union dinode *)nextinop;
726 if (sblock.fs_magic == FS_UFS1_MAGIC)
727 nextinop += sizeof(struct ufs1_dinode);
728 else
729 nextinop += sizeof(struct ufs2_dinode);
730 return (dp);
731}
732
733/*
734 * Prepare to scan a set of inodes.
735 */
736void
737setinodebuf(ino_t inum)
738{
739
740 if (inum % sblock.fs_ipg != 0)
741 errx(1, "bad inode number %d to setinodebuf", inum);
742 lastvalidinum = inum + sblock.fs_ipg - 1;
743 nextino = inum;
744 lastinum = inum;
745 readcnt = 0;
746 if (inodebuf != NULL)
747 return;
748 inobufsize = blkroundup(&sblock, INOBUFSIZE);
749 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ?
750 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
751 readpercg = sblock.fs_ipg / fullcnt;
752 partialcnt = sblock.fs_ipg % fullcnt;
753 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ?
754 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode));
755 if (partialcnt != 0) {
756 readpercg++;
757 } else {
758 partialcnt = fullcnt;
759 partialsize = inobufsize;
760 }
761 if ((inodebuf = malloc((unsigned)inobufsize)) == NULL)
762 errx(1, "cannot allocate space for inode buffer");
763}
764
765/*
766 * Free up data structures used to scan inodes.
767 */
768void
769freeinodebuf()
770{
771
772 if (inodebuf != NULL)
773 free(inodebuf);
774 inodebuf = NULL;
775}
776
777/*
778 * Read specified disk blocks.
779 */
780void
781bread(bno, buf, cnt)
782 ufs2_daddr_t bno;
783 char *buf;
784 long cnt;
785{
786
787 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
788 read(fi, buf, cnt) != cnt)
789 errx(1, "bread failed on block %ld", (long)bno);
790}
791
792/*
793 * Display updated block and i-node counts.
794 */
795void
796printchanges(fsname, type, dp, fup, id)
797 char *fsname;
798 int type;
799 struct dqblk *dp;
800 struct fileusage *fup;
801 u_long id;
802{
803 if (!vflag)
804 return;
805 if (aflag)
806 (void)printf("%s: ", fsname);
807 if (fup->fu_name[0] == '\0')
808 (void)printf("%-8lu fixed ", id);
809 else
810 (void)printf("%-8s fixed ", fup->fu_name);
811 switch (type) {
812
813 case GRPQUOTA:
814 (void)printf("(group):");
815 break;
816
817 case USRQUOTA:
818 (void)printf("(user): ");
819 break;
820
821 default:
822 (void)printf("(unknown quota type %d)", type);
823 break;
824 }
825 if (dp->dqb_curinodes != fup->fu_curinodes)
826 (void)printf("\tinodes %lu -> %lu", (u_long)dp->dqb_curinodes,
827 (u_long)fup->fu_curinodes);
828 if (dp->dqb_curblocks != fup->fu_curblocks)
829 (void)printf("\tblocks %lu -> %lu",
830 (u_long)dp->dqb_curblocks,
831 (u_long)fup->fu_curblocks);
832 (void)printf("\n");
833}