Module Name:    src
Committed By:   dholland
Date:           Tue Aug 14 03:55:49 UTC 2012

Modified Files:
        src/usr.sbin/edquota: edquota.c

Log Message:
Make editor-based edquota work again.

The format is somewhat different; I'm operating under the assumption
that nobody has automated editing scripts for the old format because
it's much easier just to use the command-line interface of
edquota. The new format is more scalable and more parseable.

Also, do a better job of diagnosing editing errors, and don't blindly
erase all quota information for the user being edited when a parse
error occurs after editing.


To generate a diff of this commit:
cvs rdiff -u -r1.49 -r1.50 src/usr.sbin/edquota/edquota.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.sbin/edquota/edquota.c
diff -u src/usr.sbin/edquota/edquota.c:1.49 src/usr.sbin/edquota/edquota.c:1.50
--- src/usr.sbin/edquota/edquota.c:1.49	Mon Aug 13 23:08:58 2012
+++ src/usr.sbin/edquota/edquota.c	Tue Aug 14 03:55:48 2012
@@ -1,4 +1,4 @@
-/*      $NetBSD: edquota.c,v 1.49 2012/08/13 23:08:58 dholland Exp $ */
+/*      $NetBSD: edquota.c,v 1.50 2012/08/14 03:55:48 dholland Exp $ */
 /*
  * Copyright (c) 1980, 1990, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -41,7 +41,7 @@ __COPYRIGHT("@(#) Copyright (c) 1980, 19
 #if 0
 static char sccsid[] = "from: @(#)edquota.c	8.3 (Berkeley) 4/27/95";
 #else
-__RCSID("$NetBSD: edquota.c,v 1.49 2012/08/13 23:08:58 dholland Exp $");
+__RCSID("$NetBSD: edquota.c,v 1.50 2012/08/14 03:55:48 dholland Exp $");
 #endif
 #endif /* not lint */
 
@@ -89,6 +89,13 @@ static const char *quotagroup = QUOTAGRO
 
 #define MAX_TMPSTR	(100+MAXPATHLEN)
 
