Subject: bin/23971: quotacheck fixes
To: None <gnats-bugs@gnats.netbsd.org>
From: None <kre@munnari.OZ.AU>
List: netbsd-bugs
Date: 01/04/2004 12:39:27
>Number:         23971
>Category:       bin
>Synopsis:       quotacheck fixes
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    bin-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Jan 04 05:42:00 UTC 2004
>Closed-Date:
>Last-Modified:
>Originator:     Robert Elz
>Release:        NetBSD 1.6T  (--current as of 2004-01-03)
>Organization:
	Prince of Songkla University
>Environment:
System: NetBSD delta.cs.mu.OZ.AU 1.6T NetBSD 1.6T (DELTA) #42: Thu May 29 12:23:16 ICT 2003 kre@fuchsia.cs.mu.OZ.AU:/usr/obj/sys/DELTA i386
Architecture: i386
Machine: i386
>Description:
	sbin/quotacheck fails when the max uid is present, and is
	generally inefficient any time there are large gaps in the
	uid space.   It also doesn't co-operate properly with the
	fsck "preen" code that it uses to implement quotacheck -a.

>How-To-Repeat:
	Try it and see...
>Fix:
	Treat this as an author's update if you like.
	Apply the following patch to quotacheck.c and quotacheck.8

	Ignore the patch in PR bin/23725 - that one isn't correct,
	isn't complete, and would cause problems (the PR should be
	closed).   This one should be applied.

--- quotacheck.c	Thu Aug  7 23:24:53 2003
+++ quotacheck-new.c	Sun Dec 28 05:30:21 2003
@@ -115,8 +115,9 @@
 static int	aflag;		/* all file systems */
 static int	gflag;		/* check group quotas */
 static int	uflag;		/* check user quotas */
 static int	vflag;		/* verbose */
+static int	qflag;		/* quick but untidy mode */
 static int	fi;		/* open disk file descriptor */
 static u_long	highid[MAXQUOTAS];/* highest addid()'ed identifier per type */
 static int needswap;	/* FS is in swapped order */
 static int got_siginfo = 0; /* got a siginfo signal */
@@ -128,13 +129,15 @@
 static void *needchk __P((struct fstab *));
 static int chkquota __P((const char *, const char *, const char *, void *,
     pid_t *));
 static int update __P((const char *, const char *, int));
+static u_long skipforward __P((u_long, u_long, FILE *));
 static int oneof __P((const char *, char *[], int));
 static int getquotagid __P((void));
 static int hasquota __P((struct fstab *, int, char **));
 static struct fileusage *lookup __P((u_long, int));
 static struct fileusage *addid __P((u_long, int, const char *));
+static u_long subsequent __P((u_long, int)); 
 static union dinode *getnextinode __P((ino_t));
 static void setinodebuf __P((ino_t));
 static void freeinodebuf __P((void));
 static void bread __P((daddr_t, char *, long));
@@ -157,9 +160,9 @@
 	const char *name;
 	int ch;
 
 	errs = maxrun = 0;
