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