git: a2630fec78b6 - stable/13 - quot: Rewrite -n mode input parser

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Wed, 19 Nov 2025 11:21:21 UTC
The branch stable/13 has been updated by des:

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

commit a2630fec78b60844e68cae8b9e1f65b7322a5f05
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-19 11:20:30 +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
    
    (cherry picked from commit fa272a5276865a97b01823fe6546940eaaf1b164)
    (cherry picked from commit 179fa1d81c73ab7ef231e17da73f230e4f8ee5a2)
---
 usr.sbin/quot/quot.8             |  3 ++-
 usr.sbin/quot/quot.c             | 47 ++++++++++++++++++++++------------------
 usr.sbin/quot/tests/quot_test.sh | 19 ++++++++++++++++
 3 files changed, 47 insertions(+), 22 deletions(-)

diff --git a/usr.sbin/quot/quot.8 b/usr.sbin/quot/quot.8
index 0338457f6aeb..b777aef7288e 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 February 8, 1994
+.Dd November 13, 2025
 .Dt QUOT 8
 .Os
 .Sh NAME
@@ -65,6 +65,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 28a7173e7340..acde6e411091 100644
--- a/usr.sbin/quot/quot.c
+++ b/usr.sbin/quot/quot.c
@@ -40,9 +40,10 @@
 #include <ufs/ffs/fs.h>
 
 #include <err.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <fstab.h>
-#include <errno.h>
+#include <inttypes.h>
 #include <libufs.h>
 #include <paths.h>
 #include <pwd.h>
@@ -480,43 +481,47 @@ douser(int fd, struct fs *super, char *name)
 static void
 donames(int fd, struct fs *super, char *name)
 {
-	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;
 		}
 		errno = 0;
 		if ((dp = get_inode(fd,super,inode))
 		    && !isfree(super, dp)) {
 			printf("%s\t",user(DIP(super, dp, di_uid))->name);
 			/* now skip whitespace */
-			while ((c = getchar()) == ' ' || c == '\t');
+			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 {
 			if (errno) {
 				err(1, "%s", name);
 			}
 			/* skip this line */
-			while ((c = getchar()) != EOF && c != '\n');
 		}
-		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 7da9d23ca11f..fd3d6df7b021 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:" >expect
 	printf "%5d\t%5d\t%-8s\n" 8 2 "#0" >>expect
+	printf "%s\n" "/dev/$dev" >ninput
+	echo "/dev/$dev:" >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
@@ -49,6 +64,10 @@ quot_test()
 	# that everything gets flushed to the memory disk.
 	atf_check mount -ur /dev/$dev
 	atf_check -o file:expect quot -fkN /dev/$dev
+	# 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