-	while ((ch = getopt(argc, argv, "aguvdl:")) != -1) {
+	while ((ch = getopt(argc, argv, "aguvqdl:")) != -1) {
 		switch(ch) {
 		case 'a':
 			aflag++;
 			break;
@@ -171,8 +174,11 @@
 			break;
 		case 'u':
 			uflag++;
 			break;
+		case 'q':
+			qflag++;
+			break;
 		case 'v':
 			vflag++;
 			break;
 		case 'l':
@@ -292,22 +298,27 @@
 	union dinode *dp;
 	int cg, i, mode, errs = 0, inosused;
 	ino_t ino;
 	struct cg *cgp;
+	char msgbuf[4096];
 
 	if (pid != NULL) {
+		fflush(stdout);
 		switch ((*pid = fork())) {
 		default:
-			break;
-		case 0:
 			return 0;
+		case 0:
+			break;
 		case -1:
 			err(1, "Cannot fork");
 		}
+		setvbuf(stdout, msgbuf, _IOFBF, sizeof msgbuf);
 	}
 
 	if ((fi = open(fsname, O_RDONLY, 0)) < 0) {
 		warn("Cannot open %s", fsname);
+		if (pid != NULL)
+			exit(1);
 		return 1;
 	}
 	if (vflag) {
 		(void)printf("*** Checking ");
@@ -316,8 +327,9 @@
 			    (qnp->flags & HASGRP) ? " and " : "");
 		if (qnp->flags & HASGRP)
 			(void)printf("%s", qfextension[GRPQUOTA]);
 		(void)printf(" quotas for %s (%s)\n", fsname, mntpt);
+		fflush(stdout);
 	}
 	signal(SIGINFO, infohandler);
 	sync();
 	dev_bsize = 1;
@@ -325,8 +337,10 @@
 	cgp = malloc(sblock.fs_cgsize);
 	if (cgp == NULL) {
 		warn("%s: can't allocate %d bytes of cg space", fsname,
 		    sblock.fs_cgsize);
+		if (pid != NULL)
+			exit(1);
 		return 1;
 	}
 
 	for (i = 0; sblock_try[i] != -1; i++) {
@@ -348,10 +362,12 @@
 		}
 	}
 	warnx("%s: superblock not found", fsname);
 	free(cgp);
+	if (pid != NULL)
+		exit(1);
 	return 1;
-found:
+ found:;
 	if (needswap)
 		ffs_sb_swap(&sblock, &sblock);
 	dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1);
 	maxino = sblock.fs_ncg * sblock.fs_ipg;
@@ -403,8 +419,10 @@
 		errs += update(mntpt, qnp->usrqfname, USRQUOTA);
 	if (qnp->flags & HASGRP)
 		errs += update(mntpt, qnp->grpqfname, GRPQUOTA);
 	close(fi);
+	if (pid != NULL)
+		exit(errs);
 	return errs;
 }
 
 /*
@@ -416,9 +434,10 @@
 	int type;
 {
 	struct fileusage *fup;
 	FILE *qfi, *qfo;
-	u_long id, lastid;
+	u_long id, lastid, nextid;
+	int need_seek;
 	struct dqblk dqbuf;
 	static int warned = 0;
 	static struct dqblk zerodqbuf;
 	static struct fileusage zerofileusage;
@@ -449,18 +468,35 @@
 		warned++;
 		(void)printf("*** Warning: %s\n",
 		    "Quotas are not compiled into this kernel");
 	}
-	for (lastid = highid[type], id = 0; id <= lastid; id++) {
+	need_seek = 1;
+	for (lastid = highid[type], id = 0; id <= lastid; id = nextid) {
 		if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
 			dqbuf = zerodqbuf;
 		if ((fup = lookup(id, type)) == 0)
 			fup = &zerofileusage;
+
+		nextid = subsequent(id, type);
+		if (nextid != id + 1)
+			nextid = skipforward(id, nextid, qfi);
+
+		if (got_siginfo) {
+			fprintf(stderr,
+			    "%s: updating %s quotas for id=%ld (%s)\n", fsname,
+			    qfextension[type < MAXQUOTAS ? type : MAXQUOTAS],
+			    id, fup->fu_name);
+			got_siginfo = 0;
+		}
 		if (dqbuf.dqb_curinodes == fup->fu_curinodes &&
 		    dqbuf.dqb_curblocks == fup->fu_curblocks) {
-			fup->fu_curinodes = 0;
-			fup->fu_curblocks = 0;
-			(void) fseek(qfo, (long)sizeof(struct dqblk), 1);
+			fup->fu_curinodes = 0;	/* reset usage  */
+			fup->fu_curblocks = 0;	/* for next filesystem */
+
+			need_seek = 1;
+
+			if (id == ULONG_MAX)	/* ++id == 0, infinite loop */
+				break;
 			continue;
 		}
 		if (vflag) {
 			if (aflag)
@@ -487,22 +523,66 @@
 		    fup->fu_curblocks >= dqbuf.dqb_isoftlimit)
 			dqbuf.dqb_itime = 0;
 		dqbuf.dqb_curinodes = fup->fu_curinodes;
 		dqbuf.dqb_curblocks = fup->fu_curblocks;
