summaryrefslogtreecommitdiff
path: root/sbin/quotacheck
diff options
context:
space:
mode:
authorTheo de Raadt <deraadt@cvs.openbsd.org>1995-10-18 08:53:40 +0000
committerTheo de Raadt <deraadt@cvs.openbsd.org>1995-10-18 08:53:40 +0000
commitd6583bb2a13f329cf0332ef2570eb8bb8fc0e39c (patch)
treeece253b876159b39c620e62b6c9b1174642e070e /sbin/quotacheck
initial import of NetBSD tree
Diffstat (limited to 'sbin/quotacheck')
-rw-r--r--sbin/quotacheck/Makefile9
-rw-r--r--sbin/quotacheck/quotacheck.8159
-rw-r--r--sbin/quotacheck/quotacheck.c614
3 files changed, 782 insertions, 0 deletions
diff --git a/sbin/quotacheck/Makefile b/sbin/quotacheck/Makefile
new file mode 100644
index 00000000000..6a11131b2bd
--- /dev/null
+++ b/sbin/quotacheck/Makefile
@@ -0,0 +1,9 @@
+# $NetBSD: Makefile,v 1.5 1995/03/18 14:59:19 cgd Exp $
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+
+PROG= quotacheck
+SRCS= quotacheck.c preen.c
+MAN= quotacheck.8
+.PATH: ${.CURDIR}/../fsck
+
+.include <bsd.prog.mk>
diff --git a/sbin/quotacheck/quotacheck.8 b/sbin/quotacheck/quotacheck.8
new file mode 100644
index 00000000000..447c6c9f5ed
--- /dev/null
+++ b/sbin/quotacheck/quotacheck.8
@@ -0,0 +1,159 @@
+.\" $NetBSD: quotacheck.8,v 1.4 1995/03/18 14:59:20 cgd Exp $
+.\"
+.\" Copyright (c) 1983, 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Robert Elz at The University of Melbourne.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)quotacheck.8 8.1 (Berkeley) 6/5/93
+.\"
+.Dd June 5, 1993
+.Dt QUOTACHECK 8
+.Os BSD 4.2
+.Sh NAME
+.Nm quotacheck
+.Nd filesystem quota consistency checker
+.Sh SYNOPSIS
+.Nm quotacheck
+.Op Fl g
+.Op Fl u
+.Op Fl v
+.Ar filesystem Ar ...
+.Nm quotacheck
+.Op Fl g
+.Op Fl u
+.Op Fl v
+.Fl a
+.Sh DESCRIPTION
+.Nm Quotacheck
+examines each filesystem,
+builds a table of current disk usage,
+and compares this table against that recorded
+in the disk quota file for the filesystem.
+If any inconsistencies are detected, both the
+quota file and the current system copy of the
+incorrect quotas are updated (the latter only
+occurs if an active filesystem is checked).
+By default both user and group quotas are checked.
+.Pp
+Available options:
+.Bl -tag -width Ds
+.It Fl a
+If the
+.Fl a
+flag is supplied in place of any filesystem names,
+.Nm quotacheck
+will check all the filesystems indicated in
+.Pa /etc/fstab
+to be read-write with disk quotas.
+By default only the types of quotas listed in
+.Pa /etc/fstab
+are checked.
+.It Fl g
+Only group quotas listed in
+.Pa /etc/fstab
+are to be checked.
+.It Fl u
+Only user quotas listed in
+.Pa /etc/fstab
+are to be checked.
+.It Fl v
+.Nm quotacheck
+reports discrepancies between the
+calculated and recorded disk quotas.
+.El
+.Pp
+Specifying both
+.Fl g
+and
+.Fl u
+is equivalent to the default.
+Parallel passes are run on the filesystems required,
+using the pass numbers in
+.Pa /etc/fstab
+in an identical fashion to
+.Xr fsck 8 .
+.Pp
+Normally
+.Nm quotacheck
+operates silently.
+.Pp
+.Nm Quotacheck
+expects each filesystem to be checked to have a
+quota files named
+.Pa quota.user
+and
+.Pa quota.group
+which are located at the root of the associated file system.
+These defaults may be overridden in
+.Pa /etc/fstab .
+If a file is not present,
+.Nm quotacheck
+will create it.
+.Pp
+.Nm Quotacheck
+is normally run at boot time from the
+.Pa /etc/rc.local
+file, see
+.Xr rc 8 ,
+before enabling disk quotas with
+.Xr quotaon 8 .
+.Pp
+.Nm Quotacheck
+accesses the raw device in calculating the actual
+disk usage for each user.
+Thus, the filesystems
+checked should be quiescent while
+.Nm quotacheck
+is running.
+.Sh FILES
+.Bl -tag -width quota.group -compact
+.It Pa quota.user
+at the filesystem root with user quotas
+.It Pa quota.group
+at the filesystem root with group quotas
+.It Pa /etc/fstab
+default filesystems
+.El
+.Sh SEE ALSO
+.Xr quota 1 ,
+.Xr quotactl 2 ,
+.Xr fstab 5 ,
+.Xr edquota 8 ,
+.Xr fsck 8 ,
+.Xr quotaon 8 ,
+.Xr repquota 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
diff --git a/sbin/quotacheck/quotacheck.c b/sbin/quotacheck/quotacheck.c
new file mode 100644
index 00000000000..ae62d9d8dc7
--- /dev/null
+++ b/sbin/quotacheck/quotacheck.c
@@ -0,0 +1,614 @@
+/* $NetBSD: quotacheck.c,v 1.10 1995/03/18 14:59:22 cgd Exp $ */
+
+/*
+ * Copyright (c) 1980, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Robert Elz at The University of Melbourne.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1980, 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94";
+#else
+static char rcsid[] = "$NetBSD: quotacheck.c,v 1.10 1995/03/18 14:59:22 cgd Exp $";
+#endif
+#endif /* not lint */
+
+/*
+ * Fix up / report on disk quotas & usage
+ */
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/quota.h>
+#include <ufs/ffs/fs.h>
+
+#include <fcntl.h>
+#include <fstab.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+char *qfname = QUOTAFILENAME;
+char *qfextension[] = INITQFNAMES;
+char *quotagroup = QUOTAGROUP;
+
+union {
+ struct fs sblk;
+ char dummy[MAXBSIZE];
+} un;
+#define sblock un.sblk
+long dev_bsize;
+long maxino;
+
+struct quotaname {
+ long flags;
+ char grpqfname[MAXPATHLEN + 1];
+ char usrqfname[MAXPATHLEN + 1];
+};
+#define HASUSR 1
+#define HASGRP 2
+
+struct fileusage {
+ struct fileusage *fu_next;
+ u_long fu_curinodes;
+ u_long fu_curblocks;
+ u_long fu_id;
+ char fu_name[1];
+ /* actually bigger */
+};
+#define FUHASH 1024 /* must be power of two */
+struct fileusage *fuhead[MAXQUOTAS][FUHASH];
+
+int aflag; /* all file systems */
+int gflag; /* check group quotas */
+int uflag; /* check user quotas */
+int vflag; /* verbose */
+int fi; /* open disk file descriptor */
+u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */
+
+struct fileusage *
+ addid __P((u_long, int, char *));
+char *blockcheck __P((char *));
+void bread __P((daddr_t, char *, long));
+int chkquota __P((char *, char *, struct quotaname *));
+void freeinodebuf __P((void));
+struct dinode *
+ getnextinode __P((ino_t));
+int getquotagid __P((void));
+int hasquota __P((struct fstab *, int, char **));
+struct fileusage *
+ lookup __P((u_long, int));
+void *needchk __P((struct fstab *));
+int oneof __P((char *, char*[], int));
+void resetinodebuf __P((void));
+int update __P((char *, char *, int));
+void usage __P((void));
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ register struct fstab *fs;
+ register struct passwd *pw;
+ register struct group *gr;
+ struct quotaname *auxdata;
+ int i, argnum, maxrun, errs;
+ long done = 0;
+ char ch, *name;
+
+ errs = maxrun = 0;
+ while ((ch = getopt(argc, argv, "aguvl:")) != EOF) {
+ switch(ch) {
+ case 'a':
+ aflag++;
+ break;
+ case 'g':
+ gflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ case 'l':
+ maxrun = atoi(optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if ((argc == 0 && !aflag) || (argc > 0 && aflag))
+ usage();
+ if (!gflag && !uflag) {
+ gflag++;
+ uflag++;
+ }
+ if (gflag) {
+ setgrent();
+ while ((gr = getgrent()) != 0)
+ (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name);
+ endgrent();
+ }
+ if (uflag) {
+ setpwent();
+ while ((pw = getpwent()) != 0)
+ (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name);
+ endpwent();
+ }
+ if (aflag)
+ exit(checkfstab(1, maxrun, needchk, chkquota));
+ if (setfsent() == 0)
+ err(1, "%s: can't open", FSTAB);
+ while ((fs = getfsent()) != NULL) {
+ if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
+ (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) &&
+ (auxdata = needchk(fs)) &&
+ (name = blockcheck(fs->fs_spec))) {
+ done |= 1 << argnum;
+ errs += chkquota(name, fs->fs_file, auxdata);
+ }
+ }
+ endfsent();
+ for (i = 0; i < argc; i++)
+ if ((done & (1 << i)) == 0)
+ fprintf(stderr, "%s not found in %s\n",
+ argv[i], FSTAB);
+ exit(errs);
+}
+
+void
+usage()
+{
+ (void)fprintf(stderr, "usage:\t%s\n\t%s\n",
+ "quotacheck -a [-guv]",
+ "quotacheck [-guv] filesys ...");
+ exit(1);
+}
+
+void *
+needchk(fs)
+ register struct fstab *fs;
+{
+ register struct quotaname *qnp;
+ char *qfnp;
+
+ if (strcmp(fs->fs_vfstype, "ufs") ||
+ strcmp(fs->fs_type, FSTAB_RW))
+ return (NULL);
+ if ((qnp = malloc(sizeof(*qnp))) == NULL)
+ err(1, "%s", strerror(errno));
+ qnp->flags = 0;
+ if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) {
+ strcpy(qnp->grpqfname, qfnp);
+ qnp->flags |= HASGRP;
+ }
+ if (uflag && hasquota(fs, USRQUOTA, &qfnp)) {
+ strcpy(qnp->usrqfname, qfnp);
+ qnp->flags |= HASUSR;
+ }
+ if (qnp->flags)
+ return (qnp);
+ free(qnp);
+ return (NULL);
+}
+
+/*
+ * Scan the specified filesystem to check quota(s) present on it.
+ */
+int
+chkquota(fsname, mntpt, qnp)
+ char *fsname, *mntpt;
+ register struct quotaname *qnp;
+{
+ register struct fileusage *fup;
+ register struct dinode *dp;
+ int cg, i, mode, errs = 0;
+ ino_t ino;
+
+ if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
+ perror(fsname);
+ return (1);
+ }
+ if (vflag) {
+ (void)printf("*** Checking ");
+ if (qnp->flags & HASUSR)
+ (void)printf("%s%s", qfextension[USRQUOTA],
+ (qnp->flags & HASGRP) ? " and " : "");
+ if (qnp->flags & HASGRP)
+ (void)printf("%s", qfextension[GRPQUOTA]);
+ (void)printf(" quotas for %s (%s)\n", fsname, mntpt);
+ }
+ sync();
+ dev_bsize = 1;
+ bread(SBOFF, (char *)&sblock, (long)SBSIZE);
+ dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
+ maxino = sblock.fs_ncg * sblock.fs_ipg;
+ resetinodebuf();
+ for (ino = 0, cg = 0; cg < sblock.fs_ncg; cg++) {
+ for (i = 0; i < sblock.fs_ipg; i++, ino++) {
+ if (ino < ROOTINO)
+ continue;
+ if ((dp = getnextinode(ino)) == NULL)
+ continue;
+ if ((mode = dp->di_mode & IFMT) == 0)
+ continue;
+ if (qnp->flags & HASGRP) {
+ fup = addid((u_long)dp->di_gid, GRPQUOTA,
+ (char *)0);
+ fup->fu_curinodes++;
+ if (mode == IFREG || mode == IFDIR ||
+ mode == IFLNK)
+ fup->fu_curblocks += dp->di_blocks;
+ }
+ if (qnp->flags & HASUSR) {
+ fup = addid((u_long)dp->di_uid, USRQUOTA,
+ (char *)0);
+ fup->fu_curinodes++;
+ if (mode == IFREG || mode == IFDIR ||
+ mode == IFLNK)
+ fup->fu_curblocks += dp->di_blocks;
+ }
+ }
+ }
+ freeinodebuf();
+ if (qnp->flags & HASUSR)
+ errs += update(mntpt, qnp->usrqfname, USRQUOTA);
+ if (qnp->flags & HASGRP)
+ errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
+ close(fi);
+ return (errs);
+}
+
+/*
+ * Update a specified quota file.
+ */
+int
+update(fsname, quotafile, type)
+ char *fsname, *quotafile;
+ register int type;
+{
+ register struct fileusage *fup;
+ register FILE *qfi, *qfo;
+ register u_long id, lastid;
+ struct dqblk dqbuf;
+ static int warned = 0;
+ static struct dqblk zerodqbuf;
+ static struct fileusage zerofileusage;
+
+ if ((qfo = fopen(quotafile, "r+")) == NULL) {
+ if (errno == ENOENT)
+ qfo = fopen(quotafile, "w+");
+ if (qfo) {
+ (void) fprintf(stderr,
+ "quotacheck: creating quota file %s\n", quotafile);
+#define MODE (S_IRUSR|S_IWUSR|S_IRGRP)
+ (void) fchown(fileno(qfo), getuid(), getquotagid());
+ (void) fchmod(fileno(qfo), MODE);
+ } else {
+ (void) fprintf(stderr,
+ "quotacheck: %s: %s\n", quotafile, strerror(errno));
+ return (1);
+ }
+ }
+ if ((qfi = fopen(quotafile, "r")) == NULL) {
+ (void) fprintf(stderr,
+ "quotacheck: %s: %s\n", quotafile, strerror(errno));
+ (void) fclose(qfo);
+ return (1);
+ }
+ if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 &&
+ errno == EOPNOTSUPP && !warned && vflag) {
+ warned++;
+ (void)printf("*** Warning: %s\n",
+ "Quotas are not compiled into this kernel");
+ }
+ for (lastid = highid[type], id = 0; id <= lastid; id++) {
+ if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
+ dqbuf = zerodqbuf;
+ if ((fup = lookup(id, type)) == 0)
+ fup = &zerofileusage;
+ if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
+ dqbuf.dqb_curblocks == fup->fu_curblocks) {
+ fup->fu_curinodes = 0;
+ fup->fu_curblocks = 0;
+ fseek(qfo, (long)sizeof(struct dqblk), 1);
+ continue;
+ }
+ if (vflag) {
+ if (aflag)
+ printf("%s: ", fsname);
+ printf("%-8s fixed:", fup->fu_name);
+ if (dqbuf.dqb_curinodes != fup->fu_curinodes)
+ (void)printf("\tinodes %d -> %d",
+ dqbuf.dqb_curinodes, fup->fu_curinodes);
+ if (dqbuf.dqb_curblocks != fup->fu_curblocks)
+ (void)printf("\tblocks %d -> %d",
+ dqbuf.dqb_curblocks, fup->fu_curblocks);
+ (void)printf("\n");
+ }
+ /*
+ * Reset time limit if have a soft limit and were
+ * previously under it, but are now over it.
+ */
+ if (dqbuf.dqb_bsoftlimit &&
+ dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
+ fup->fu_curblocks >= dqbuf.dqb_bsoftlimit)
+ dqbuf.dqb_btime = 0;
+ if (dqbuf.dqb_isoftlimit &&
+ dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit &&
+ fup->fu_curblocks >= dqbuf.dqb_isoftlimit)
+ dqbuf.dqb_itime = 0;
+ dqbuf.dqb_curinodes = fup->fu_curinodes;
+ dqbuf.dqb_curblocks = fup->fu_curblocks;
+ fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
+ (void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
+ (caddr_t)&dqbuf);
+ fup->fu_curinodes = 0;
+ fup->fu_curblocks = 0;
+ }
+ fclose(qfi);
+ fflush(qfo);
+ ftruncate(fileno(qfo),
+ (off_t)((highid[type] + 1) * sizeof(struct dqblk)));
+ fclose(qfo);
+ return (0);
+}
+
+/*
+ * Check to see if target appears in list of size cnt.
+ */
+int
+oneof(target, list, cnt)
+ register char *target, *list[];
+ int cnt;
+{
+ register int i;
+
+ for (i = 0; i < cnt; i++)
+ if (strcmp(target, list[i]) == 0)
+ return (i);
+ return (-1);
+}
+
+/*
+ * Determine the group identifier for quota files.
+ */
+int
+getquotagid()
+{
+ struct group *gr;
+
+ if (gr = getgrnam(quotagroup))
+ return (gr->gr_gid);
+ return (-1);
+}
+
+/*
+ * Check to see if a particular quota is to be enabled.
+ */
+int
+hasquota(fs, type, qfnamep)
+ register struct fstab *fs;
+ int type;
+ char **qfnamep;
+{
+ register char *opt;
+ char *cp;
+ static char initname, usrname[100], grpname[100];
+ static char buf[BUFSIZ];
+
+ if (!initname) {
+ (void)snprintf(usrname, sizeof(usrname),
+ "%s%s", qfextension[USRQUOTA], qfname);
+ (void)snprintf(grpname, sizeof(grpname),
+ "%s%s", qfextension[GRPQUOTA], qfname);
+ initname = 1;
+ }
+ strcpy(buf, fs->fs_mntops);
+ for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
+ if (cp = strchr(opt, '='))
+ *cp++ = '\0';
+ if (type == USRQUOTA && strcmp(opt, usrname) == 0)
+ break;
+ if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
+ break;
+ }
+ if (!opt)
+ return (0);
+ if (cp)
+ *qfnamep = cp;
+ else {
+ (void)snprintf(buf, sizeof(buf),
+ "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
+ *qfnamep = buf;
+ }
+ return (1);
+}
+
+/*
+ * Routines to manage the file usage table.
+ *
+ * Lookup an id of a specific type.
+ */
+struct fileusage *
+lookup(id, type)
+ u_long id;
+ int type;
+{
+ register struct fileusage *fup;
+
+ for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next)
+ if (fup->fu_id == id)
+ return (fup);
+ return (NULL);
+}
+
+/*
+ * Add a new file usage id if it does not already exist.
+ */
+struct fileusage *
+addid(id, type, name)
+ u_long id;
+ int type;
+ char *name;
+{
+ struct fileusage *fup, **fhp;
+ int len;
+
+ if (fup = lookup(id, type))
+ return (fup);
+ if (name)
+ len = strlen(name);
+ else
+ len = 10;
+ if ((fup = calloc(1, sizeof(*fup) + len)) == NULL)
+ err(1, "%s", strerror(errno));
+ fhp = &fuhead[type][id & (FUHASH - 1)];
+ fup->fu_next = *fhp;
+ *fhp = fup;
+ fup->fu_id = id;
+ if (id > highid[type])
+ highid[type] = id;
+ if (name)
+ memcpy(fup->fu_name, name, len + 1);
+ else
+ (void)sprintf(fup->fu_name, "%u", id);
+ return (fup);
+}
+
+/*
+ * Special purpose version of ginode used to optimize pass
+ * over all the inodes in numerical order.
+ */
+ino_t nextino, lastinum;
+long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
+struct dinode *inodebuf;
+#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */
+
+struct dinode *
+getnextinode(inumber)
+ ino_t inumber;
+{
+ long size;
+ daddr_t dblk;
+ static struct dinode *dp;
+
+ if (inumber != nextino++ || inumber > maxino)
+ err(1, "bad inode number %d to nextinode", inumber);
+ if (inumber >= lastinum) {
+ readcnt++;
+ dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
+ if (readcnt % readpercg == 0) {
+ size = partialsize;
+ lastinum += partialcnt;
+ } else {
+ size = inobufsize;
+ lastinum += fullcnt;
+ }
+ bread(dblk, (char *)inodebuf, size);
+ dp = inodebuf;
+ }
+ return (dp++);
+}
+
+/*
+ * Prepare to scan a set of inodes.
+ */
+void
+resetinodebuf()
+{
+
+ nextino = 0;
+ lastinum = 0;
+ readcnt = 0;
+ inobufsize = blkroundup(&sblock, INOBUFSIZE);
+ fullcnt = inobufsize / sizeof(struct dinode);
+ readpercg = sblock.fs_ipg / fullcnt;
+ partialcnt = sblock.fs_ipg % fullcnt;
+ partialsize = partialcnt * sizeof(struct dinode);
+ if (partialcnt != 0) {
+ readpercg++;
+ } else {
+ partialcnt = fullcnt;
+ partialsize = inobufsize;
+ }
+ if (inodebuf == NULL &&
+ (inodebuf = malloc((u_int)inobufsize)) == NULL)
+ err(1, "%s", strerror(errno));
+ while (nextino < ROOTINO)
+ getnextinode(nextino);
+}
+
+/*
+ * Free up data structures used to scan inodes.
+ */
+void
+freeinodebuf()
+{
+
+ if (inodebuf != NULL)
+ free(inodebuf);
+ inodebuf = NULL;
+}
+
+/*
+ * Read specified disk blocks.
+ */
+void
+bread(bno, buf, cnt)
+ daddr_t bno;
+ char *buf;
+ long cnt;
+{
+
+ if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 ||
+ read(fi, buf, cnt) != cnt)
+ err(1, "block %ld", bno);
+}