+enum sources {
+	SRC_EDITED,	/* values came from user */
+	SRC_QUOTA,	/* values came from a specific quota entry */
+	SRC_DEFAULT,	/* values were copied from the default quota entry */
+	SRC_CLEAR,	/* values arose by zeroing out a quota entry */
+};
+
 struct quotause {
 	struct	quotause *next;
 	unsigned found:1,	/* found after running editor */
@@ -96,6 +103,7 @@ struct quotause {
 		isdefault:1;
 
 	struct	quotaval qv[EDQUOTA_NUMOBJTYPES];
+	enum sources source[EDQUOTA_NUMOBJTYPES];
 	char	fsname[MAXPATHLEN + 1];
 	char	implementation[32];
 };
@@ -149,6 +157,46 @@ getidbyname(const char *name, int idtype
 	return -1;
 }
 
+/*
+ * check if a source is "real" (reflects actual data) or not
+ */
+static bool
+source_is_real(enum sources source)
+{
+	switch (source) {
+	    case SRC_EDITED:
+	    case SRC_QUOTA:
+		return true;
+	    case SRC_DEFAULT:
+	    case SRC_CLEAR:
+		return false;
+	}
+	assert(!"encountered invalid source");
+	return false;
+}
+
+/*
+ * some simple string tools
+ */
+
+static /*const*/ char *
+skipws(/*const*/ char *s)
+{
+	while (*s == ' ' || *s == '\t') {
+		s++;
+	}
+	return s;
+}
+
+static /*const*/ char *
+skipword(/*const*/ char *s)
+{
+	while (*s != '\0' && *s != '\n' && *s != ' ' && *s != '\t') {
+		s++;
+	}
+	return s;
+}
+
 ////////////////////////////////////////////////////////////
 // quotause operations
 
@@ -159,6 +207,7 @@ static struct quotause *
 quotause_create(void)
 {
 	struct quotause *qup;
+	unsigned i;
 
 	qup = malloc(sizeof(*qup));
 	if (qup == NULL) {
@@ -169,7 +218,10 @@ quotause_create(void)
 	qup->found = 0;
 	qup->xgrace = 0;
 	qup->isdefault = 0;
-	memset(qup->qv, 0, sizeof(qup->qv));
+	for (i=0; i<EDQUOTA_NUMOBJTYPES; i++) {
+		quotaval_clear(&qup->qv[i]);
+		qup->source[i] = SRC_CLEAR;
+	}
 	qup->fsname[0] = '\0';
 
 	return qup;
@@ -360,13 +412,30 @@ dogetprivs2(struct quotahandle *qh, int 
 	qk.qk_idtype = idtype;
 	qk.qk_id = defaultq ? QUOTA_DEFAULTID : id;
 	qk.qk_objtype = objtype;
-	if (quota_get(qh, &qk, &qup->qv[objtype])) {
-		/* no entry, get default entry */
-		qk.qk_id = QUOTA_DEFAULTID;
-		if (quota_get(qh, &qk, &qup->qv[objtype])) {
-			return -1;
-		}
+	if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) {
+		/* succeeded */
+		qup->source[objtype] = SRC_QUOTA;
+		return 0;
 	}
+	if (errno != ENOENT) {
+		/* serious failure */
+		return -1;
+	}
+
+	/* no entry, get default entry */
+	qk.qk_id = QUOTA_DEFAULTID;
+	if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) {
+		/* succeeded */
+		qup->source[objtype] = SRC_DEFAULT;
+		return 0;
+	}
+	if (errno != ENOENT) {
+		return -1;
+	}
+
+	/* use a zeroed-out entry */
+	quotaval_clear(&qup->qv[objtype]);
+	qup->source[objtype] = SRC_CLEAR;
 	return 0;
 }
 
@@ -448,18 +517,24 @@ putprivs2(uint32_t id, int idtype, struc
 		err(1, "%s: quota_open", qup->fsname);
 	}
 
-	qk.qk_idtype = idtype;
-	qk.qk_id = id;
-	qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
-	if (quota_put(qh, &qk, &qup->qv[QO_BLK])) {
-		err(1, "%s: quota_put (%s blocks)", qup->fsname, idname);
+	if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
+		qk.qk_idtype = idtype;
+		qk.qk_id = id;
+		qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
+		if (quota_put(qh, &qk, &qup->qv[QO_BLK])) {
+			err(1, "%s: quota_put (%s blocks)", qup->fsname,
+			    idname);
+		}
 	}
 
-	qk.qk_idtype = idtype;
-	qk.qk_id = id;
-	qk.qk_objtype = QUOTA_OBJTYPE_FILES;
-	if (quota_put(qh, &qk, &qup->qv[QO_FL])) {
-		err(1, "%s: quota_put (%s files)", qup->fsname, idname);
+	if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
+		qk.qk_idtype = idtype;
+		qk.qk_id = id;
+		qk.qk_objtype = QUOTA_OBJTYPE_FILES;
+		if (quota_put(qh, &qk, &qup->qv[QO_FL])) {
+			err(1, "%s: quota_put (%s files)", qup->fsname,
+			    idname);
+		}
 	}
 
 	quota_close(qh);
@@ -497,18 +572,7 @@ getprivs(long id, int defaultq, int idty
 		qup = getprivs2(id, idtype, fst[i].f_mntonname, defaultq,
 				&qlist->idtypename);
 		if (qup == NULL) {
-			/*
-			 * XXX: returning NULL is totally wrong. On
-			 * serious error, abort; on minor error, warn
-			 * and continue.
-			 *
-			 * Note: we cannot warn unconditionally here
-			 * because this case apparently includes "no
-			 * quota entry on this volume" and that causes
-			 * the atf tests to fail. Bletch.
-			 */
-			/*return NULL;*/
-			/*warnx("getprivs2 failed");*/
+			warnx("getprivs2 failed for id %ld", id);
 			continue;
 		}
 
@@ -526,9 +590,7 @@ getprivs(long id, int defaultq, int idty
 		/* if we get there, filesys is not mounted. try the old way */
 		qup = getprivs1(id, idtype, filesys);
 		if (qup == NULL) {
-			/* XXX. see above */
-			/*return NULL;*/
-			/*warnx("getprivs1 failed");*/
+			warnx("getprivs1 failed");
 			return qlist;
 		}
 		quotalist_append(qlist, qup);
@@ -710,6 +772,7 @@ writeprivs(struct quotalist *qlist, int 
 	struct quotause *qup;
 	FILE *fd;
 	char b0[32], b1[32], b2[32], b3[32];
+	const char *comm;
 
 	(void)ftruncate(outfd, 0);
 	(void)lseek(outfd, (off_t)0, SEEK_SET);
@@ -722,46 +785,38 @@ writeprivs(struct quotalist *qlist, int 
 	}
 	for (qup = qlist->head; qup; qup = qup->next) {
 		struct quotaval *q = qup->qv;
-		fprintf(fd, "%s (%s):\n",
-		     qup->fsname, qup->implementation);
-		if (!qup->isdefault || qup->xgrace) {
-			fprintf(fd, "\tblocks in use: %s, "
-			    "limits (soft = %s, hard = %s",
-			    intprt(b1, 21, q[QO_BLK].qv_usage,
-			    HN_NOSPACE | HN_B, Hflag), 
-			    intprt(b2, 21, q[QO_BLK].qv_softlimit,
-			    HN_NOSPACE | HN_B, Hflag),
-			    intprt(b3, 21, q[QO_BLK].qv_hardlimit,
-				HN_NOSPACE | HN_B, Hflag));
-			if (qup->xgrace)
-				fprintf(fd, ", ");
-		} else
-			fprintf(fd, "\tblocks: (");
-			
+
+		fprintf(fd, "%s (%s):\n", qup->fsname, qup->implementation);
+
+		comm = source_is_real(qup->source[QO_BLK]) ? "" : "#";
+		fprintf(fd, "\tblocks:\n");
+		fprintf(fd, "\t\t%susage: %s\n", comm,
+			intprt(b1, 21, q[QO_BLK].qv_usage,
+			       HN_NOSPACE | HN_B, Hflag));
+		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
+			intprt(b2, 21, q[QO_BLK].qv_softlimit,
+			       HN_NOSPACE | HN_B, Hflag),
+			intprt(b3, 21, q[QO_BLK].qv_hardlimit,
+			       HN_NOSPACE | HN_B, Hflag));
 		if (qup->xgrace || qup->isdefault) {
-		    fprintf(fd, "grace = %s",
-			timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag));
+			fprintf(fd, "\t\t%sgrace: %s\n", comm,
+				timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag));
 		}
-		fprintf(fd, ")\n");
-		if (!qup->isdefault || qup->xgrace) {
-			fprintf(fd, "\tinodes in use: %s, "
-			    "limits (soft = %s, hard = %s",
-			    intprt(b1, 21, q[QO_FL].qv_usage,
-			    HN_NOSPACE, Hflag),
-			    intprt(b2, 21, q[QO_FL].qv_softlimit,
-			    HN_NOSPACE, Hflag),
-			    intprt(b3, 21, q[QO_FL].qv_hardlimit,
-			     HN_NOSPACE, Hflag));
-			if (qup->xgrace)
-				fprintf(fd, ", ");
-		} else
-			fprintf(fd, "\tinodes: (");
 
+		comm = source_is_real(qup->source[QO_FL]) ? "" : "#";
+		fprintf(fd, "\tinodes:\n");
+		fprintf(fd, "\t\t%susage: %s\n", comm,
+			intprt(b1, 21, q[QO_FL].qv_usage,
+			       HN_NOSPACE, Hflag));
+		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
+			intprt(b2, 21, q[QO_FL].qv_softlimit,
+			       HN_NOSPACE, Hflag),
+			intprt(b3, 21, q[QO_FL].qv_hardlimit,
+			       HN_NOSPACE, Hflag));
 		if (qup->xgrace || qup->isdefault) {
-		    fprintf(fd, "grace = %s",
-			timepprt(b0, 21, q[QO_FL].qv_grace, Hflag));
+			fprintf(fd, "\t\t%sgrace: %s\n", comm,
+				timepprt(b0, 21, q[QO_FL].qv_grace, Hflag));
 		}
-		fprintf(fd, ")\n");
 	}
 	fclose(fd);
 	return 1;
@@ -773,239 +828,278 @@ writeprivs(struct quotalist *qlist, int 
 static int
 readprivs(struct quotalist *qlist, int infd, int dflag)
 {
-	struct quotause *qup;
-	FILE *fd;
-	int cnt;
-	char fsp[BUFSIZ];
-	static char line0[BUFSIZ], line1[BUFSIZ], line2[BUFSIZ];
-	static char scurb[BUFSIZ], scuri[BUFSIZ], ssoft[BUFSIZ], shard[BUFSIZ];
-	static char stime[BUFSIZ];
-	uint64_t softb, hardb, softi, hardi;
-	time_t graceb = -1, gracei = -1;
-	int version;
+	FILE *fd;			/* file */
+	unsigned lineno;		/* line number in file */
+	char line[BUFSIZ];		/* input buffer */
+	size_t len;			/* length of input buffer */
+	bool seenheader;		/* true if past the file header */
+	struct quotause *qup;		/* current filesystem */
+	bool haveobjtype;		/* true if objtype is valid */
+	unsigned objtype;		/* current object type */
+	int objtypeflags;		/* humanize flags */
+	/* XXX make following const later (requires non-local cleanup) */
+	/*const*/ char *text, *text2, *t; /* scratch values */
+	char b0[32], b1[32];
+	uint64_t soft, hard, current;
+	time_t grace;
+	struct quotaval *qv;
+
+	lineno = 0;
+	seenheader = false;
+	qup = NULL;
+	haveobjtype = false;
+	objtype = QUOTA_OBJTYPE_BLOCKS; /* for gcc 4.5 */
+	objtypeflags = HN_B;
 
 	(void)lseek(infd, (off_t)0, SEEK_SET);
 	fd = fdopen(dup(infd), "r");
 	if (fd == NULL) {
 		warn("Can't re-read temp file");
-		return 0;
+		return -1;
 	}
+
 	/*
-	 * Discard title line, then read pairs of lines to process.
+	 * Read back the same format we wrote.
 	 */
-	(void) fgets(line1, sizeof (line1), fd);
-	while (fgets(line0, sizeof (line0), fd) != NULL &&
-	       fgets(line1, sizeof (line2), fd) != NULL &&
-	       fgets(line2, sizeof (line2), fd) != NULL) {
-		if (sscanf(line0, "%s (version %d):\n", fsp, &version) != 2) {
-			warnx("%s: bad format", line0);
-			goto out;
-		}
-#define last_char(str) ((str)[strlen(str) - 1])
-		if (last_char(line1) != '\n') {
-			warnx("%s:%s: bad format", fsp, line1);
-			goto out;
-		}
-		last_char(line1) = '\0';
-		if (last_char(line2) != '\n') {
-			warnx("%s:%s: bad format", fsp, line2);
-			goto out;
-		}
-		last_char(line2) = '\0';
-		if (dflag && version == 1) {
-			if (sscanf(line1,
-			    "\tblocks: (grace = %s\n", stime) != 1) {
-				warnx("%s:%s: bad format", fsp, line1);
-				goto out;
-			}
-			if (last_char(stime) != ')') {
-				warnx("%s:%s: bad format", fsp, line1);
-				goto out;
-			}
-			last_char(stime) = '\0';
-			if (timeprd(stime, &graceb) != 0) {
-				warnx("%s:%s: bad number", fsp, stime);
-				goto out;
-			}
-			if (sscanf(line2,
-			    "\tinodes: (grace = %s\n", stime) != 1) {
-				warnx("%s:%s: bad format", fsp, line2);
-				goto out;
-			}
-			if (last_char(stime) != ')') {
-				warnx("%s:%s: bad format", fsp, line2);
-				goto out;
+
+	while (fgets(line, sizeof(line), fd)) {
+		lineno++;
+		if (!seenheader) {
+			if ((!strncmp(line, "Default ", 8) && dflag) ||
+			    (!strncmp(line, "Quotas for ", 11) && !dflag)) {
+				/* ok. */
+				seenheader = 1;
+				continue;
 			}
-			last_char(stime) = '\0';
-			if (timeprd(stime, &gracei) != 0) {
-				warnx("%s:%s: bad number", fsp, stime);
-				goto out;
+			warnx("Header line missing");
+			goto fail;
+		}
+		len = strlen(line);
+		if (len == 0) {
+			/* ? */
+			continue;
+		}
+		if (line[len - 1] != '\n') {
+			warnx("Line %u too long", lineno);
+			goto fail;
+		}
+		line[--len] = '\0';
+		if (line[len - 1] == '\r') {
+			line[--len] = '\0';
+		}
+		if (len == 0) {
+			/* blank line */
+			continue;
+		}
+
+		/*
+		 * If the line has:     it is:
+                 *      two tabs        values
+		 *      one tab         the next objtype
+		 *      no tabs         the next filesystem
+		 */
+		if (line[0] == '\t' && line[1] == '\t') {
+			if (qup == NULL) {
+				warnx("Line %u: values with no filesystem",
+				      lineno);
+				goto fail;
+			}
+			if (!haveobjtype) {
+				warnx("Line %u: values with no object type",
+				      lineno);
+				goto fail;
+			}
+			qv = &qup->qv[objtype];
+
+			text = line + 2;
+			if (*text == '#') {
+				/* commented out; ignore */
+				continue;
 			}
-		} else {
-			cnt = sscanf(line1,
-			    "\tblocks in use: %s limits (soft = %s hard = %s "
-			    "grace = %s", scurb, ssoft, shard, stime);
-			if (cnt == 3) {
-				if (version != 1 ||
-				    last_char(scurb) != ',' ||
-				    last_char(ssoft) != ',' ||
-				    last_char(shard) != ')') {
-					warnx("%s:%s: bad format %d",
-					    fsp, line1, cnt);
-					goto out;
+			else if (!strncmp(text, "usage:", 6)) {
+
+				/* usage: %llu */
+				text += 6;
+				t = skipws(text);
+				if (intrd(t, &current, objtypeflags) != 0) {
+					warnx("Line %u: Bad number %s",
+					      lineno, t);
+					goto fail;
 				}
-				stime[0] = '\0';
-			} else if (cnt == 4) {
-				if (version < 2 ||
-				    last_char(scurb) != ',' ||
-				    last_char(ssoft) != ',' ||
-				    last_char(shard) != ',' ||
-				    last_char(stime) != ')') {
-					warnx("%s:%s: bad format %d",
-					    fsp, line1, cnt);
-					goto out;
+
+				/*
+				 * Because the humanization can lead
+				 * to roundoff, check if the two
+				 * values produce the same humanized
+				 * string, rather than if they're the
+				 * same number. Sigh.
+				 */
+				intprt(b0, 21, current,
+				       HN_NOSPACE | HN_B, Hflag);
+				intprt(b1, 21, qv->qv_usage,
+				       HN_NOSPACE | HN_B, Hflag);
+				if (strcmp(b0, b1)) {
+					warnx("Line %u: cannot change usage",
+					      lineno);
 				}
-			} else {
-				warnx("%s: %s: bad format cnt %d", fsp, line1,
-				    cnt);
-				goto out;
-			}
-			/* drop last char which is ',' or ')' */
-			last_char(scurb) = '\0';
-			last_char(ssoft) = '\0';
-			last_char(shard) = '\0';
-			last_char(stime) = '\0';
-			
-			if (intrd(ssoft, &softb, HN_B) != 0) {
-				warnx("%s:%s: bad number", fsp, ssoft);
-				goto out;
-			}
-			if (intrd(shard, &hardb, HN_B) != 0) {
-				warnx("%s:%s: bad number", fsp, shard);
-				goto out;
-			}
-			if (cnt == 4) {
-				if (timeprd(stime, &graceb) != 0) {
-					warnx("%s:%s: bad number", fsp, stime);
-					goto out;
+				continue;
+
+			} else if (!strncmp(text, "limits:", 7)) {
+
+				/* limits: soft %llu, hard %llu */
+				text += 7;
+				text2 = strchr(text, ',');
+				if (text2 == NULL) {
+					warnx("Line %u: expected comma",
+					      lineno);
+					goto fail;
 				}
-			}
+				*text2 = '\0';
+				text2++;
+
+				t = skipws(text);
+				t = skipword(t);
+				t = skipws(t);
+				if (intrd(t, &soft, objtypeflags) != 0) {
+					warnx("Line %u: Bad number %s",
+					      lineno, t);
+					goto fail;
+				}
+				t = skipws(text2);
+				t = skipword(t);
+				t = skipws(t);
+				if (intrd(t, &hard, objtypeflags) != 0) {
+					warnx("Line %u: Bad number %s",
+					      lineno, t);
+					goto fail;
+				}
+
+				/*
+				 * Cause time limit to be reset when the quota
+				 * is next used if previously had no soft limit
+				 * or were under it, but now have a soft limit
+				 * and are over it.
+				 */
+				if (qv->qv_usage && qv->qv_usage >= soft &&
+				    (qv->qv_softlimit == 0 ||
+				     qv->qv_usage < qv->qv_softlimit)) {
+					qv->qv_expiretime = 0;
+				}
+				if (soft != qv->qv_softlimit ||
+				    hard != qv->qv_hardlimit) {
+					qv->qv_softlimit = soft;
+					qv->qv_hardlimit = hard;
+					qup->source[objtype] = SRC_EDITED;
+				}
+				qup->found = 1;
 
-			cnt = sscanf(line2,
-			    "\tinodes in use: %s limits (soft = %s hard = %s "
-			    "grace = %s", scuri, ssoft, shard, stime);
-			if (cnt == 3) {
-				if (version != 1 ||
-				    last_char(scuri) != ',' ||
-				    last_char(ssoft) != ',' ||
-				    last_char(shard) != ')') {
-					warnx("%s:%s: bad format %d",
-					    fsp, line2, cnt);
-					goto out;
+			} else if (!strncmp(text, "grace:", 6)) {
+
+				text += 6;
+				/* grace: %llu */
+				t = skipws(text);
+				if (timeprd(t, &grace) != 0) {
+					warnx("Line %u: Bad number %s",
+					      lineno, t);
+					goto fail;
 				}
-				stime[0] = '\0';
-			} else if (cnt == 4) {
-				if (version < 2 ||
-				    last_char(scuri) != ',' ||
-				    last_char(ssoft) != ',' ||
-				    last_char(shard) != ',' ||
-				    last_char(stime) != ')') {
-					warnx("%s:%s: bad format %d",
-					    fsp, line2, cnt);
-					goto out;
+				if (qup->isdefault || qup->xgrace) {
+					if (grace != qv->qv_grace) {
+						qv->qv_grace = grace;
+						qup->source[objtype] =
+							SRC_EDITED;
+					}
+					qup->found = 1;
+				} else {
+					warnx("Line %u: Cannot set individual "
+					      "grace time on this filesystem",
+					      lineno);
+					goto fail;
 				}
+
 			} else {
-				warnx("%s: %s: bad format", fsp, line2);
-				goto out;
-			}
-			/* drop last char which is ',' or ')' */
-			last_char(scuri) = '\0';
-			last_char(ssoft) = '\0';
-			last_char(shard) = '\0';
-			last_char(stime) = '\0';
-			if (intrd(ssoft, &softi, 0) != 0) {
-				warnx("%s:%s: bad number", fsp, ssoft);
-				goto out;
+				warnx("Line %u: Unknown/unexpected value line",
+				      lineno);
+				goto fail;
+			}
+		} else if (line[0] == '\t') {
+			text = line + 1;
+			if (*text == '#') {
+				/* commented out; ignore */
+				continue;
 			}
-			if (intrd(shard, &hardi, 0) != 0) {
-				warnx("%s:%s: bad number", fsp, shard);
-				goto out;
+			else if (!strncmp(text, "blocks:", 7)) {
+				objtype = QUOTA_OBJTYPE_BLOCKS;
+				objtypeflags = HN_B;
+				haveobjtype = true;
+			} else if (!strncmp(text, "inodes:", 7)) {
+				objtype = QUOTA_OBJTYPE_FILES;
+				objtypeflags = 0;
+				haveobjtype = true;
+			} else {
+				warnx("Line %u: Unknown/unexpected object "
+				      "type (%s)", lineno, text);
+				goto fail;
 			}
-			if (cnt == 4) {
-				if (timeprd(stime, &gracei) != 0) {
-					warnx("%s:%s: bad number", fsp, stime);
-					goto out;
+		} else {
+			t = strchr(line, ' ');
+			if (t == NULL) {
+				t = strchr(line, ':');
+				if (t == NULL) {
+					t = line + len;
 				}
 			}
-		}
-		for (qup = qlist->head; qup; qup = qup->next) {
-			struct quotaval *q = qup->qv;
-			char b1[32], b2[32];
-			if (strcmp(fsp, qup->fsname))
-				continue;
-			if (version == 1 && dflag) {
-				q[QO_BLK].qv_grace = graceb;
-				q[QO_FL].qv_grace = gracei;
-				qup->found = 1;
+			*t = '\0';
+
+			if (*line == '#') {
+				/* commented out; ignore */
 				continue;
 			}
 
-			if (strcmp(intprt(b1, 21, q[QO_BLK].qv_usage,
-			    HN_NOSPACE | HN_B, Hflag),
-			    scurb) != 0 ||
-			    strcmp(intprt(b2, 21, q[QO_FL].qv_usage,
-			    HN_NOSPACE, Hflag),
-			    scuri) != 0) {
-				warnx("%s: cannot change current allocation",
-				    fsp);
-				break;
+			for (qup = qlist->head; qup; qup = qup->next) {
+				if (!strcmp(line, qup->fsname))
+					break;
+			}
+			if (qup == NULL) {
+				warnx("Line %u: Filesystem %s invalid or has "
+				      "no quota support", lineno, line);
+				goto fail;
 			}
-			/*
-			 * Cause time limit to be reset when the quota
-			 * is next used if previously had no soft limit
-			 * or were under it, but now have a soft limit
-			 * and are over it.
-			 */
-			if (q[QO_BLK].qv_usage &&
-			    q[QO_BLK].qv_usage >= softb &&
-			    (q[QO_BLK].qv_softlimit == 0 ||
-			     q[QO_BLK].qv_usage < q[QO_BLK].qv_softlimit))
-				q[QO_BLK].qv_expiretime = 0;
-			if (q[QO_FL].qv_usage &&
-			    q[QO_FL].qv_usage >= softi &&
-			    (q[QO_FL].qv_softlimit == 0 ||
-			     q[QO_FL].qv_usage < q[QO_FL].qv_softlimit))
-				q[QO_FL].qv_expiretime = 0;
-			q[QO_BLK].qv_softlimit = softb;
-			q[QO_BLK].qv_hardlimit = hardb;
-			if (version == 2)
-				q[QO_BLK].qv_grace = graceb;
-			q[QO_FL].qv_softlimit  = softi;
-			q[QO_FL].qv_hardlimit  = hardi;
-			if (version == 2)
-				q[QO_FL].qv_grace = gracei;
-			qup->found = 1;
+			haveobjtype = false;
 		}
 	}
-out:
+
 	fclose(fd);
+
 	/*
-	 * Disable quotas for any filesystems that have not been found.
+	 * Disable quotas for any filesystems that we didn't see,
+	 * because they must have been deleted in the editor.
+	 *
+	 * XXX this should be improved so it results in
+	 * quota_delete(), not just writing out a blank quotaval.
 	 */
 	for (qup = qlist->head; qup; qup = qup->next) {
-		struct quotaval *q = qup->qv;
 		if (qup->found) {
 			qup->found = 0;
 			continue;
 		}
-		q[QO_BLK].qv_softlimit = UQUAD_MAX;
-		q[QO_BLK].qv_hardlimit = UQUAD_MAX;
-		q[QO_BLK].qv_grace = 0;
-		q[QO_FL].qv_softlimit = UQUAD_MAX;
-		q[QO_FL].qv_hardlimit = UQUAD_MAX;
-		q[QO_FL].qv_grace = 0;
+
+		if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
+			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_BLOCKS]);
+			qup->source[QUOTA_OBJTYPE_BLOCKS] = SRC_EDITED;
+		}
+
+		if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
+			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_FILES]);
+			qup->source[QUOTA_OBJTYPE_FILES] = SRC_EDITED;
+		}
 	}
-	return 1;
+	return 0;
+
+fail:
+	sleep(3);
+	fclose(fd);
+	return -1;
 }
 
 ////////////////////////////////////////////////////////////
@@ -1026,6 +1120,8 @@ replicate(const char *fs, int idtype, co
 	for (qup = protoprivs->head; qup; qup = qup->next) {
 		qup->qv[QO_BLK].qv_expiretime = 0;
 		qup->qv[QO_FL].qv_expiretime = 0;
+		qup->source[QO_BLK] = SRC_EDITED;
+		qup->source[QO_FL] = SRC_EDITED;
 	}
 	for (i=0; i<numnames; i++) {
 		id = getidbyname(names[i], idtype);
@@ -1119,6 +1215,8 @@ assign(const char *fs, int idtype,
 				q[QO_BLK].qv_grace = graceb;
 				q[QO_FL].qv_grace = gracei;
 			}
+			lqup->source[QO_BLK] = SRC_EDITED;
+			lqup->source[QO_FL] = SRC_EDITED;
 		}
 		putprivs(id, idtype, curprivs);
 		quotalist_destroy(curprivs);
@@ -1157,7 +1255,7 @@ editone(const char *fs, int idtype, cons
 	if (editit(tmppath) == 0)
 		goto fail;
 
-	if (readprivs(curprivs, tmpfd, dflag) == 0)
+	if (readprivs(curprivs, tmpfd, dflag) < 0)
 		goto fail;
 
 	putprivs(id, idtype, curprivs);

Reply via email to