+
+		if (need_seek) {
+			(void) fseeko(qfo, (off_t)id * sizeof(struct dqblk),
+			    SEEK_SET);
+			need_seek = nextid != id + 1;
+		}
 		(void) fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
-		(void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
-		    (caddr_t)&dqbuf);
+
+		if (!warned)
+			(void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
+			    (caddr_t)&dqbuf);
+
 		fup->fu_curinodes = 0;
 		fup->fu_curblocks = 0;
+		if (id == ULONG_MAX)
+			break;
 	}
 	(void) fclose(qfi);
 	(void) fflush(qfo);
-	(void) ftruncate(fileno(qfo),
-	    (off_t)((highid[type] + 1) * sizeof(struct dqblk)));
+	if (highid[type] != ULONG_MAX)
+		(void) ftruncate(fileno(qfo),
+		    (off_t)((highid[type] + 1) * sizeof(struct dqblk)));
 	(void) fclose(qfo);
 	return (0);
 }
 
+u_long
+skipforward(cur, to, qfi)
+	u_long cur, to;
+	FILE *qfi;
+{
+	struct dqblk dqbuf;
+
+	if (qflag) {
+		(void) fseeko(qfi, (off_t)to * sizeof(struct dqblk), SEEK_SET);
+		return (to);
+	}
+
+	while (++cur < to) {
+		/*
+		 * if EOF occurs, nothing left to read, we're done
+		 */
+		if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0)
+			return (to);
+
+		/*
+		 * If we find an entry that shows usage, before the next
+		 * id that has actual usage, we have to stop here, so the
+		 * incorrect entry can be corrected in the file
+		 */
+		if (dqbuf.dqb_curinodes != 0 || dqbuf.dqb_curblocks != 0) {
+			(void)fseek(qfi, -(long)sizeof(struct dqblk), SEEK_CUR);
+			return (cur);
+		}
+	}
+	return (to);
+}
+
 /*
  * Check to see if target appears in list of size cnt.
  */
 static int
@@ -624,8 +704,34 @@
 		(void)sprintf(fup->fu_name, "%lu", id);
 	return (fup);
 }
 
+static u_long
+subsequent(id, type)
+	u_long id;
+	int type;
+{
+	struct fileusage *fup, **iup, **cup;
+	u_long next, offset;
+
+	next = highid[type] + 1;
+	offset = 0;
+	cup = iup = &fuhead[type][id & (FUHASH-1)];
+	do {
+		++offset;
+		if (++cup >= &fuhead[type][FUHASH])
+			cup = &fuhead[type][0];
+		for (fup = *cup; fup != 0; fup = fup->fu_next) {
+			if (fup->fu_id > id && fup->fu_id <= id + offset)
+				return (fup->fu_id);
+			if (fup->fu_id > id && fup->fu_id < next)
+				next = fup->fu_id;
+		}
+	} while (cup != iup);
+
+	return next;
+}
+
 /*
  * Special purpose version of ginode used to optimize first pass
  * over all the inodes in numerical order.
  */
@@ -755,5 +861,4 @@
 infohandler(int sig)
 {
 	got_siginfo = 1;
 } 
-
--- quotacheck.8	Thu Aug  7 23:24:52 2003
+++ quotacheck-new.8	Fri Dec 12 18:38:24 2003
@@ -41,13 +41,15 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl g
 .Op Fl u
+.Op Fl q
 .Op Fl v
 .Ar filesystem Ar ...
 .Nm
 .Op Fl g
 .Op Fl u
+.Op Fl q
 .Op Fl v
 .Fl a
 .Sh DESCRIPTION
 .Nm
@@ -81,11 +83,20 @@
 .It Fl u
 Only user quotas listed in
 .Pa /etc/fstab
 are to be checked.
+.It Fl q
+.Nm
+runs more quickly,
+particularly on systems with sparse user id usage,
+but fails to correct quotas for users [groups]
+not in the system user [group] database,
+and owning no files on the filesystem,
+if the quota file incorrectly believes that they do.
 .It Fl v
 .Nm
-reports discrepancies between the
+is more verbose,
+and reports corrected discrepancies between the
 calculated and recorded disk quotas.
 .El
 .Pp
 Specifying both
>Release-Note:
>Audit-Trail:
>Unformatted: