Subject: bin/23725: possible quotacheck enhancement
To: None <gnats-bugs@gnats.netbsd.org>
From: None <kre@munnari.OZ.AU>
List: netbsd-bugs
Date: 12/12/2003 19:11:53
>Number: 23725
>Category: bin
>Synopsis: possible quotacheck enhancement
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: bin-bug-people
>State: open
>Class: change-request
>Submitter-Id: net
>Arrival-Date: Fri Dec 12 12:14:00 UTC 2003
>Closed-Date:
>Last-Modified:
>Originator: Robert Elz
>Release: NetBSD 1.6X (sources -current as of yesterday)
>Organization:
Prince of Songkla University
>Environment:
System: NetBSD jade.coe.psu.ac.th 1.6X NetBSD 1.6X (JADE) #17: Wed Sep 24 20:25:35 ICT 2003 kre@jade.coe.psu.ac.th:/usr/src/real-sys/arch/i386/compile/JADE i386
Architecture: i386
Machine: i386
>Description:
quotacheck can run very slowly (take a long time, an extremely
long time) if run on a filesystem that happens to have a file
allocated to a uid (or gid if using group quotas) that is large.
I believe (from past experience) that the principle cause of this
is its habit of doing a seek forward, of one dqblk size, for every
record it does not need to write (seek, to preserve holes in the
quota file, is certainly correct, but one seek per irrelevant uid
gets very costly, very quickly).
When I have fixed this previously, I've simply remembered a seek
is needed, and done an appropriate seek before the next write.
This time I got ambitious.
>How-To-Repeat:
Add (user or group) quotas to a filesystem. Create a file on
the filesystem (give it a few blocks for run, though this isn't
strictly needed)
chown 10000000 filename
(use chgrp of course if using group quotas).
Run quotacheck, and wait, and wait... (run it in background).
When you get very bored, bring it to foreground, and do a SIGINFO
from the keyboard (have ^T or something set for that) - notice
that quotacheck says nothing, though the man page promises it
will.
>Fix:
****** WARNING *******
This fix has been compiled, but tested only on my kre model virtual
processor (model psycho 7), using the revolutionary new virtual
filestore... (loses data all the time unfortunately)
****** WARNING *******
The patch below does several things. It implements the
seek no more than once per write enhancement. It avoids
looping over (perhaps 2^32 uids, when only 2 or 3 (or 2 or
3 hundred perhaps) are actually in use (but they just happen
to be 0, and 2^32 - 1) A new option ('q') is added, that
will cause quotacheck to skip reading the parts of the quota
file that it has no reason to believe should exist (user not
in passwd file (group not in group file) and owns no files
on the filesystem) - however this does mean that if the quota
file believes that this user (group) which owns nothing, and
has no name, is listed as owning something, then with -q that
will not be corrected (without -q the entire quota file needs
to be read, to locate any entries that record usage when they
shouldn't). It also implements SIGINFO handling in the slow
part of quotacheck - actually updating the quota file (reading
the filesystem takes usually minutes at most, doing the calculations
happens in parallel, but is only going to be seconds - updating
the quota file can take hours...)
This patch is for use only by someone willing to wreck their
quota file (so dump it to text before trying this) with the
aim of helping to produce a better system - unfortunately, I
have no current systems using quotas to try it on (and am much
too lazy to set one up).
I believe it has one bug - bad things are likely if uid 2^32-1
actually owns files (or exists in the password file). (same for
groups) (assuming long is 32 bits, on a LP64 system, all should
be OK, as uids, and gids, don't ever get near 2^64-1 there).
If someone decides to test this, please let me know how you get on.
--- quotacheck.c 2003-08-07 23:24:53.000000000 +0700
+++ quotacheck-new.c 2003-12-12 18:44:56.000000000 +0700
@@ -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':
@@ -416,9 +422,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 +456,33 @@
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) {
+#if 0 /** Why? **/
fup->fu_curinodes = 0;
fup->fu_curblocks = 0;
- (void) fseek(qfo, (long)sizeof(struct dqblk), 1);
+#endif
+ need_seek = 1;
continue;
}
if (vflag) {
if (aflag)
@@ -487,13 +509,23 @@
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 = 0;
+ }
(void) fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo);
+
(void) quotactl(fsname, QCMD(Q_SETUSE, type), id,
(caddr_t)&dqbuf);
+
+#if 0 /** Why? **/
fup->fu_curinodes = 0;
fup->fu_curblocks = 0;
+#endif
}
(void) fclose(qfi);
(void) fflush(qfo);
(void) ftruncate(fileno(qfo),
@@ -501,8 +533,40 @@
(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 +688,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 +845,4 @@
infohandler(int sig)
{
got_siginfo = 1;
}
-
--- quotacheck.8 2003-08-07 23:24:52.000000000 +0700
+++ quotacheck-new.8 2003-12-12 18:38:24.000000000 +0700
@@ -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: