git: fa272a527686 - main - quot: Rewrite -n mode input parser

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Fri, 14 Nov 2025 14:29:08 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=fa272a5276865a97b01823fe6546940eaaf1b164

commit fa272a5276865a97b01823fe6546940eaaf1b164
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-11-14 14:28:40 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-11-14 14:29:05 +0000

    quot: Rewrite -n mode input parser
    
    The existing parser was needlessly complicated and wildly inconsistent
    in how it handled invalid input.  Rewrite using getline() and treat
    invalid input consistently: silently ignore lines that don't begin with
    a number, and print a warning if the inode number is out of range.
    
    PR:             290992
    MFC after:      1 week
    Reviewed by:    obrien
    Differential Revision:  https://reviews.freebsd.org/D53726
---
 usr.sbin/quot/quot.8             |  3 ++-
 usr.sbin/quot/quot.c             | 47 +++++++++++++++++++++-------------------
 usr.sbin/quot/tests/quot_test.sh | 19 ++++++++++++++++
 3 files changed, 46 insertions(+), 23 deletions(-)

diff --git a/usr.sbin/quot/quot.8 b/usr.sbin/quot/quot.8
index 32e666e2a863..69c0a2d84b9b 100644
--- a/usr.sbin/quot/quot.8
+++ b/usr.sbin/quot/quot.8
@@ -27,7 +27,7 @@
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd October 15, 2025
+.Dd November 13, 2025
 .Dt QUOT 8
 .Os
 .Sh NAME
@@ -60,6 +60,7 @@ By default, all sizes are reported in 512-byte block counts.
 Given a list of inodes (plus some optional data on each line)
 in the standard input, for each file print out the owner (plus
 the remainder of the input line).
+Lines that do not begin with a number are ignored.
 This is traditionally used
 in the pipe:
 .Bd -literal -offset indent
diff --git a/usr.sbin/quot/quot.c b/usr.sbin/quot/quot.c
index 5dda36ac8499..d2f7646f7041 100644
--- a/usr.sbin/quot/quot.c
+++ b/usr.sbin/quot/quot.c
@@ -41,6 +41,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <fstab.h>
+#include <inttypes.h>
 #include <libufs.h>
 #include <mntopts.h>
 #include <paths.h>
@@ -390,41 +391,43 @@ douser(int fd, struct fs *super)
 static void
 donames(int fd, struct fs *super)
 {
-	int c;
-	ino_t maxino;
-	uintmax_t inode;
 	union dinode *dp;
+	char *end, *line;
+	size_t cap;
+	ssize_t len;
+	intmax_t inode, maxino;
 
 	maxino = super->fs_ncg * super->fs_ipg - 1;
-	/* first skip the name of the filesystem */
-	while ((c = getchar()) != EOF && (c < '0' || c > '9'))
-		while ((c = getchar()) != EOF && c != '\n');
-	ungetc(c, stdin);
-	while (scanf("%ju", &inode) == 1) {
-		if (inode > maxino) {
-			warnx("illegal inode %ju", inode);
-			return;
+	line = NULL;
+	cap = 0;
+	while ((len = getline(&line, &cap, stdin)) > 0) {
+		if (len > 0 && line[len - 1] == '\n')
+			line[--len] = '\0';
+		inode = strtoimax(line, &end, 10);
+		/*
+		 * Silently ignore lines that do not begin with a number.
+		 * For backward compatibility reasons, we do not require
+		 * the optional comment to be preceded by whitespace.
+		 */
+		if (end == line)
+			continue;
+		if (inode <= 0 || inode > maxino) {
+			warnx("invalid inode %jd", inode);
+			continue;
 		}
 		if ((dp = get_inode(fd, super, inode)) != NULL &&
 		    !isfree(super, dp)) {
 			printf("%s\t", user(DIP(super, dp, di_uid))->name);
 			/* now skip whitespace */
-			while ((c = getchar()) == ' ' || c == '\t')
-				/* nothing */;
+			while (*end == ' ' || *end == '\t')
+				end++;
 			/* and print out the remainder of the input line */
-			while (c != EOF && c != '\n') {
-				putchar(c);
-				c = getchar();
-			}
-			putchar('\n');
+			printf("%s\n", end);
 		} else {
 			/* skip this line */
-			while ((c = getchar()) != EOF && c != '\n')
-				/* nothing */;
 		}
-		if (c == EOF)
-			break;
 	}
+	free(line);
 }
 
 static void
diff --git a/usr.sbin/quot/tests/quot_test.sh b/usr.sbin/quot/tests/quot_test.sh
index 21088d162a53..c5e6717adca1 100644
--- a/usr.sbin/quot/tests/quot_test.sh
+++ b/usr.sbin/quot/tests/quot_test.sh
@@ -15,6 +15,8 @@ quot_setup()
 	atf_check mount /dev/$dev "$mnt"
 	echo "/dev/$dev: ($mnt)" >expect
 	printf "%5d\t%5d\t%-8s\n" 8 2 "#0" >>expect
+	printf "%s\n" "/dev/$dev" >ninput
+	echo "/dev/$dev: ($mnt)" >nexpect
 }
 
 # Create a directory owned by a given UID
@@ -23,12 +25,25 @@ quot_adduid()
 	local uid=$1
 	atf_check install -d -o $uid -g 0 mnt/$uid
 	printf "%5d\t%5d\t%-8s\n" 4 1 "#$uid" >>expect
+	ls -di mnt/$uid >>ninput
+	printf "%s\t%s\n" "#$uid" mnt/$uid >>nexpect
 }
 
 # Perform the tests
 quot_test()
 {
 	local dev=$(cat dev)
+	# Deliberately add invalid lines to our -n input before the
+	# valid ones to verify that quot does not abort on first
+	# error.  Note that quot deliberately ignores initial lines
+	# that don't start with a number, and that after encountering
+	# at least one line that does start with a number, quot would
+	# previously terminate on encountering one that doesn't (now
+	# it simply ignores them).  This also tests that we don't
+	# require whitespace between the inode number and the comment.
+	echo "0zero" >>ninput
+	echo "invalid" >>ninput
+	echo "-1minusone" >>ninput
 	# Create inodes owned by a large number of users to exercise
 	# hash collisions and rehashing.  The code uses an open hash
 	# table that starts out with only 8 entries and doubles every
@@ -50,6 +65,10 @@ quot_test()
 	atf_check mount -ur /dev/$dev
 	atf_check -o file:expect quot -fkN /dev/$dev
 	atf_check -o file:expect quot -fkN $(realpath mnt)
+	# Test -n option
+	atf_check -o file:nexpect \
+	    -e inline:"quot: invalid inode 0\nquot: invalid inode -1\n" \
+	    quot -Nn /dev/$dev <ninput
 }
 
 # Unmount and release the memory disk